devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/9] Amlogic C3 ISP support
@ 2024-09-18  6:07 Keke Li via B4 Relay
  2024-09-18  6:07 ` [PATCH v3 1/9] dt-bindings: media: Add amlogic,c3-mipi-csi2.yaml Keke Li via B4 Relay
                   ` (8 more replies)
  0 siblings, 9 replies; 37+ messages in thread
From: Keke Li via B4 Relay @ 2024-09-18  6:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-media, devicetree, linux-kernel, kieran.bingham,
	laurent.pinchart, dan.scally, Keke Li, Krzysztof Kozlowski

The Amlogic C3 platform integrates an ISP capable of supporting
multi-camera, multi-exposure high dynamic range (HDR) imaging with up to
14-bit raw RGB Bayer data.
    
Capturing images on the C3 involves operating the CSI2 receiver and PHY,
an adapter layer that integrates the inline processing from the PHY to
the ISP, and the ISP driver itself.
    
This implementation consists of several distinct module drivers and
is expected to support different platforms in the future.

---
Changes in v3:
- Moved change log here.
- Use "port" instead of "ports" in amogic,c3-isp.yaml.
- Reworked c3_xxx_vb2_queue_setup() to meet the request for larger size.
- Reworked c3_mipi_csi_write(), c3_mipi_csi_update_bits() and 
  c3_mipi_csi_update_bits() to return void.
- Reworked c3_mipi_xxx_set_fmt() to return v4l2_subdev_get_fmt()
  in second "if" segment and delete the first "if" segment.
- Switched back to .remove() in struct platform_driver c3_xxx_driver.
- Adjusted from three lines to two lines in some places
  where registers are changed.
- Rename the title of the drivers in MAINTAINERS file.
- Update the v4l2-compliance report.
- Link to v2: https://lore.kernel.org/r/20240909-c3isp-v2-0-3c866a1cea56@amlogic.com

Changes in v2:
- Drop "minItems" and some blank lines in amlogic,c3-xxx.yaml.
- Rename items of the reg-names in amlogic,c3-mipi-csi2.yaml and
  amlogic,c3-mipi-adapter.yaml.
- Replace dev_err and return with dev_err_probe in probe function.
- Change clock[] to clocks[] in struct c3_xxx_info.
- Change clock_rate[] to clock_rates[] in struct c3_xxx_info.
- Change clock[] to clks[] in struct xxx_device.
- Link to v1: https://lore.kernel.org/r/20240903-c3isp-v1-0-8af0edcc13c8@amlogic.com

The rest of this message is the v4l2-compliance report for the driver.
The v4l2-compliance reports 4 failures and 2 warnings. The 2 warnings
are from imx290 driver(drivers/media/i2c/imx290.c). The 4 failures are
because height and width are set to 0 for MEDIA_BUS_FMT_METADATA_FIXED
in isp-core subdev, but this is a document requiement.

v4l2-compliance 1.29.0-5246, 64 bits, 64-bit time_t
v4l2-compliance SHA: 201ccf743cde 2024-09-06 07:16:29

Compliance test for c3-isp device /dev/media0:

Media Driver Info:
        Driver name      : c3-isp
        Model            : c3-isp
        Serial           : 
        Bus info         : platform:ff000000.isp
        Media version    : 6.11.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 6.11.0

Required ioctls:
        test MEDIA_IOC_DEVICE_INFO: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/media0 open: OK
        test MEDIA_IOC_DEVICE_INFO: OK
        test for unlimited opens: OK

Media Controller ioctls:
        test MEDIA_IOC_G_TOPOLOGY: OK
        Entities: 12 Interfaces: 12 Pads: 20 Links: 23
        test MEDIA_IOC_ENUM_ENTITIES/LINKS: OK
        test MEDIA_IOC_SETUP_LINK: OK

Total for c3-isp device /dev/media0: 8, Succeeded: 8, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for c3-isp device /dev/video0:

Driver Info:
        Driver name      : c3-isp
        Card type        : AML C3 ISP
        Bus info         : platform:ff000000.isp
        Driver version   : 6.11.0
        Capabilities     : 0x84200001
                Video Capture
                Streaming
                Extended Pix Format
                Device Capabilities
        Device Caps      : 0x04200001
                Video Capture
                Streaming
                Extended Pix Format
Media Driver Info:
        Driver name      : c3-isp
        Model            : c3-isp
        Serial           : 
        Bus info         : platform:ff000000.isp
        Media version    : 6.11.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 6.11.0
Interface Info:
        ID               : 0x0300001b
        Type             : V4L Video
Entity Info:
        ID               : 0x00000019 (25)
        Name             : isp-cap0
        Function         : V4L2 I/O
        Pad 0x0100001a   : 0: Sink
          Link 0x0200002d: from remote pad 0x1000008 of entity 'isp-resizer0' (Video Scaler): Data, Enabled

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/video0 open: OK
        test VIDIOC_QUERYCAP: OK
        test VIDIOC_G/S_PRIORITY: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK
        test VIDIOC_TRY_FMT: OK
        test VIDIOC_S_FMT: OK
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK
        test Requests: OK (Not Supported)
        test blocking wait: OK

Total for c3-isp device /dev/video0: 49, Succeeded: 49, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for c3-isp device /dev/video1:

Driver Info:
        Driver name      : c3-isp
        Card type        : AML C3 ISP
        Bus info         : platform:ff000000.isp
        Driver version   : 6.11.0
        Capabilities     : 0x84200001
                Video Capture
                Streaming
                Extended Pix Format
                Device Capabilities
        Device Caps      : 0x04200001
                Video Capture
                Streaming
                Extended Pix Format
Media Driver Info:
        Driver name      : c3-isp
        Model            : c3-isp
        Serial           : 
        Bus info         : platform:ff000000.isp
        Media version    : 6.11.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 6.11.0
Interface Info:
        ID               : 0x0300001f
        Type             : V4L Video
Entity Info:
        ID               : 0x0000001d (29)
        Name             : isp-cap1
        Function         : V4L2 I/O
        Pad 0x0100001e   : 0: Sink
          Link 0x02000031: from remote pad 0x100000b of entity 'isp-resizer1' (Video Scaler): Data

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/video1 open: OK
        test VIDIOC_QUERYCAP: OK
        test VIDIOC_G/S_PRIORITY: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK
        test VIDIOC_TRY_FMT: OK
        test VIDIOC_S_FMT: OK
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK
        test Requests: OK (Not Supported)
        test blocking wait: OK

Total for c3-isp device /dev/video1: 49, Succeeded: 49, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for c3-isp device /dev/video2:

Driver Info:
        Driver name      : c3-isp
        Card type        : AML C3 ISP
        Bus info         : platform:ff000000.isp
        Driver version   : 6.11.0
        Capabilities     : 0x84200001
                Video Capture
                Streaming
                Extended Pix Format
                Device Capabilities
        Device Caps      : 0x04200001
                Video Capture
                Streaming
                Extended Pix Format
Media Driver Info:
        Driver name      : c3-isp
        Model            : c3-isp
        Serial           : 
        Bus info         : platform:ff000000.isp
        Media version    : 6.11.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 6.11.0
Interface Info:
        ID               : 0x03000023
        Type             : V4L Video
Entity Info:
        ID               : 0x00000021 (33)
        Name             : isp-cap2
        Function         : V4L2 I/O
        Pad 0x01000022   : 0: Sink
          Link 0x02000035: from remote pad 0x100000e of entity 'isp-resizer2' (Video Scaler): Data

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/video2 open: OK
        test VIDIOC_QUERYCAP: OK
        test VIDIOC_G/S_PRIORITY: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK
        test VIDIOC_TRY_FMT: OK
        test VIDIOC_S_FMT: OK
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK
        test Requests: OK (Not Supported)
        test blocking wait: OK

Total for c3-isp device /dev/video2: 49, Succeeded: 49, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for c3-isp device /dev/video3:

Driver Info:
        Driver name      : c3-isp
        Card type        : AML C3 ISP
        Bus info         : platform:ff000000.isp
        Driver version   : 6.11.0
        Capabilities     : 0x84a00000
                Metadata Capture
                Streaming
                Extended Pix Format
                Device Capabilities
        Device Caps      : 0x04a00000
                Metadata Capture
                Streaming
                Extended Pix Format
Media Driver Info:
        Driver name      : c3-isp
        Model            : c3-isp
        Serial           : 
        Bus info         : platform:ff000000.isp
        Media version    : 6.11.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 6.11.0
Interface Info:
        ID               : 0x03000027
        Type             : V4L Video
Entity Info:
        ID               : 0x00000025 (37)
        Name             : isp-stats
        Function         : V4L2 I/O
        Pad 0x01000026   : 0: Sink
          Link 0x02000039: from remote pad 0x1000004 of entity 'isp-core' (Video Pixel Formatter): Data, Enabled

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/video3 open: OK
        test VIDIOC_QUERYCAP: OK
        test VIDIOC_G/S_PRIORITY: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK
        test VIDIOC_TRY_FMT: OK
        test VIDIOC_S_FMT: OK
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK (Not Supported)

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK
        test Requests: OK (Not Supported)
        test blocking wait: OK

Total for c3-isp device /dev/video3: 49, Succeeded: 49, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for c3-isp device /dev/video4:

Driver Info:
        Driver name      : c3-isp
        Card type        : AML C3 ISP
        Bus info         : platform:ff000000.isp
        Driver version   : 6.11.0
        Capabilities     : 0x8c200000
                Metadata Output
                Streaming
                Extended Pix Format
                Device Capabilities
        Device Caps      : 0x0c200000
                Metadata Output
                Streaming
                Extended Pix Format
Media Driver Info:
        Driver name      : c3-isp
        Model            : c3-isp
        Serial           : 
        Bus info         : platform:ff000000.isp
        Media version    : 6.11.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 6.11.0
Interface Info:
        ID               : 0x0300002b
        Type             : V4L Video
Entity Info:
        ID               : 0x00000029 (41)
        Name             : isp-params
        Function         : V4L2 I/O
        Pad 0x0100002a   : 0: Source
          Link 0x0200003b: to remote pad 0x1000003 of entity 'isp-core' (Video Pixel Formatter): Data, Enabled

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/video4 open: OK
        test VIDIOC_QUERYCAP: OK
        test VIDIOC_G/S_PRIORITY: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK
        test VIDIOC_TRY_FMT: OK
        test VIDIOC_S_FMT: OK
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK (Not Supported)

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK
        test Requests: OK (Not Supported)
        test blocking wait: OK

Total for c3-isp device /dev/video4: 49, Succeeded: 49, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for c3-isp device /dev/v4l-subdev0:

Driver Info:
        Driver version   : 6.11.0
        Capabilities     : 0x00000000
        Client Capabilities: 0x0000000000000002
interval-uses-which Media Driver Info:
        Driver name      : c3-isp
        Model            : c3-isp
        Serial           : 
        Bus info         : platform:ff000000.isp
        Media version    : 6.11.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 6.11.0
Interface Info:
        ID               : 0x03000041
        Type             : V4L Sub-Device
Entity Info:
        ID               : 0x00000001 (1)
        Name             : isp-core
        Function         : Video Pixel Formatter
        Pad 0x01000002   : 0: Sink
          Link 0x02000012: from remote pad 0x1000011 of entity 'mipi-adapter' (Video Interface Bridge): Data, Enabled, Immutable
        Pad 0x01000003   : 1: Sink
          Link 0x0200003b: from remote pad 0x100002a of entity 'isp-params' (V4L2 I/O): Data, Enabled
        Pad 0x01000004   : 2: Source
          Link 0x02000039: to remote pad 0x1000026 of entity 'isp-stats' (V4L2 I/O): Data, Enabled
        Pad 0x01000005   : 3: Source
          Link 0x0200002f: to remote pad 0x1000007 of entity 'isp-resizer0' (Video Scaler): Data, Enabled
          Link 0x02000033: to remote pad 0x100000a of entity 'isp-resizer1' (Video Scaler): Data
          Link 0x02000037: to remote pad 0x100000d of entity 'isp-resizer2' (Video Scaler): Data

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_SUDBEV_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/v4l-subdev0 open: OK
        test VIDIOC_SUBDEV_QUERYCAP: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Sub-Device ioctls (Sink Pad 0):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Try VIDIOC_SUBDEV_G/S_FMT: OK
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Active VIDIOC_SUBDEV_G/S_FMT: OK
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Sub-Device ioctls (Sink Pad 1):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
                fail: v4l2-test-subdevs.cpp(369): fmt.width == 0 || fmt.width > 65536
                fail: v4l2-test-subdevs.cpp(418): checkMBusFrameFmt(node, fmt.format)
        test Try VIDIOC_SUBDEV_G/S_FMT: FAIL
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
                fail: v4l2-test-subdevs.cpp(369): fmt.width == 0 || fmt.width > 65536
                fail: v4l2-test-subdevs.cpp(418): checkMBusFrameFmt(node, fmt.format)
        test Active VIDIOC_SUBDEV_G/S_FMT: FAIL
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Sub-Device ioctls (Source Pad 2):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
                fail: v4l2-test-subdevs.cpp(369): fmt.width == 0 || fmt.width > 65536
                fail: v4l2-test-subdevs.cpp(418): checkMBusFrameFmt(node, fmt.format)
        test Try VIDIOC_SUBDEV_G/S_FMT: FAIL
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
                fail: v4l2-test-subdevs.cpp(369): fmt.width == 0 || fmt.width > 65536
                fail: v4l2-test-subdevs.cpp(418): checkMBusFrameFmt(node, fmt.format)
        test Active VIDIOC_SUBDEV_G/S_FMT: FAIL
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Sub-Device ioctls (Source Pad 3):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Try VIDIOC_SUBDEV_G/S_FMT: OK
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Active VIDIOC_SUBDEV_G/S_FMT: OK
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK (Not Supported)
        test VIDIOC_TRY_FMT: OK (Not Supported)
        test VIDIOC_S_FMT: OK (Not Supported)
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK (Not Supported)

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK (Not Supported)
        test Requests: OK (Not Supported)
        test blocking wait: OK (Not Supported)

Total for c3-isp device /dev/v4l-subdev0: 75, Succeeded: 71, Failed: 4, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for c3-isp device /dev/v4l-subdev1:

Driver Info:
        Driver version   : 6.11.0
        Capabilities     : 0x00000000
        Client Capabilities: 0x0000000000000002
interval-uses-which Media Driver Info:
        Driver name      : c3-isp
        Model            : c3-isp
        Serial           : 
        Bus info         : platform:ff000000.isp
        Media version    : 6.11.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 6.11.0
Interface Info:
        ID               : 0x03000043
        Type             : V4L Sub-Device
Entity Info:
        ID               : 0x00000006 (6)
        Name             : isp-resizer0
        Function         : Video Scaler
        Pad 0x01000007   : 0: Sink
          Link 0x0200002f: from remote pad 0x1000005 of entity 'isp-core' (Video Pixel Formatter): Data, Enabled
        Pad 0x01000008   : 1: Source
          Link 0x0200002d: to remote pad 0x100001a of entity 'isp-cap0' (V4L2 I/O): Data, Enabled

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_SUDBEV_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/v4l-subdev1 open: OK
        test VIDIOC_SUBDEV_QUERYCAP: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Sub-Device ioctls (Sink Pad 0):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Try VIDIOC_SUBDEV_G/S_FMT: OK
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Active VIDIOC_SUBDEV_G/S_FMT: OK
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Sub-Device ioctls (Source Pad 1):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Try VIDIOC_SUBDEV_G/S_FMT: OK
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Active VIDIOC_SUBDEV_G/S_FMT: OK
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK (Not Supported)
        test VIDIOC_TRY_FMT: OK (Not Supported)
        test VIDIOC_S_FMT: OK (Not Supported)
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK (Not Supported)

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK (Not Supported)
        test Requests: OK (Not Supported)
        test blocking wait: OK (Not Supported)

Total for c3-isp device /dev/v4l-subdev1: 61, Succeeded: 61, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for c3-isp device /dev/v4l-subdev2:

Driver Info:
        Driver version   : 6.11.0
        Capabilities     : 0x00000000
        Client Capabilities: 0x0000000000000002
interval-uses-which Media Driver Info:
        Driver name      : c3-isp
        Model            : c3-isp
        Serial           : 
        Bus info         : platform:ff000000.isp
        Media version    : 6.11.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 6.11.0
Interface Info:
        ID               : 0x03000045
        Type             : V4L Sub-Device
Entity Info:
        ID               : 0x00000009 (9)
        Name             : isp-resizer1
        Function         : Video Scaler
        Pad 0x0100000a   : 0: Sink
          Link 0x02000033: from remote pad 0x1000005 of entity 'isp-core' (Video Pixel Formatter): Data
        Pad 0x0100000b   : 1: Source
          Link 0x02000031: to remote pad 0x100001e of entity 'isp-cap1' (V4L2 I/O): Data

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_SUDBEV_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/v4l-subdev2 open: OK
        test VIDIOC_SUBDEV_QUERYCAP: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Sub-Device ioctls (Sink Pad 0):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Try VIDIOC_SUBDEV_G/S_FMT: OK
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Active VIDIOC_SUBDEV_G/S_FMT: OK
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Sub-Device ioctls (Source Pad 1):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Try VIDIOC_SUBDEV_G/S_FMT: OK
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Active VIDIOC_SUBDEV_G/S_FMT: OK
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK (Not Supported)
        test VIDIOC_TRY_FMT: OK (Not Supported)
        test VIDIOC_S_FMT: OK (Not Supported)
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK (Not Supported)

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK (Not Supported)
        test Requests: OK (Not Supported)
        test blocking wait: OK (Not Supported)

Total for c3-isp device /dev/v4l-subdev2: 61, Succeeded: 61, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for c3-isp device /dev/v4l-subdev3:

Driver Info:
        Driver version   : 6.11.0
        Capabilities     : 0x00000000
        Client Capabilities: 0x0000000000000002
interval-uses-which Media Driver Info:
        Driver name      : c3-isp
        Model            : c3-isp
        Serial           : 
        Bus info         : platform:ff000000.isp
        Media version    : 6.11.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 6.11.0
Interface Info:
        ID               : 0x03000047
        Type             : V4L Sub-Device
Entity Info:
        ID               : 0x0000000c (12)
        Name             : isp-resizer2
        Function         : Video Scaler
        Pad 0x0100000d   : 0: Sink
          Link 0x02000037: from remote pad 0x1000005 of entity 'isp-core' (Video Pixel Formatter): Data
        Pad 0x0100000e   : 1: Source
          Link 0x02000035: to remote pad 0x1000022 of entity 'isp-cap2' (V4L2 I/O): Data

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_SUDBEV_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/v4l-subdev3 open: OK
        test VIDIOC_SUBDEV_QUERYCAP: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Sub-Device ioctls (Sink Pad 0):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Try VIDIOC_SUBDEV_G/S_FMT: OK
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Active VIDIOC_SUBDEV_G/S_FMT: OK
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Sub-Device ioctls (Source Pad 1):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Try VIDIOC_SUBDEV_G/S_FMT: OK
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Active VIDIOC_SUBDEV_G/S_FMT: OK
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK (Not Supported)
        test VIDIOC_TRY_FMT: OK (Not Supported)
        test VIDIOC_S_FMT: OK (Not Supported)
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK (Not Supported)

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK (Not Supported)
        test Requests: OK (Not Supported)
        test blocking wait: OK (Not Supported)

Total for c3-isp device /dev/v4l-subdev3: 61, Succeeded: 61, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for c3-isp device /dev/v4l-subdev4:

Driver Info:
        Driver version   : 6.11.0
        Capabilities     : 0x00000000
        Client Capabilities: 0x0000000000000002
interval-uses-which Media Driver Info:
        Driver name      : c3-isp
        Model            : c3-isp
        Serial           : 
        Bus info         : platform:ff000000.isp
        Media version    : 6.11.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 6.11.0
Interface Info:
        ID               : 0x03000049
        Type             : V4L Sub-Device
Entity Info:
        ID               : 0x0000000f (15)
        Name             : mipi-adapter
        Function         : Video Interface Bridge
        Pad 0x01000010   : 0: Sink
          Link 0x02000017: from remote pad 0x1000016 of entity 'mipi-csi2' (Video Interface Bridge): Data, Enabled, Immutable
        Pad 0x01000011   : 1: Source
          Link 0x02000012: to remote pad 0x1000002 of entity 'isp-core' (Video Pixel Formatter): Data, Enabled, Immutable

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_SUDBEV_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/v4l-subdev4 open: OK
        test VIDIOC_SUBDEV_QUERYCAP: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Sub-Device ioctls (Sink Pad 0):
	Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Try VIDIOC_SUBDEV_G/S_FMT: OK
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Active VIDIOC_SUBDEV_G/S_FMT: OK
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Sub-Device ioctls (Source Pad 1):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Try VIDIOC_SUBDEV_G/S_FMT: OK
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Active VIDIOC_SUBDEV_G/S_FMT: OK
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK (Not Supported)
        test VIDIOC_TRY_FMT: OK (Not Supported)
        test VIDIOC_S_FMT: OK (Not Supported)
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK (Not Supported)

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK (Not Supported)
        test Requests: OK (Not Supported)
        test blocking wait: OK (Not Supported)

Total for c3-isp device /dev/v4l-subdev4: 61, Succeeded: 61, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for c3-isp device /dev/v4l-subdev5:

Driver Info:
        Driver version   : 6.11.0
        Capabilities     : 0x00000000
        Client Capabilities: 0x0000000000000002
interval-uses-which Media Driver Info:
        Driver name      : c3-isp
        Model            : c3-isp
        Serial           : 
        Bus info         : platform:ff000000.isp
        Media version    : 6.11.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 6.11.0
Interface Info:
        ID               : 0x0300004b
        Type             : V4L Sub-Device
Entity Info:
        ID               : 0x00000014 (20)
        Name             : mipi-csi2
        Function         : Video Interface Bridge
        Pad 0x01000015   : 0: Sink
          Link 0x0200003f: from remote pad 0x100003e of entity 'imx290 2-001a' (Camera Sensor): Data, Enabled, Immutable
        Pad 0x01000016   : 1: Source
          Link 0x02000017: to remote pad 0x1000010 of entity 'mipi-adapter' (Video Interface Bridge): Data, Enabled, Immutable

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_SUDBEV_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/v4l-subdev5 open: OK
        test VIDIOC_SUBDEV_QUERYCAP: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Sub-Device ioctls (Sink Pad 0):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Try VIDIOC_SUBDEV_G/S_FMT: OK
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Active VIDIOC_SUBDEV_G/S_FMT: OK
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Sub-Device ioctls (Source Pad 1):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Try VIDIOC_SUBDEV_G/S_FMT: OK
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Active VIDIOC_SUBDEV_G/S_FMT: OK
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK (Not Supported)
        test VIDIOC_TRY_FMT: OK (Not Supported)
        test VIDIOC_S_FMT: OK (Not Supported)
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK (Not Supported)

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK (Not Supported)
        test Requests: OK (Not Supported)
        test blocking wait: OK (Not Supported)

Total for c3-isp device /dev/v4l-subdev5: 61, Succeeded: 61, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for c3-isp device /dev/v4l-subdev6:

Driver Info:
        Driver version   : 6.11.0
        Capabilities     : 0x00000000
        Client Capabilities: 0x0000000000000002
interval-uses-which Media Driver Info:
        Driver name      : c3-isp
        Model            : c3-isp
        Serial           : 
        Bus info         : platform:ff000000.isp
        Media version    : 6.11.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 6.11.0
Interface Info:
        ID               : 0x0300004d
        Type             : V4L Sub-Device
Entity Info:
        ID               : 0x0000003d (61)
        Name             : imx290 2-001a
        Function         : Camera Sensor
        Pad 0x0100003e   : 0: Source
          Link 0x0200003f: to remote pad 0x1000015 of entity 'mipi-csi2' (Video Interface Bridge): Data, Enabled, Immutable

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_SUDBEV_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/v4l-subdev6 open: OK
        test VIDIOC_SUBDEV_QUERYCAP: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Sub-Device ioctls (Source Pad 0):
        Try Stream 0
        test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Try VIDIOC_SUBDEV_G/S_FMT: OK
                warn: v4l2-test-subdevs.cpp(566): VIDIOC_SUBDEV_G_SELECTION is supported for target 0 but not VIDIOC_SUBDEV_S_SELECTION
        test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
        Active Stream 0
        test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
        test Active VIDIOC_SUBDEV_G/S_FMT: OK
                warn: v4l2-test-subdevs.cpp(566): VIDIOC_SUBDEV_G_SELECTION is supported for target 0 but not VIDIOC_SUBDEV_S_SELECTION
        test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
        test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
        test VIDIOC_QUERYCTRL: OK
        test VIDIOC_G/S_CTRL: OK
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 12 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK (Not Supported)
        test VIDIOC_TRY_FMT: OK (Not Supported)
        test VIDIOC_S_FMT: OK (Not Supported)
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK (Not Supported)

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK (Not Supported)
        test Requests: OK (Not Supported)
        test blocking wait: OK (Not Supported)

Total for c3-isp device /dev/v4l-subdev6: 54, Succeeded: 54, Failed: 0, Warnings: 2

Grand Total for c3-isp device /dev/media0: 687, Succeeded: 683, Failed: 4, Warnings: 2

To: Mauro Carvalho Chehab <mchehab@kernel.org>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
Cc: linux-media@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: kieran.bingham@ideasonboard.com
Cc: laurent.pinchart@ideasonboard.com
Cc: dan.scally@ideasonboard.com
Signed-off-by: Keke Li <keke.li@amlogic.com>

---
Keke Li (9):
      dt-bindings: media: Add amlogic,c3-mipi-csi2.yaml
      media: platform: Add c3 mipi csi2 driver
      dt-bindings: media: Add amlogic,c3-mipi-adapter.yaml
      media: platform: Add c3 mipi adapter driver
      dt-bindings: media: Add amlogic,c3-isp.yaml
      media: Add C3ISP_PARAMS and C3ISP_STATS meta formats
      media: platform: Add c3 ISP driver
      Documentation: media: add documentation file metafmt-c3-isp.rst
      Documentation: media: add documentation file c3-isp.rst

 Documentation/admin-guide/media/c3-isp.dot         |  26 +
 Documentation/admin-guide/media/c3-isp.rst         |  96 +++
 Documentation/admin-guide/media/v4l-drivers.rst    |   1 +
 .../devicetree/bindings/media/amlogic,c3-isp.yaml  |  85 ++
 .../bindings/media/amlogic,c3-mipi-adapter.yaml    | 108 +++
 .../bindings/media/amlogic,c3-mipi-csi2.yaml       | 124 +++
 .../userspace-api/media/v4l/meta-formats.rst       |   1 +
 .../userspace-api/media/v4l/metafmt-c3-isp.rst     |  39 +
 MAINTAINERS                                        |  24 +
 drivers/media/platform/amlogic/Kconfig             |   3 +
 drivers/media/platform/amlogic/Makefile            |   4 +
 drivers/media/platform/amlogic/c3-isp/Kconfig      |  17 +
 drivers/media/platform/amlogic/c3-isp/Makefile     |  10 +
 .../media/platform/amlogic/c3-isp/c3-isp-capture.c | 759 +++++++++++++++++
 .../media/platform/amlogic/c3-isp/c3-isp-common.h  | 327 ++++++++
 .../media/platform/amlogic/c3-isp/c3-isp-core.c    | 675 +++++++++++++++
 drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c | 486 +++++++++++
 .../media/platform/amlogic/c3-isp/c3-isp-params.c  | 857 +++++++++++++++++++
 .../media/platform/amlogic/c3-isp/c3-isp-regs.h    | 683 +++++++++++++++
 .../media/platform/amlogic/c3-isp/c3-isp-resizer.c | 768 +++++++++++++++++
 .../media/platform/amlogic/c3-isp/c3-isp-stats.c   | 488 +++++++++++
 .../amlogic/c3-isp/include/uapi/c3-isp-config.h    | 537 ++++++++++++
 .../media/platform/amlogic/c3-mipi-adapter/Kconfig |  16 +
 .../platform/amlogic/c3-mipi-adapter/Makefile      |   3 +
 .../amlogic/c3-mipi-adapter/c3-mipi-adap.c         | 913 +++++++++++++++++++++
 .../media/platform/amlogic/c3-mipi-csi2/Kconfig    |  16 +
 .../media/platform/amlogic/c3-mipi-csi2/Makefile   |   3 +
 .../platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c   | 910 ++++++++++++++++++++
 drivers/media/v4l2-core/v4l2-ioctl.c               |   2 +
 include/uapi/linux/videodev2.h                     |   4 +
 30 files changed, 7985 insertions(+)
---
base-commit: 4f3e012d4cfd1d9bf837870c961f462ca9f23ebe
change-id: 20240903-c3isp-7cfe70bb2583

Best regards,
-- 
Keke Li <keke.li@amlogic.com>



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

* [PATCH v3 1/9] dt-bindings: media: Add amlogic,c3-mipi-csi2.yaml
  2024-09-18  6:07 [PATCH v3 0/9] Amlogic C3 ISP support Keke Li via B4 Relay
@ 2024-09-18  6:07 ` Keke Li via B4 Relay
  2024-09-18  6:07 ` [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver Keke Li via B4 Relay
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 37+ messages in thread
From: Keke Li via B4 Relay @ 2024-09-18  6:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-media, devicetree, linux-kernel, kieran.bingham,
	laurent.pinchart, dan.scally, Keke Li, Krzysztof Kozlowski

From: Keke Li <keke.li@amlogic.com>

c3-mipi-csi2 is used to receive mipi data from image sensor.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Keke Li <keke.li@amlogic.com>
---
 .../bindings/media/amlogic,c3-mipi-csi2.yaml       | 124 +++++++++++++++++++++
 1 file changed, 124 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml b/Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml
new file mode 100644
index 000000000000..5cea06595205
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml
@@ -0,0 +1,124 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/amlogic,c3-mipi-csi2.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic C3 MIPI CSI-2 receiver
+
+maintainers:
+  - Keke Li <keke.li@amlogic.com>
+
+description:
+  MIPI CSI-2 receiver contains CSI-2 RX PHY and host controller.
+  It receives the MIPI data from the image sensor and sends MIPI data
+  to MIPI adapter.
+
+properties:
+  compatible:
+    enum:
+      - amlogic,c3-mipi-csi2
+
+  reg:
+    maxItems: 3
+
+  reg-names:
+    items:
+      - const: aphy
+      - const: dphy
+      - const: host
+
+  power-domains:
+    maxItems: 1
+
+  clocks:
+    maxItems: 2
+
+  clock-names:
+    items:
+      - const: vapb
+      - const: phy0
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
+        description: input port node, connected to sensor.
+
+        properties:
+          endpoint:
+            $ref: video-interfaces.yaml#
+            unevaluatedProperties: false
+
+            properties:
+              data-lanes:
+                minItems: 1
+                maxItems: 4
+
+            required:
+              - data-lanes
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: output port node
+
+    required:
+      - port@0
+      - port@1
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - power-domains
+  - clocks
+  - clock-names
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/amlogic,c3-peripherals-clkc.h>
+    #include <dt-bindings/power/amlogic,c3-pwrc.h>
+
+    soc {
+        #address-cells = <2>;
+        #size-cells = <2>;
+
+        csi: csi@ff018000 {
+            compatible = "amlogic,c3-mipi-csi2";
+            reg = <0x0 0xff018000 0x0 0x400>,
+                  <0x0 0xff019000 0x0 0x300>,
+                  <0x0 0xff01a000 0x0 0x100>;
+            reg-names = "aphy", "dphy", "host";
+            power-domains = <&pwrc PWRC_C3_MIPI_ISP_WRAP_ID>;
+            clocks =  <&clkc_periphs CLKID_VAPB>,
+                      <&clkc_periphs CLKID_CSI_PHY0>;
+            clock-names = "vapb", "phy0";
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+                    c3_mipi_csi_in: endpoint {
+                        remote-endpoint = <&imx290_out>;
+                        data-lanes = <1 2 3 4>;
+                    };
+                };
+
+                port@1 {
+                    reg = <1>;
+                    c3_mipi_csi_out: endpoint {
+                        remote-endpoint = <&c3_adap_in>;
+                    };
+                };
+            };
+        };
+    };
+...

-- 
2.46.1



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

* [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver
  2024-09-18  6:07 [PATCH v3 0/9] Amlogic C3 ISP support Keke Li via B4 Relay
  2024-09-18  6:07 ` [PATCH v3 1/9] dt-bindings: media: Add amlogic,c3-mipi-csi2.yaml Keke Li via B4 Relay
@ 2024-09-18  6:07 ` Keke Li via B4 Relay
  2024-11-04 17:50   ` Jacopo Mondi
  2024-11-05  8:21   ` Jacopo Mondi
  2024-09-18  6:07 ` [PATCH v3 3/9] dt-bindings: media: Add amlogic,c3-mipi-adapter.yaml Keke Li via B4 Relay
                   ` (6 subsequent siblings)
  8 siblings, 2 replies; 37+ messages in thread
From: Keke Li via B4 Relay @ 2024-09-18  6:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-media, devicetree, linux-kernel, kieran.bingham,
	laurent.pinchart, dan.scally, Keke Li

From: Keke Li <keke.li@amlogic.com>

This driver is used to receive mipi data from image sensor.

Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Signed-off-by: Keke Li <keke.li@amlogic.com>
---
 MAINTAINERS                                        |   7 +
 drivers/media/platform/amlogic/Kconfig             |   1 +
 drivers/media/platform/amlogic/Makefile            |   2 +
 .../media/platform/amlogic/c3-mipi-csi2/Kconfig    |  16 +
 .../media/platform/amlogic/c3-mipi-csi2/Makefile   |   3 +
 .../platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c   | 910 +++++++++++++++++++++
 6 files changed, 939 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 2cdd7cacec86..9e75874a6e69 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1209,6 +1209,13 @@ F:	Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
 F:	drivers/perf/amlogic/
 F:	include/soc/amlogic/
 
+AMLOGIC MIPI CSI2 DRIVER
+M:	Keke Li <keke.li@amlogic.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml
+F:	drivers/media/platform/amlogic/c3-mipi-csi2/
+
 AMPHENOL CHIPCAP 2 HUMIDITY-TEMPERATURE IIO DRIVER
 M:	Javier Carrasco <javier.carrasco.cruz@gmail.com>
 L:	linux-hwmon@vger.kernel.org
diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
index 5014957404e9..b7c2de14848b 100644
--- a/drivers/media/platform/amlogic/Kconfig
+++ b/drivers/media/platform/amlogic/Kconfig
@@ -2,4 +2,5 @@
 
 comment "Amlogic media platform drivers"
 
+source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
 source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
index d3cdb8fa4ddb..4f571ce5d13e 100644
--- a/drivers/media/platform/amlogic/Makefile
+++ b/drivers/media/platform/amlogic/Makefile
@@ -1,2 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
+
+obj-y += c3-mipi-csi2/
 obj-y += meson-ge2d/
diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
new file mode 100644
index 000000000000..0d7b2e203273
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VIDEO_C3_MIPI_CSI2
+	tristate "Amlogic C3 MIPI CSI-2 receiver"
+	depends on ARCH_MESON || COMPILE_TEST
+	depends on VIDEO_DEV
+	depends on OF
+	select MEDIA_CONTROLLER
+	select V4L2_FWNODE
+	select VIDEO_V4L2_SUBDEV_API
+	help
+	  Video4Linux2 driver for Amlogic C3 MIPI CSI-2 receiver.
+	  C3 MIPI CSI-2 receiver is used to receive MIPI data from
+	  image sensor.
+
+	  To compile this driver as a module choose m here.
diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
new file mode 100644
index 000000000000..cc08fc722bfd
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_VIDEO_C3_MIPI_CSI2) += c3-mipi-csi2.o
diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
new file mode 100644
index 000000000000..6ac60d5b26a8
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
@@ -0,0 +1,910 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-async.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-subdev.h>
+
+/* C3 CSI-2 submodule definition */
+enum {
+	SUBMD_APHY,
+	SUBMD_DPHY,
+	SUBMD_HOST,
+};
+
+#define CSI2_SUBMD_MASK             GENMASK(17, 16)
+#define CSI2_SUBMD_SHIFT            16
+#define CSI2_SUBMD(x)               (((x) & (CSI2_SUBMD_MASK)) >> (CSI2_SUBMD_SHIFT))
+#define CSI2_REG_ADDR_MASK          GENMASK(15, 0)
+#define CSI2_REG_ADDR(x)            ((x) & (CSI2_REG_ADDR_MASK))
+#define CSI2_REG_A(x)               ((SUBMD_APHY << CSI2_SUBMD_SHIFT) | (x))
+#define CSI2_REG_D(x)               ((SUBMD_DPHY << CSI2_SUBMD_SHIFT) | (x))
+#define CSI2_REG_H(x)               ((SUBMD_HOST << CSI2_SUBMD_SHIFT) | (x))
+
+#define MIPI_CSI2_CLOCK_NUM_MAX     3
+#define MIPI_CSI2_SUBDEV_NAME       "mipi-csi2"
+
+/* C3 CSI-2 APHY register */
+#define MIPI_CSI_2M_PHY2_CNTL1      CSI2_REG_A(0x44)
+#define MIPI_APHY_NORMAL_CNTL1      0x3f425C00
+
+#define MIPI_CSI_2M_PHY2_CNTL2      CSI2_REG_A(0x48)
+#define MIPI_APHY_4LANES_CNTL2      0x033a0000
+#define MIPI_APHY_NORMAL_CNTL2      0x333a0000
+
+#define MIPI_CSI_2M_PHY2_CNTL3      CSI2_REG_A(0x4c)
+#define MIPI_APHY_2LANES_CNTL3      0x03800000
+
+/* C3 CSI-2 DPHY register */
+#define MIPI_PHY_CTRL	            CSI2_REG_D(0x00)
+#define MIPI_DPHY_LANES_ENABLE      0x0
+
+#define MIPI_PHY_CLK_LANE_CTRL	    CSI2_REG_D(0x04)
+#define MIPI_DPHY_CLK_CONTINUE_MODE 0x3d8
+
+#define MIPI_PHY_DATA_LANE_CTRL     CSI2_REG_D(0x08)
+#define MIPI_DPHY_LANE_CTRL_DISABLE 0x0
+
+#define MIPI_PHY_DATA_LANE_CTRL1    CSI2_REG_D(0x0c)
+#define MIPI_DPHY_INSERT_ERRESC     BIT(0)
+#define MIPI_DPHY_HS_SYNC_CHECK     BIT(1)
+#define MIPI_DPHY_FIVE_HS_PIPE      GENMASK(6, 2)
+#define MIPI_DPHY_FIVE_HS_PIPE_SHIFT           2
+#define MIPI_DPHY_DATA_PIPE_SELECT  GENMASK(9, 7)
+#define MIPI_DPHY_DATA_PIPE_SELECT_SHIFT       7
+
+#define MIPI_PHY_TCLK_MISS	    CSI2_REG_D(0x10)
+#define MIPI_DPHY_CLK_MISS          0x9
+
+#define MIPI_PHY_TCLK_SETTLE	    CSI2_REG_D(0x14)
+#define MIPI_DPHY_CLK_SETTLE        0x1F
+
+#define MIPI_PHY_THS_EXIT	    CSI2_REG_D(0x18)
+#define MIPI_DPHY_HS_EXIT           0x8
+
+#define MIPI_PHY_THS_SKIP	    CSI2_REG_D(0x1c)
+#define MIPI_DPHY_HS_SKIP           0xa
+
+#define MIPI_PHY_THS_SETTLE	    CSI2_REG_D(0x20)
+#define MIPI_PHY_TINIT	            CSI2_REG_D(0x24)
+#define MIPI_DPHY_INIT_CYCLES       0x4e20
+
+#define MIPI_PHY_TULPS_C	    CSI2_REG_D(0x28)
+#define MIPI_DPHY_ULPS_CHECK_CYCLES 0x1000
+
+#define MIPI_PHY_TULPS_S	    CSI2_REG_D(0x2c)
+#define MIPI_DPHY_ULPS_START_CYCLES 0x100
+
+#define MIPI_PHY_TMBIAS             CSI2_REG_D(0x30)
+#define MIPI_DPHY_MBIAS_CYCLES      0x100
+
+#define MIPI_PHY_TLP_EN_W           CSI2_REG_D(0x34)
+#define MIPI_DPHY_ULPS_STOP_CYCLES  0xC
+
+#define MIPI_PHY_TLPOK	            CSI2_REG_D(0x38)
+#define MIPI_DPHY_POWER_UP_CYCLES   0x100
+
+#define MIPI_PHY_TWD_INIT	    CSI2_REG_D(0x3c)
+#define MIPI_DPHY_INIT_WATCH_DOG    0x400000
+
+#define MIPI_PHY_TWD_HS             CSI2_REG_D(0x40)
+#define MIPI_DPHY_HS_WATCH_DOG      0x400000
+
+#define MIPI_PHY_MUX_CTRL0	    CSI2_REG_D(0x284)
+#define MIPI_DPHY_LANE3_SELECT      GENMASK(3, 0)
+#define MIPI_DPHY_LANE2_SELECT      GENMASK(7, 4)
+#define MIPI_DPHY_LANE2_SELECT_SHIFT           4
+#define MIPI_DPHY_LANE1_SELECT      GENMASK(11, 8)
+#define MIPI_DPHY_LANE1_SELECT_SHIFT            8
+#define MIPI_DPHY_LANE0_SELECT      GENMASK(14, 12)
+
+#define MIPI_PHY_MUX_CTRL1	    CSI2_REG_D(0x288)
+#define MIPI_DPHY_LANE3_CTRL_SIGNAL GENMASK(3, 0)
+#define MIPI_DPHY_LANE2_CTRL_SIGNAL GENMASK(7, 4)
+#define MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT      4
+#define MIPI_DPHY_LANE1_CTRL_SIGNAL GENMASK(11, 8)
+#define MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT       8
+#define MIPI_DPHY_LANE0_CTRL_SIGNAL GENMASK(14, 12)
+#define MIPI_DPHY_CLK_SELECT        BIT(17)
+
+/* C3 CSI-2 HOST register */
+#define CSI2_HOST_N_LANES           CSI2_REG_H(0x04)
+#define CSI2_HOST_CSI2_RESETN       CSI2_REG_H(0x10)
+#define CSI2_HOST_RESETN_DEFAULT    0x0
+#define CSI2_HOST_RESETN_RST_VALUE  BIT(0)
+
+#define CSI2_HOST_MASK1             CSI2_REG_H(0x28)
+#define CSI2_HOST_ERROR_MASK1       GENMASK(28, 0)
+
+#define MIPI_CSI2_MAX_WIDTH         2888
+#define MIPI_CSI2_MIN_WIDTH         160
+#define MIPI_CSI2_MAX_HEIGHT        2240
+#define MIPI_CSI2_MIN_HEIGHT        120
+#define MIPI_CSI2_DEFAULT_WIDTH     1920
+#define MIPI_CSI2_DEFAULT_HEIGHT    1080
+#define MIPI_CSI2_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
+
+/* C3 CSI-2 pad list */
+enum {
+	MIPI_CSI2_PAD_SINK,
+	MIPI_CSI2_PAD_SRC,
+	MIPI_CSI2_PAD_MAX
+};
+
+/**
+ * struct csi_info - MIPI CSI2 information
+ *
+ * @clocks: array of MIPI CSI2 clock names
+ * @clock_rates: array of MIPI CSI2 clock rate
+ * @clock_num: actual clock number
+ */
+struct csi_info {
+	char *clocks[MIPI_CSI2_CLOCK_NUM_MAX];
+	u32 clock_rates[MIPI_CSI2_CLOCK_NUM_MAX];
+	u32 clock_num;
+};
+
+/**
+ * struct csi_device - MIPI CSI2 platform device
+ *
+ * @dev: pointer to the struct device
+ * @aphy: MIPI CSI2 aphy register address
+ * @dphy: MIPI CSI2 dphy register address
+ * @host: MIPI CSI2 host register address
+ * @clks: array of MIPI CSI2 clocks
+ * @sd: MIPI CSI2 sub-device
+ * @pads: MIPI CSI2 sub-device pads
+ * @notifier: notifier to register on the v4l2-async API
+ * @src_sd: source sub-device
+ * @bus: MIPI CSI2 bus information
+ * @src_sd_pad: source sub-device pad
+ * @lock: protect MIPI CSI2 device
+ * @info: version-specific MIPI CSI2 information
+ */
+struct csi_device {
+	struct device *dev;
+	void __iomem *aphy;
+	void __iomem *dphy;
+	void __iomem *host;
+	struct clk_bulk_data clks[MIPI_CSI2_CLOCK_NUM_MAX];
+
+	struct v4l2_subdev sd;
+	struct media_pad pads[MIPI_CSI2_PAD_MAX];
+	struct v4l2_async_notifier notifier;
+	struct v4l2_subdev *src_sd;
+	struct v4l2_mbus_config_mipi_csi2 bus;
+
+	u16 src_sd_pad;
+	struct mutex lock; /* Protect csi device */
+	const struct csi_info *info;
+};
+
+static const u32 c3_mipi_csi_formats[] = {
+	MEDIA_BUS_FMT_SBGGR10_1X10,
+	MEDIA_BUS_FMT_SGBRG10_1X10,
+	MEDIA_BUS_FMT_SGRBG10_1X10,
+	MEDIA_BUS_FMT_SRGGB10_1X10,
+	MEDIA_BUS_FMT_SBGGR12_1X12,
+	MEDIA_BUS_FMT_SGBRG12_1X12,
+	MEDIA_BUS_FMT_SGRBG12_1X12,
+	MEDIA_BUS_FMT_SRGGB12_1X12,
+};
+
+/* Hardware configuration */
+
+static void c3_mipi_csi_write(struct csi_device *csi, u32 reg, u32 val)
+{
+	void __iomem *addr;
+
+	switch (CSI2_SUBMD(reg)) {
+	case SUBMD_APHY:
+		addr = csi->aphy + CSI2_REG_ADDR(reg);
+		break;
+	case SUBMD_DPHY:
+		addr = csi->dphy + CSI2_REG_ADDR(reg);
+		break;
+	case SUBMD_HOST:
+		addr = csi->host + CSI2_REG_ADDR(reg);
+		break;
+	default:
+		dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
+		return;
+	}
+
+	writel(val, addr);
+}
+
+static void c3_mipi_csi_update_bits(struct csi_device *csi, u32 reg,
+				    u32 mask, u32 val)
+{
+	void __iomem *addr;
+	u32 orig, tmp;
+
+	switch (CSI2_SUBMD(reg)) {
+	case SUBMD_APHY:
+		addr = csi->aphy + CSI2_REG_ADDR(reg);
+		break;
+	case SUBMD_DPHY:
+		addr = csi->dphy + CSI2_REG_ADDR(reg);
+		break;
+	case SUBMD_HOST:
+		addr = csi->host + CSI2_REG_ADDR(reg);
+		break;
+	default:
+		dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
+		return;
+	}
+
+	orig = readl(addr);
+	tmp = orig & ~mask;
+	tmp |= val & mask;
+
+	if (tmp != orig)
+		writel(tmp, addr);
+}
+
+static void c3_mipi_csi_cfg_aphy(struct csi_device *csi, u32 lanes)
+{
+	c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL1, MIPI_APHY_NORMAL_CNTL1);
+
+	if (lanes == 4)
+		c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_4LANES_CNTL2);
+	else
+		c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_NORMAL_CNTL2);
+
+	if (lanes == 2)
+		c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL3, MIPI_APHY_2LANES_CNTL3);
+}
+
+static void c3_mipi_csi_2lanes_setting(struct csi_device *csi)
+{
+	/* Disable lane 2 and lane 3 */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0xf);
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
+				0xf << MIPI_DPHY_LANE2_SELECT_SHIFT);
+	/* Select analog data lane 1 */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
+				0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
+	/* Select analog data lane 0 */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
+
+	/* Disable lane 2 and lane 3 control signal */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0xf);
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
+				0xf << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
+	/* Select lane 1 signal */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
+				0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
+	/* Select lane 0 signal */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
+	/* Select input 0 as clock */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
+				MIPI_DPHY_CLK_SELECT);
+}
+
+static void c3_mipi_csi_4lanes_setting(struct csi_device *csi)
+{
+	/* Select analog data lane 3 */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0x3);
+	/* Select analog data lane 2 */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
+				0x2 << MIPI_DPHY_LANE2_SELECT_SHIFT);
+	/* Select analog data lane 1 */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
+				0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
+	/* Select analog data lane 0 */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
+
+	/* Select lane 3 signal */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0x3);
+	/* Select lane 2 signal */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
+				0x2 << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
+	/* Select lane 1 signal */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
+				0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
+	/* Select lane 0 signal */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
+	/* Select input 0 as clock */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
+				MIPI_DPHY_CLK_SELECT);
+}
+
+static void c3_mipi_csi_cfg_dphy(struct csi_device *csi, u32 lanes, s64 rate)
+{
+	u32 val;
+	u32 settle;
+
+	/* Calculate the high speed settle */
+	val = DIV_ROUND_UP(1000000000, rate);
+	settle = (16 * val + 230) / 10;
+
+	c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL, MIPI_DPHY_CLK_CONTINUE_MODE);
+	c3_mipi_csi_write(csi, MIPI_PHY_TCLK_MISS, MIPI_DPHY_CLK_MISS);
+	c3_mipi_csi_write(csi, MIPI_PHY_TCLK_SETTLE, MIPI_DPHY_CLK_SETTLE);
+	c3_mipi_csi_write(csi, MIPI_PHY_THS_EXIT, MIPI_DPHY_HS_EXIT);
+	c3_mipi_csi_write(csi, MIPI_PHY_THS_SKIP, MIPI_DPHY_HS_SKIP);
+	c3_mipi_csi_write(csi, MIPI_PHY_THS_SETTLE, settle);
+	c3_mipi_csi_write(csi, MIPI_PHY_TINIT, MIPI_DPHY_INIT_CYCLES);
+	c3_mipi_csi_write(csi, MIPI_PHY_TMBIAS, MIPI_DPHY_MBIAS_CYCLES);
+	c3_mipi_csi_write(csi, MIPI_PHY_TULPS_C, MIPI_DPHY_ULPS_CHECK_CYCLES);
+	c3_mipi_csi_write(csi, MIPI_PHY_TULPS_S, MIPI_DPHY_ULPS_START_CYCLES);
+	c3_mipi_csi_write(csi, MIPI_PHY_TLP_EN_W, MIPI_DPHY_ULPS_STOP_CYCLES);
+	c3_mipi_csi_write(csi, MIPI_PHY_TLPOK, MIPI_DPHY_POWER_UP_CYCLES);
+	c3_mipi_csi_write(csi, MIPI_PHY_TWD_INIT, MIPI_DPHY_INIT_WATCH_DOG);
+	c3_mipi_csi_write(csi, MIPI_PHY_TWD_HS, MIPI_DPHY_HS_WATCH_DOG);
+	c3_mipi_csi_write(csi, MIPI_PHY_DATA_LANE_CTRL, MIPI_DPHY_LANE_CTRL_DISABLE);
+
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_INSERT_ERRESC,
+				MIPI_DPHY_INSERT_ERRESC);
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_HS_SYNC_CHECK,
+				MIPI_DPHY_HS_SYNC_CHECK);
+	/*
+	 * Set 5 pipe lines to the same high speed.
+	 * Each bit for one pipe line.
+	 */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_FIVE_HS_PIPE,
+				0x1f << MIPI_DPHY_FIVE_HS_PIPE_SHIFT);
+
+	/* Output data with pipe line data. */
+	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_DATA_PIPE_SELECT,
+				0x3 << MIPI_DPHY_DATA_PIPE_SELECT_SHIFT);
+	if (lanes == 2)
+		c3_mipi_csi_2lanes_setting(csi);
+	else
+		c3_mipi_csi_4lanes_setting(csi);
+
+	/* Enable digital data and clock lanes */
+	c3_mipi_csi_write(csi, MIPI_PHY_CTRL, MIPI_DPHY_LANES_ENABLE);
+}
+
+static void c3_mipi_csi_cfg_host(struct csi_device *csi, u32 lanes)
+{
+	/* Reset CSI-2 controller output */
+	c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_DEFAULT);
+	c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_RST_VALUE);
+
+	/* Set data lane number */
+	c3_mipi_csi_write(csi, CSI2_HOST_N_LANES, lanes - 1);
+
+	/* Enable error mask */
+	c3_mipi_csi_write(csi, CSI2_HOST_MASK1, CSI2_HOST_ERROR_MASK1);
+}
+
+static int c3_mipi_csi_start_stream(struct csi_device *csi)
+{
+	s64 link_freq;
+	s64 lane_rate;
+
+	link_freq = v4l2_get_link_freq(csi->src_sd->ctrl_handler, 0, 0);
+	if (link_freq < 0) {
+		dev_err(csi->dev, "Unable to obtain link frequency: %lld\n", link_freq);
+		return link_freq;
+	}
+
+	lane_rate = link_freq * 2;
+	if (lane_rate > 1500000000)
+		return -EINVAL;
+
+	c3_mipi_csi_cfg_aphy(csi, csi->bus.num_data_lanes);
+	c3_mipi_csi_cfg_dphy(csi, csi->bus.num_data_lanes, lane_rate);
+	c3_mipi_csi_cfg_host(csi, csi->bus.num_data_lanes);
+
+	return 0;
+}
+
+static int c3_mipi_csi_enable_streams(struct v4l2_subdev *sd,
+				      struct v4l2_subdev_state *state,
+				      u32 pad, u64 streams_mask)
+{
+	struct csi_device *csi = v4l2_get_subdevdata(sd);
+	u64 sink_streams;
+	int ret;
+
+	guard(mutex)(&csi->lock);
+
+	pm_runtime_resume_and_get(csi->dev);
+
+	c3_mipi_csi_start_stream(csi);
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+						       MIPI_CSI2_PAD_SINK,
+						       &streams_mask);
+	ret = v4l2_subdev_enable_streams(csi->src_sd,
+					 csi->src_sd_pad,
+					 sink_streams);
+	if (ret) {
+		pm_runtime_put(csi->dev);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int c3_mipi_csi_disable_streams(struct v4l2_subdev *sd,
+				       struct v4l2_subdev_state *state,
+				       u32 pad, u64 streams_mask)
+{
+	struct csi_device *csi = v4l2_get_subdevdata(sd);
+	u64 sink_streams;
+	int ret;
+
+	guard(mutex)(&csi->lock);
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+						       MIPI_CSI2_PAD_SINK,
+						       &streams_mask);
+	ret = v4l2_subdev_disable_streams(csi->src_sd,
+					  csi->src_sd_pad,
+					  sink_streams);
+	if (ret)
+		dev_err(csi->dev, "Failed to disable %s\n", csi->src_sd->name);
+
+	pm_runtime_put(csi->dev);
+
+	return ret;
+}
+
+static int c3_mipi_csi_cfg_routing(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state,
+				   struct v4l2_subdev_krouting *routing)
+{
+	static const struct v4l2_mbus_framefmt format = {
+		.width = MIPI_CSI2_DEFAULT_WIDTH,
+		.height = MIPI_CSI2_DEFAULT_HEIGHT,
+		.code = MIPI_CSI2_DEFAULT_FMT,
+		.field = V4L2_FIELD_NONE,
+		.colorspace = V4L2_COLORSPACE_RAW,
+		.ycbcr_enc = V4L2_YCBCR_ENC_601,
+		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
+		.xfer_func = V4L2_XFER_FUNC_NONE,
+	};
+	int ret;
+
+	ret = v4l2_subdev_routing_validate(sd, routing,
+					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int c3_mipi_csi_init_routing(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes;
+	struct v4l2_subdev_krouting routing;
+
+	routes.sink_pad = MIPI_CSI2_PAD_SINK;
+	routes.sink_stream = 0;
+	routes.source_pad = MIPI_CSI2_PAD_SRC;
+	routes.source_stream = 0;
+	routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
+
+	routing.num_routes = 1;
+	routing.routes = &routes;
+
+	return c3_mipi_csi_cfg_routing(sd, state, &routing);
+}
+
+static int c3_mipi_csi_set_routing(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state,
+				   enum v4l2_subdev_format_whence which,
+				   struct v4l2_subdev_krouting *routing)
+{
+	bool is_streaming = v4l2_subdev_is_streaming(sd);
+
+	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
+		return -EBUSY;
+
+	return c3_mipi_csi_cfg_routing(sd, state, routing);
+}
+
+static int c3_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
+				      struct v4l2_subdev_state *state,
+				      struct v4l2_subdev_mbus_code_enum *code)
+{
+	switch (code->pad) {
+	case MIPI_CSI2_PAD_SINK:
+		if (code->index >= ARRAY_SIZE(c3_mipi_csi_formats))
+			return -EINVAL;
+
+		code->code = c3_mipi_csi_formats[code->index];
+		break;
+	case MIPI_CSI2_PAD_SRC:
+		struct v4l2_mbus_framefmt *fmt;
+
+		if (code->index > 0)
+			return -EINVAL;
+
+		fmt = v4l2_subdev_state_get_format(state, code->pad);
+		code->code = fmt->code;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int c3_mipi_csi_set_fmt(struct v4l2_subdev *sd,
+			       struct v4l2_subdev_state *state,
+			       struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *fmt;
+	unsigned int i;
+
+	if (format->pad != MIPI_CSI2_PAD_SINK)
+		return v4l2_subdev_get_fmt(sd, state, format);
+
+	fmt = v4l2_subdev_state_get_format(state, format->pad);
+
+	for (i = 0; i < ARRAY_SIZE(c3_mipi_csi_formats); i++)
+		if (format->format.code == c3_mipi_csi_formats[i])
+			break;
+
+	if (i == ARRAY_SIZE(c3_mipi_csi_formats))
+		fmt->code = c3_mipi_csi_formats[0];
+	else
+		fmt->code = c3_mipi_csi_formats[i];
+
+	fmt->width = clamp_t(u32, format->format.width,
+			     MIPI_CSI2_MIN_WIDTH, MIPI_CSI2_MAX_WIDTH);
+	fmt->height = clamp_t(u32, format->format.height,
+			      MIPI_CSI2_MIN_HEIGHT, MIPI_CSI2_MAX_HEIGHT);
+
+	format->format = *fmt;
+
+	/* Synchronize the format to source pad */
+	fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
+	*fmt = format->format;
+
+	return 0;
+}
+
+static int c3_mipi_csi_init_state(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *sink_fmt;
+	struct v4l2_mbus_framefmt *src_fmt;
+
+	sink_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SINK);
+	src_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
+
+	sink_fmt->width = MIPI_CSI2_DEFAULT_WIDTH;
+	sink_fmt->height = MIPI_CSI2_DEFAULT_HEIGHT;
+	sink_fmt->field = V4L2_FIELD_NONE;
+	sink_fmt->code = MIPI_CSI2_DEFAULT_FMT;
+	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
+	sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
+	sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
+	sink_fmt->quantization =
+		V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
+					      sink_fmt->ycbcr_enc);
+	*src_fmt = *sink_fmt;
+
+	return c3_mipi_csi_init_routing(sd, state);
+}
+
+static const struct v4l2_subdev_pad_ops c3_mipi_csi_pad_ops = {
+	.enum_mbus_code = c3_mipi_csi_enum_mbus_code,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = c3_mipi_csi_set_fmt,
+	.set_routing = c3_mipi_csi_set_routing,
+	.enable_streams = c3_mipi_csi_enable_streams,
+	.disable_streams = c3_mipi_csi_disable_streams,
+};
+
+static const struct v4l2_subdev_ops c3_mipi_csi_subdev_ops = {
+	.pad = &c3_mipi_csi_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops c3_mipi_csi_internal_ops = {
+	.init_state = c3_mipi_csi_init_state,
+};
+
+/* Media entity operations */
+static const struct media_entity_operations c3_mipi_csi_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+/* PM runtime */
+
+static int __maybe_unused c3_mipi_csi_runtime_suspend(struct device *dev)
+{
+	struct csi_device *csi = dev_get_drvdata(dev);
+
+	clk_bulk_disable_unprepare(csi->info->clock_num, csi->clks);
+
+	return 0;
+}
+
+static int __maybe_unused c3_mipi_csi_runtime_resume(struct device *dev)
+{
+	struct csi_device *csi = dev_get_drvdata(dev);
+
+	return clk_bulk_prepare_enable(csi->info->clock_num, csi->clks);
+}
+
+static const struct dev_pm_ops c3_mipi_csi_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				pm_runtime_force_resume)
+	SET_RUNTIME_PM_OPS(c3_mipi_csi_runtime_suspend,
+			   c3_mipi_csi_runtime_resume, NULL)
+};
+
+/* Probe/remove & platform driver */
+
+static int c3_mipi_csi_subdev_init(struct csi_device *csi)
+{
+	struct v4l2_subdev *sd = &csi->sd;
+	int ret;
+
+	v4l2_subdev_init(sd, &c3_mipi_csi_subdev_ops);
+	sd->owner = THIS_MODULE;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	sd->internal_ops = &c3_mipi_csi_internal_ops;
+	snprintf(sd->name, sizeof(sd->name), "%s", MIPI_CSI2_SUBDEV_NAME);
+
+	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+	sd->entity.ops = &c3_mipi_csi_entity_ops;
+
+	sd->dev = csi->dev;
+	v4l2_set_subdevdata(sd, csi);
+
+	csi->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+	csi->pads[MIPI_CSI2_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_pads_init(&sd->entity, MIPI_CSI2_PAD_MAX, csi->pads);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_init_finalize(sd);
+	if (ret) {
+		media_entity_cleanup(&sd->entity);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void c3_mipi_csi_subdev_deinit(struct csi_device *csi)
+{
+	v4l2_subdev_cleanup(&csi->sd);
+	media_entity_cleanup(&csi->sd.entity);
+}
+
+/* Subdev notifier register */
+static int c3_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier,
+				    struct v4l2_subdev *sd,
+				    struct v4l2_async_connection *asc)
+{
+	struct csi_device *csi = v4l2_get_subdevdata(notifier->sd);
+	struct media_pad *sink = &csi->sd.entity.pads[MIPI_CSI2_PAD_SINK];
+	int ret;
+
+	ret = media_entity_get_fwnode_pad(&sd->entity,
+					  sd->fwnode, MEDIA_PAD_FL_SOURCE);
+	if (ret < 0) {
+		dev_err(csi->dev, "Failed to find pad for %s\n", sd->name);
+		return ret;
+	}
+
+	csi->src_sd = sd;
+	csi->src_sd_pad = ret;
+
+	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
+					       MEDIA_LNK_FL_IMMUTABLE);
+}
+
+static const struct v4l2_async_notifier_operations c3_mipi_csi_notify_ops = {
+	.bound = c3_mipi_csi_notify_bound,
+};
+
+static int c3_mipi_csi_async_register(struct csi_device *csi)
+{
+	struct v4l2_fwnode_endpoint vep = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY,
+	};
+	struct v4l2_async_connection *asc;
+	struct fwnode_handle *ep;
+	int ret;
+
+	v4l2_async_subdev_nf_init(&csi->notifier, &csi->sd);
+
+	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0,
+					     FWNODE_GRAPH_ENDPOINT_NEXT);
+	if (!ep)
+		return -ENOTCONN;
+
+	ret = v4l2_fwnode_endpoint_parse(ep, &vep);
+	if (ret)
+		goto err_put_handle;
+
+	csi->bus = vep.bus.mipi_csi2;
+	if (csi->bus.num_data_lanes != 2 && csi->bus.num_data_lanes != 4)
+		goto err_put_handle;
+
+	asc = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep,
+					      struct v4l2_async_connection);
+	if (IS_ERR(asc)) {
+		ret = PTR_ERR(asc);
+		goto err_put_handle;
+	}
+
+	csi->notifier.ops = &c3_mipi_csi_notify_ops;
+	ret = v4l2_async_nf_register(&csi->notifier);
+	if (ret)
+		goto err_cleanup_nf;
+
+	ret = v4l2_async_register_subdev(&csi->sd);
+	if (ret)
+		goto err_unregister_nf;
+
+	fwnode_handle_put(ep);
+
+	return 0;
+
+err_unregister_nf:
+	v4l2_async_nf_unregister(&csi->notifier);
+err_cleanup_nf:
+	v4l2_async_nf_cleanup(&csi->notifier);
+err_put_handle:
+	fwnode_handle_put(ep);
+	return ret;
+}
+
+static void c3_mipi_csi_async_unregister(struct csi_device *csi)
+{
+	v4l2_async_unregister_subdev(&csi->sd);
+	v4l2_async_nf_unregister(&csi->notifier);
+	v4l2_async_nf_cleanup(&csi->notifier);
+}
+
+static int c3_mipi_csi_ioremap_resource(struct csi_device *csi)
+{
+	struct device *dev = csi->dev;
+	struct platform_device *pdev = to_platform_device(dev);
+
+	csi->aphy = devm_platform_ioremap_resource_byname(pdev, "aphy");
+	if (IS_ERR(csi->aphy))
+		return PTR_ERR(csi->aphy);
+
+	csi->dphy = devm_platform_ioremap_resource_byname(pdev, "dphy");
+	if (IS_ERR(csi->dphy))
+		return PTR_ERR(csi->dphy);
+
+	csi->host = devm_platform_ioremap_resource_byname(pdev, "host");
+	if (IS_ERR(csi->host))
+		return PTR_ERR(csi->host);
+
+	return 0;
+}
+
+static int c3_mipi_csi_configure_clocks(struct csi_device *csi)
+{
+	const struct csi_info *info = csi->info;
+	int ret;
+	u32 i;
+
+	for (i = 0; i < info->clock_num; i++)
+		csi->clks[i].id = info->clocks[i];
+
+	ret = devm_clk_bulk_get(csi->dev, info->clock_num, csi->clks);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < info->clock_num; i++) {
+		if (!info->clock_rates[i])
+			continue;
+		ret = clk_set_rate(csi->clks[i].clk, info->clock_rates[i]);
+		if (ret) {
+			dev_err(csi->dev, "Failed to set %s rate %u\n", info->clocks[i],
+				info->clock_rates[i]);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int c3_mipi_csi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct csi_device *csi;
+	int ret;
+
+	csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
+	if (!csi)
+		return -ENOMEM;
+
+	csi->info = of_device_get_match_data(dev);
+	csi->dev = dev;
+
+	ret = c3_mipi_csi_ioremap_resource(csi);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
+
+	ret = c3_mipi_csi_configure_clocks(csi);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to configure clocks\n");
+
+	platform_set_drvdata(pdev, csi);
+
+	mutex_init(&csi->lock);
+	pm_runtime_enable(dev);
+
+	ret = c3_mipi_csi_subdev_init(csi);
+	if (ret)
+		goto err_disable_runtime_pm;
+
+	ret = c3_mipi_csi_async_register(csi);
+	if (ret)
+		goto err_deinit_subdev;
+
+	return 0;
+
+err_deinit_subdev:
+	c3_mipi_csi_subdev_deinit(csi);
+err_disable_runtime_pm:
+	pm_runtime_disable(dev);
+	mutex_destroy(&csi->lock);
+	return ret;
+};
+
+static void c3_mipi_csi_remove(struct platform_device *pdev)
+{
+	struct csi_device *csi = platform_get_drvdata(pdev);
+
+	c3_mipi_csi_async_unregister(csi);
+	c3_mipi_csi_subdev_deinit(csi);
+
+	pm_runtime_disable(&pdev->dev);
+	mutex_destroy(&csi->lock);
+};
+
+static const struct csi_info c3_mipi_csi_info = {
+	.clocks = {"vapb", "phy0"},
+	.clock_rates = {0, 200000000},
+	.clock_num = 2
+};
+
+static const struct of_device_id c3_mipi_csi_of_match[] = {
+	{ .compatible = "amlogic,c3-mipi-csi2",
+	  .data = &c3_mipi_csi_info,
+	},
+	{ },
+};
+MODULE_DEVICE_TABLE(of, c3_mipi_csi_of_match);
+
+static struct platform_driver c3_mipi_csi_driver = {
+	.probe = c3_mipi_csi_probe,
+	.remove = c3_mipi_csi_remove,
+	.driver = {
+		.name = "c3-mipi-csi2",
+		.of_match_table = c3_mipi_csi_of_match,
+		.pm = &c3_mipi_csi_pm_ops,
+	},
+};
+
+module_platform_driver(c3_mipi_csi_driver);
+
+MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
+MODULE_DESCRIPTION("Amlogic C3 MIPI CSI-2 receiver");
+MODULE_LICENSE("GPL");

-- 
2.46.1



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

* [PATCH v3 3/9] dt-bindings: media: Add amlogic,c3-mipi-adapter.yaml
  2024-09-18  6:07 [PATCH v3 0/9] Amlogic C3 ISP support Keke Li via B4 Relay
  2024-09-18  6:07 ` [PATCH v3 1/9] dt-bindings: media: Add amlogic,c3-mipi-csi2.yaml Keke Li via B4 Relay
  2024-09-18  6:07 ` [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver Keke Li via B4 Relay
@ 2024-09-18  6:07 ` Keke Li via B4 Relay
  2024-09-18  6:07 ` [PATCH v3 4/9] media: platform: Add c3 mipi adapter driver Keke Li via B4 Relay
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 37+ messages in thread
From: Keke Li via B4 Relay @ 2024-09-18  6:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-media, devicetree, linux-kernel, kieran.bingham,
	laurent.pinchart, dan.scally, Keke Li, Krzysztof Kozlowski

From: Keke Li <keke.li@amlogic.com>

c3-mipi-adapter is used to organize mipi data and
send raw data to ISP module.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Keke Li <keke.li@amlogic.com>
---
 .../bindings/media/amlogic,c3-mipi-adapter.yaml    | 108 +++++++++++++++++++++
 1 file changed, 108 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml b/Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml
new file mode 100644
index 000000000000..d900ff9df61b
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml
@@ -0,0 +1,108 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/amlogic,c3-mipi-adapter.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic C3 MIPI adapter receiver
+
+maintainers:
+  - Keke Li <keke.li@amlogic.com>
+
+description:
+  MIPI adapter is used to convert the MIPI CSI-2 data
+  into an ISP supported data format.
+
+properties:
+  compatible:
+    enum:
+      - amlogic,c3-mipi-adapter
+
+  reg:
+    maxItems: 3
+
+  reg-names:
+    items:
+      - const: top
+      - const: fd
+      - const: rd
+
+  power-domains:
+    maxItems: 1
+
+  clocks:
+    maxItems: 2
+
+  clock-names:
+    items:
+      - const: vapb
+      - const: isp0
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: input port node.
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: output port node.
+
+    required:
+      - port@0
+      - port@1
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - power-domains
+  - clocks
+  - clock-names
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/amlogic,c3-peripherals-clkc.h>
+    #include <dt-bindings/power/amlogic,c3-pwrc.h>
+
+    soc {
+        #address-cells = <2>;
+        #size-cells = <2>;
+
+        adap: adap@ff010000 {
+            compatible = "amlogic,c3-mipi-adapter";
+            reg = <0x0 0xff010000 0x0 0x100>,
+                  <0x0 0xff01b000 0x0 0x100>,
+                  <0x0 0xff01d000 0x0 0x200>;
+            reg-names = "top", "fd", "rd";
+            power-domains = <&pwrc PWRC_C3_ISP_TOP_ID>;
+            clocks = <&clkc_periphs CLKID_VAPB>,
+                     <&clkc_periphs CLKID_ISP0>;
+            clock-names = "vapb", "isp0";
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+                    c3_adap_in: endpoint {
+                        remote-endpoint = <&c3_mipi_csi_out>;
+                    };
+                };
+
+                port@1 {
+                    reg = <1>;
+                    c3_adap_out: endpoint {
+                        remote-endpoint = <&c3_isp_in>;
+                    };
+                };
+            };
+        };
+    };
+...

-- 
2.46.1



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

* [PATCH v3 4/9] media: platform: Add c3 mipi adapter driver
  2024-09-18  6:07 [PATCH v3 0/9] Amlogic C3 ISP support Keke Li via B4 Relay
                   ` (2 preceding siblings ...)
  2024-09-18  6:07 ` [PATCH v3 3/9] dt-bindings: media: Add amlogic,c3-mipi-adapter.yaml Keke Li via B4 Relay
@ 2024-09-18  6:07 ` Keke Li via B4 Relay
  2024-11-05 11:24   ` Jacopo Mondi
  2024-09-18  6:07 ` [PATCH v3 5/9] dt-bindings: media: Add amlogic,c3-isp.yaml Keke Li via B4 Relay
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 37+ messages in thread
From: Keke Li via B4 Relay @ 2024-09-18  6:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-media, devicetree, linux-kernel, kieran.bingham,
	laurent.pinchart, dan.scally, Keke Li

From: Keke Li <keke.li@amlogic.com>

This driver mainly responsible for organizing
MIPI data and sending raw data to ISP pipeline.

Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Signed-off-by: Keke Li <keke.li@amlogic.com>
---
 MAINTAINERS                                        |   7 +
 drivers/media/platform/amlogic/Kconfig             |   1 +
 drivers/media/platform/amlogic/Makefile            |   1 +
 .../media/platform/amlogic/c3-mipi-adapter/Kconfig |  16 +
 .../platform/amlogic/c3-mipi-adapter/Makefile      |   3 +
 .../amlogic/c3-mipi-adapter/c3-mipi-adap.c         | 913 +++++++++++++++++++++
 6 files changed, 941 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 9e75874a6e69..31168c05f304 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1209,6 +1209,13 @@ F:	Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
 F:	drivers/perf/amlogic/
 F:	include/soc/amlogic/
 
+AMLOGIC MIPI ADAPTER DRIVER
+M:	Keke Li <keke.li@amlogic.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml
+F:	drivers/media/platform/amlogic/c3-mipi-adapter/
+
 AMLOGIC MIPI CSI2 DRIVER
 M:	Keke Li <keke.li@amlogic.com>
 L:	linux-media@vger.kernel.org
diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
index b7c2de14848b..df09717b28d0 100644
--- a/drivers/media/platform/amlogic/Kconfig
+++ b/drivers/media/platform/amlogic/Kconfig
@@ -2,5 +2,6 @@
 
 comment "Amlogic media platform drivers"
 
+source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
 source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
 source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
index 4f571ce5d13e..b370154b090c 100644
--- a/drivers/media/platform/amlogic/Makefile
+++ b/drivers/media/platform/amlogic/Makefile
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
+obj-y += c3-mipi-adapter/
 obj-y += c3-mipi-csi2/
 obj-y += meson-ge2d/
diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig b/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig
new file mode 100644
index 000000000000..bf19059b3543
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VIDEO_C3_MIPI_ADAPTER
+	tristate "Amlogic C3 MIPI adapter"
+	depends on ARCH_MESON || COMPILE_TEST
+	depends on VIDEO_DEV
+	depends on OF
+	select MEDIA_CONTROLLER
+	select V4L2_FWNODE
+	select VIDEO_V4L2_SUBDEV_API
+	help
+	  Video4Linux2 driver for Amlogic C3 MIPI adapter.
+	  C3 MIPI adapter mainly responsible for organizing
+	  MIPI data and sending raw data to ISP pipeline.
+
+	  To compile this driver as a module choose m here.
diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile b/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile
new file mode 100644
index 000000000000..216fc310c5b4
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_VIDEO_C3_MIPI_ADAPTER) += c3-mipi-adap.o
diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c b/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c
new file mode 100644
index 000000000000..b64eb417c2e2
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c
@@ -0,0 +1,913 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-async.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-subdev.h>
+
+/* C3 adapter submodule definition */
+enum {
+	SUBMD_TOP,
+	SUBMD_FD,
+	SUBMD_RD,
+};
+
+#define ADAP_SUBMD_MASK             GENMASK(17, 16)
+#define ADAP_SUBMD_SHIFT            16
+#define ADAP_SUBMD(x)               (((x) & (ADAP_SUBMD_MASK)) >> (ADAP_SUBMD_SHIFT))
+#define ADAP_REG_ADDR_MASK          GENMASK(15, 0)
+#define ADAP_REG_ADDR(x)            ((x) & (ADAP_REG_ADDR_MASK))
+#define ADAP_REG_T(x)               ((SUBMD_TOP << ADAP_SUBMD_SHIFT) | (x))
+#define ADAP_REG_F(x)               ((SUBMD_FD << ADAP_SUBMD_SHIFT) | (x))
+#define ADAP_REG_R(x)               ((SUBMD_RD << ADAP_SUBMD_SHIFT) | (x))
+
+#define MIPI_ADAP_CLOCK_NUM_MAX     3
+#define MIPI_ADAP_SUBDEV_NAME       "mipi-adapter"
+
+/* C3 MIPI adapter TOP register */
+#define MIPI_ADAPT_DE_CTRL0         ADAP_REG_T(0x40)
+#define ADAP_DE_READ_BYPASS         BIT(3)
+#define ADAP_DE_WRITE_BYPASS        BIT(7)
+
+/* C3 MIPI adapter FRONTEND register */
+#define CSI2_CLK_RESET              ADAP_REG_F(0x00)
+#define ADAP_FD_APPLY_RESET         BIT(0)
+#define ADAP_FD_ENABLE              BIT(1)
+
+#define CSI2_GEN_CTRL0              ADAP_REG_F(0x04)
+#define ADAP_FD_VIRTUAL_CHN0_EN     BIT(0)
+#define ADAP_FD_VIRTUAL_CHN1_EN     BIT(1)
+#define ADAP_FD_VIRTUAL_CHN2_EN     BIT(2)
+#define ADAP_FD_VIRTUAL_CHN3_EN     BIT(3)
+#define ADAP_FD_ENABLE_PACKETS      GENMASK(20, 16)
+#define ADAP_FD_ENABLE_RAW          BIT(16)
+
+#define CSI2_X_START_END_ISP        ADAP_REG_F(0x0c)
+#define ADAP_FD_X_END_MASK          GENMASK(31, 16)
+#define ADAP_FD_X_END_SHIFT         16
+#define ADAP_FD_X_END(x)            ((x) - 1)
+
+#define CSI2_Y_START_END_ISP        ADAP_REG_F(0x10)
+#define ADAP_FD_Y_END_MASK          GENMASK(31, 16)
+#define ADAP_FD_Y_END_SHIFT         16
+#define ADAP_FD_Y_END(x)            ((x) - 1)
+
+#define CSI2_VC_MODE                ADAP_REG_F(0x1c)
+#define ADAP_FD_VS_SEL_VC_MASK      GENMASK(19, 16)
+#define ADAP_FD_VS_DIRECT_PATH      BIT(16)
+#define ADAP_FD_HS_SEL_VC_MASK      GENMASK(23, 20)
+#define ADAP_FD_HS_DIRECT_PATH      BIT(20)
+
+/* C3 MIPI adapter READER register */
+#define MIPI_ADAPT_DDR_RD0_CNTL0    ADAP_REG_R(0x00)
+#define ADAP_RD0_MODULE_ENABLE      BIT(0)
+#define ADAP_RD0_LINE_STRIDE_MASK   GENMASK(13, 4)
+#define ADAP_RD0_LINE_STRIDE_SHIFT  4
+#define ADAP_RD0_SAMPLE_SEL_MASK    GENMASK(27, 26)
+#define ADAP_RD0_DATA_IN_VSYNC      BIT(26)
+#define ADAP_RD0_BURST_TYPE_MASK    GENMASK(29, 28)
+#define ADAP_RD0_BURST_TYPE_SHIFT   28
+#define ADAP_RD0_BURST_TYPE_INRC8   3
+#define ADAP_RD0_FRAME_RD_START     BIT(31)
+
+#define MIPI_ADAPT_DDR_RD0_CNTL1    ADAP_REG_R(0x04)
+#define ADAP_RD0_LINE_SIZE_MASK     GENMASK(9, 0)
+#define ADAP_RD0_LINE_NUM_MASK      GENMASK(28, 16)
+#define ADAP_RD0_LINE_NUM_SHIFT     16
+
+#define MIPI_ADAPT_PIXEL0_CNTL0     ADAP_REG_R(0x80)
+#define ADAP_PIXEL0_WORK_MODE_MASK  GENMASK(17, 16)
+#define ADAP_PIXEL0_DIRECT_PATH     BIT(16)
+#define ADAP_PIXEL0_DATA_TYPE_MASK  GENMASK(25, 20)
+#define ADAP_PIXEL0_DATA_TYPE_SHIFT 20
+#define ADAP_PIXEL0_DATA_TYPE_10BITS 0x2b
+#define ADAP_PIXEL0_DATA_TYPE_12BITS 0x2c
+#define ADAP_PIXEL0_START_ENABLE    BIT(31)
+
+#define MIPI_ADAPT_PIXEL0_CNTL1     ADAP_REG_R(0x84)
+#define ADAP_PIXEL0_X_END_MASK      GENMASK(15, 0)
+#define ADAP_PIXEL0_X_END(x)        ((x) - 1)
+
+#define MIPI_ADAPT_PIXEL0_CNTL2     ADAP_REG_R(0x88)
+#define ADAP_PIXEL0_FIFO_SIZE_MASK  GENMASK(9, 0)
+#define ADAP_PIXEL0_PIXEL_NUM_MASK  GENMASK(27, 15)
+#define ADAP_PIXEL0_PIXEL_NUM_SHIFT 15
+
+#define MIPI_ADAPT_ALIG_CNTL0       ADAP_REG_R(0x100)
+#define ADAP_ALIG_V_TOTAL_NUM_MASK  GENMASK(15, 0)
+/* Need to add a default blank */
+#define ADAP_ALIG_V_TOTAL_NUM(x)    ((x) + 64)
+#define ADAP_ALIG_H_TOTAL_NUM_MASK  GENMASK(31, 16)
+#define ADAP_ALIG_H_TOTAL_NUM_SHIFT 16
+/* Need to add a default blank */
+#define ADAP_ALIG_H_TOTAL_NUM(x)    ((x) + 64)
+
+#define MIPI_ADAPT_ALIG_CNTL1       ADAP_REG_R(0x104)
+#define ADAP_ALIG_HPE_NUM_MASK      GENMASK(31, 16)
+#define ADAP_ALIG_HPE_NUM_SHIFT     16
+
+#define MIPI_ADAPT_ALIG_CNTL2       ADAP_REG_R(0x108)
+#define ADAP_ALIG_VPE_NUM_MASK      GENMASK(31, 16)
+#define ADAP_ALIG_VPE_NUM_SHIFT     16
+
+#define MIPI_ADAPT_ALIG_CNTL3       ADAP_REG_R(0x10c)
+#define ADAP_ALIG_FRM_ST_PIXEL_MASK GENMASK(15, 0)
+
+#define MIPI_ADAPT_ALIG_CNTL6       ADAP_REG_R(0x118)
+#define ADAP_ALIG_LANE0_ENABLE      BIT(0)
+#define ADAP_ALIG_DATA_MODE0_MASK   BIT(4)
+#define ADAP_ALIG_DIRECT_MODE       BIT(4)
+#define ADAP_ALIG_VDATA0_ENABLE     BIT(12)
+#define ADAP_ALIG_VDATA1_ENABLE     BIT(13)
+#define ADAP_ALIG_VDATA2_ENABLE     BIT(14)
+#define ADAP_ALIG_VDATA3_ENABLE     BIT(15)
+
+#define MIPI_ADAPT_ALIG_CNTL8       ADAP_REG_R(0x120)
+#define ADAP_ALIG_FRAME_CONTINUE    BIT(5)
+#define ADAP_ALIG_EXC_MASK_DIS      BIT(12)
+#define ADAP_ALIG_START_ENABLE      BIT(31)
+
+#define MIPI_ADAP_MAX_WIDTH         2888
+#define MIPI_ADAP_MIN_WIDTH         160
+#define MIPI_ADAP_MAX_HEIGHT        2240
+#define MIPI_ADAP_MIN_HEIGHT        120
+#define MIPI_ADAP_DEFAULT_WIDTH     1920
+#define MIPI_ADAP_DEFAULT_HEIGHT    1080
+#define MIPI_ADAP_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
+
+/* C3 MIPI adapter pad list */
+enum {
+	MIPI_ADAP_PAD_SINK,
+	MIPI_ADAP_PAD_SRC,
+	MIPI_ADAP_PAD_MAX
+};
+
+/*
+ * struct adap_info - mipi adapter information
+ *
+ * @clocks: array of mipi adapter clock names
+ * @clock_rates: array of mipi adapter clock rate
+ * @clock_num: actual clock number
+ */
+struct adap_info {
+	char *clocks[MIPI_ADAP_CLOCK_NUM_MAX];
+	u32 clock_rates[MIPI_ADAP_CLOCK_NUM_MAX];
+	u32 clock_num;
+};
+
+/**
+ * struct adap_device - mipi adapter platform device
+ *
+ * @dev: pointer to the struct device
+ * @top: mipi adapter top register address
+ * @fd: mipi adapter frontend register address
+ * @rd: mipi adapter reader register address
+ * @clks: array of MIPI adapter clocks
+ * @sd: mipi adapter sub-device
+ * @pads: mipi adapter sub-device pads
+ * @notifier: notifier to register on the v4l2-async API
+ * @format: save sub-device format
+ * @src_sd: source sub-device
+ * @src_sd_pad: source sub-device pad
+ * @lock: protect mipi adapter device
+ * @info: version-specific MIPI adapter information
+ */
+struct adap_device {
+	struct device *dev;
+	void __iomem *top;
+	void __iomem *fd;
+	void __iomem *rd;
+	struct clk_bulk_data clks[MIPI_ADAP_CLOCK_NUM_MAX];
+
+	struct v4l2_subdev sd;
+	struct media_pad pads[MIPI_ADAP_PAD_MAX];
+	struct v4l2_async_notifier notifier;
+	struct v4l2_subdev_format format;
+	struct v4l2_subdev *src_sd;
+
+	u16 src_sd_pad;
+	struct mutex lock; /* Protect adapter device */
+	const struct adap_info *info;
+};
+
+/* Format helpers */
+
+struct adap_pix_format {
+	u32 code;
+	u8 width;
+};
+
+static const struct adap_pix_format c3_mipi_adap_formats[] = {
+	{ MEDIA_BUS_FMT_SBGGR10_1X10, 10 },
+	{ MEDIA_BUS_FMT_SGBRG10_1X10, 10 },
+	{ MEDIA_BUS_FMT_SGRBG10_1X10, 10 },
+	{ MEDIA_BUS_FMT_SRGGB10_1X10, 10 },
+	{ MEDIA_BUS_FMT_SBGGR12_1X12, 12 },
+	{ MEDIA_BUS_FMT_SGBRG12_1X12, 12 },
+	{ MEDIA_BUS_FMT_SGRBG12_1X12, 12 },
+	{ MEDIA_BUS_FMT_SRGGB12_1X12, 12 },
+};
+
+static const struct adap_pix_format
+*c3_mipi_adap_find_format(u32 code)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(c3_mipi_adap_formats); i++)
+		if (code == c3_mipi_adap_formats[i].code)
+			return &c3_mipi_adap_formats[i];
+
+	return NULL;
+}
+
+/* Hardware configuration */
+
+static void c3_mipi_adap_update_bits(struct adap_device *adap, u32 reg,
+				     u32 mask, u32 val)
+{
+	void __iomem *addr;
+	u32 orig, tmp;
+
+	switch (ADAP_SUBMD(reg)) {
+	case SUBMD_TOP:
+		addr = adap->top + ADAP_REG_ADDR(reg);
+		break;
+	case SUBMD_FD:
+		addr = adap->fd + ADAP_REG_ADDR(reg);
+		break;
+	case SUBMD_RD:
+		addr = adap->rd + ADAP_REG_ADDR(reg);
+		break;
+	default:
+		dev_err(adap->dev, "Invalid sub-module: %lu\n", ADAP_SUBMD(reg));
+		return;
+	}
+
+	orig = readl(addr);
+	tmp = orig & ~mask;
+	tmp |= val & mask;
+
+	if (tmp != orig)
+		writel(tmp, addr);
+}
+
+/* Configure adapter top sub module */
+static void c3_mipi_adap_cfg_top(struct adap_device *adap)
+{
+	/* Bypass decompress */
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0,
+				 ADAP_DE_READ_BYPASS, ADAP_DE_READ_BYPASS);
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0,
+				 ADAP_DE_WRITE_BYPASS, ADAP_DE_WRITE_BYPASS);
+}
+
+/* Configure adapter frontend sub module */
+static void c3_mipi_adap_cfg_frontend(struct adap_device *adap,
+				      struct v4l2_mbus_framefmt *fmt)
+{
+	/* The default value of BIT_0 is 1, so need release reset firstly. */
+	c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET, ADAP_FD_APPLY_RESET, 0);
+
+	c3_mipi_adap_update_bits(adap, CSI2_X_START_END_ISP, ADAP_FD_X_END_MASK,
+				 ADAP_FD_X_END(fmt->width) << ADAP_FD_X_END_SHIFT);
+	c3_mipi_adap_update_bits(adap, CSI2_Y_START_END_ISP, ADAP_FD_Y_END_MASK,
+				 ADAP_FD_Y_END(fmt->height) << ADAP_FD_Y_END_SHIFT);
+
+	/* Select VS and HS signal to direct path */
+	c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, ADAP_FD_VS_SEL_VC_MASK,
+				 ADAP_FD_VS_DIRECT_PATH);
+	c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, ADAP_FD_HS_SEL_VC_MASK,
+				 ADAP_FD_HS_DIRECT_PATH);
+
+	/* Enable to receive RAW image */
+	c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_ENABLE_PACKETS,
+				 ADAP_FD_ENABLE_RAW);
+
+	/* Enable virtual channel 0~3 */
+	c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN0_EN,
+				 ADAP_FD_VIRTUAL_CHN0_EN);
+	c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN1_EN,
+				 ADAP_FD_VIRTUAL_CHN1_EN);
+	c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN2_EN,
+				 ADAP_FD_VIRTUAL_CHN2_EN);
+	c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN3_EN,
+				 ADAP_FD_VIRTUAL_CHN3_EN);
+}
+
+/* Configure adapter reader sub module */
+static void c3_mipi_adap_cfg_reader(struct adap_device *adap,
+				    struct v4l2_mbus_framefmt *fmt)
+{
+	const struct adap_pix_format *pix_format;
+	u32 line_size;
+	u8 data_type;
+
+	/* Data size for a line, unit: 128 bits */
+	pix_format = c3_mipi_adap_find_format(fmt->code);
+	line_size = fmt->width * pix_format->width;
+	line_size = DIV_ROUND_UP(line_size, 128);
+
+	if (pix_format->width == 10) {
+		data_type = ADAP_PIXEL0_DATA_TYPE_10BITS;
+	} else if (pix_format->width == 12) {
+		data_type = ADAP_PIXEL0_DATA_TYPE_12BITS;
+	} else {
+		dev_err(adap->dev, "Invalid raw format width: %u\n", pix_format->width);
+		return;
+	}
+
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL1,
+				 ADAP_RD0_LINE_SIZE_MASK, line_size);
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL1, ADAP_RD0_LINE_NUM_MASK,
+				 fmt->height << ADAP_RD0_LINE_NUM_SHIFT);
+
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_MODULE_ENABLE,
+				 ADAP_RD0_MODULE_ENABLE);
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_LINE_STRIDE_MASK,
+				 line_size << ADAP_RD0_LINE_STRIDE_SHIFT);
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_SAMPLE_SEL_MASK,
+				 ADAP_RD0_DATA_IN_VSYNC);
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_BURST_TYPE_MASK,
+				 ADAP_RD0_BURST_TYPE_INRC8 << ADAP_RD0_BURST_TYPE_SHIFT);
+
+	/* Set data type and work mode */
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0,
+				 ADAP_PIXEL0_WORK_MODE_MASK, ADAP_PIXEL0_DIRECT_PATH);
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0, ADAP_PIXEL0_DATA_TYPE_MASK,
+				 data_type << ADAP_PIXEL0_DATA_TYPE_SHIFT);
+
+	/* Set pixel end number */
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL1, ADAP_PIXEL0_X_END_MASK,
+				 ADAP_PIXEL0_X_END(fmt->width));
+
+	/* Set line pixel number and reader fifo size */
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL2,
+				 ADAP_PIXEL0_FIFO_SIZE_MASK, line_size);
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL2, ADAP_PIXEL0_PIXEL_NUM_MASK,
+				 fmt->width << ADAP_PIXEL0_PIXEL_NUM_SHIFT);
+
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, ADAP_ALIG_V_TOTAL_NUM_MASK,
+				 ADAP_ALIG_V_TOTAL_NUM(fmt->width));
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, ADAP_ALIG_H_TOTAL_NUM_MASK,
+				 ADAP_ALIG_H_TOTAL_NUM(fmt->height)
+				 << ADAP_ALIG_H_TOTAL_NUM_SHIFT);
+
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL1, ADAP_ALIG_HPE_NUM_MASK,
+				 fmt->width << ADAP_ALIG_HPE_NUM_SHIFT);
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL2, ADAP_ALIG_VPE_NUM_MASK,
+				 fmt->height << ADAP_ALIG_VPE_NUM_SHIFT);
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL3,
+				 ADAP_ALIG_FRM_ST_PIXEL_MASK, fmt->width);
+
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
+				 ADAP_ALIG_LANE0_ENABLE, ADAP_ALIG_LANE0_ENABLE);
+
+	/* Select direct mode */
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
+				 ADAP_ALIG_DATA_MODE0_MASK, ADAP_ALIG_DIRECT_MODE);
+
+	/* Enable vdata 0~3 */
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
+				 ADAP_ALIG_VDATA0_ENABLE, ADAP_ALIG_VDATA0_ENABLE);
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
+				 ADAP_ALIG_VDATA1_ENABLE, ADAP_ALIG_VDATA1_ENABLE);
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
+				 ADAP_ALIG_VDATA2_ENABLE, ADAP_ALIG_VDATA2_ENABLE);
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
+				 ADAP_ALIG_VDATA3_ENABLE, ADAP_ALIG_VDATA2_ENABLE);
+
+	/* continue mode and disable hold counter */
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
+				 ADAP_ALIG_FRAME_CONTINUE, ADAP_ALIG_FRAME_CONTINUE);
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
+				 ADAP_ALIG_EXC_MASK_DIS, ADAP_ALIG_EXC_MASK_DIS);
+}
+
+static void c3_mipi_adap_start_stream(struct adap_device *adap)
+{
+	/* Enable to start and will auto clear to 0 */
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
+				 ADAP_ALIG_START_ENABLE, ADAP_ALIG_START_ENABLE);
+
+	/* Enable to start and will auto clear to 0 */
+	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0,
+				 ADAP_PIXEL0_START_ENABLE, ADAP_PIXEL0_START_ENABLE);
+
+	/* Enable frontend */
+	c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET,
+				 ADAP_FD_ENABLE, ADAP_FD_ENABLE);
+}
+
+static void c3_mipi_adap_cfg_format(struct adap_device *adap)
+{
+	struct v4l2_subdev_format *format = &adap->format;
+
+	c3_mipi_adap_cfg_top(adap);
+	c3_mipi_adap_cfg_frontend(adap, &format->format);
+	c3_mipi_adap_cfg_reader(adap, &format->format);
+}
+
+/* V4L2 subdev operations */
+
+static int c3_mipi_adap_enable_streams(struct v4l2_subdev *sd,
+				       struct v4l2_subdev_state *state,
+				       u32 pad, u64 streams_mask)
+{
+	struct adap_device *adap = v4l2_get_subdevdata(sd);
+	u64 sink_streams;
+	int ret;
+
+	guard(mutex)(&adap->lock);
+
+	pm_runtime_resume_and_get(adap->dev);
+
+	c3_mipi_adap_cfg_format(adap);
+	c3_mipi_adap_start_stream(adap);
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+						       MIPI_ADAP_PAD_SINK,
+						       &streams_mask);
+	ret = v4l2_subdev_enable_streams(adap->src_sd,
+					 adap->src_sd_pad,
+					 sink_streams);
+	if (ret) {
+		pm_runtime_put(adap->dev);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int c3_mipi_adap_disable_streams(struct v4l2_subdev *sd,
+					struct v4l2_subdev_state *state,
+					u32 pad, u64 streams_mask)
+{
+	struct adap_device *adap = v4l2_get_subdevdata(sd);
+	u64 sink_streams;
+	int ret;
+
+	guard(mutex)(&adap->lock);
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+						       MIPI_ADAP_PAD_SINK,
+						       &streams_mask);
+	ret = v4l2_subdev_disable_streams(adap->src_sd,
+					  adap->src_sd_pad,
+					  sink_streams);
+	if (ret)
+		dev_err(adap->dev, "Failed to disable %s\n", adap->src_sd->name);
+
+	pm_runtime_put(adap->dev);
+
+	return ret;
+}
+
+static int c3_mipi_adap_cfg_routing(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *state,
+				    struct v4l2_subdev_krouting *routing)
+{
+	static const struct v4l2_mbus_framefmt format = {
+		.width = MIPI_ADAP_DEFAULT_WIDTH,
+		.height = MIPI_ADAP_DEFAULT_HEIGHT,
+		.code = MIPI_ADAP_DEFAULT_FMT,
+		.field = V4L2_FIELD_NONE,
+		.colorspace = V4L2_COLORSPACE_RAW,
+		.ycbcr_enc = V4L2_YCBCR_ENC_601,
+		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
+		.xfer_func = V4L2_XFER_FUNC_NONE,
+	};
+	int ret;
+
+	ret = v4l2_subdev_routing_validate(sd, routing,
+					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int c3_mipi_adap_init_routing(struct v4l2_subdev *sd,
+				     struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes;
+	struct v4l2_subdev_krouting routing;
+
+	routes.sink_pad = MIPI_ADAP_PAD_SINK;
+	routes.sink_stream = 0;
+	routes.source_pad = MIPI_ADAP_PAD_SRC;
+	routes.source_stream = 0;
+	routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
+
+	routing.num_routes = 1;
+	routing.routes = &routes;
+
+	return c3_mipi_adap_cfg_routing(sd, state, &routing);
+}
+
+static int c3_mipi_adap_set_routing(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *state,
+				    enum v4l2_subdev_format_whence which,
+				    struct v4l2_subdev_krouting *routing)
+{
+	bool is_streaming = v4l2_subdev_is_streaming(sd);
+
+	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
+		return -EBUSY;
+
+	return c3_mipi_adap_cfg_routing(sd, state, routing);
+}
+
+static int c3_mipi_adap_enum_mbus_code(struct v4l2_subdev *sd,
+				       struct v4l2_subdev_state *state,
+				       struct v4l2_subdev_mbus_code_enum *code)
+{
+	switch (code->pad) {
+	case MIPI_ADAP_PAD_SINK:
+		if (code->index >= ARRAY_SIZE(c3_mipi_adap_formats))
+			return -EINVAL;
+
+		code->code = c3_mipi_adap_formats[code->index].code;
+		break;
+	case MIPI_ADAP_PAD_SRC:
+		struct v4l2_mbus_framefmt *fmt;
+
+		if (code->index > 0)
+			return -EINVAL;
+
+		fmt = v4l2_subdev_state_get_format(state, code->pad);
+		code->code = fmt->code;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int c3_mipi_adap_set_fmt(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *state,
+				struct v4l2_subdev_format *format)
+{
+	struct adap_device *adap = v4l2_get_subdevdata(sd);
+	struct v4l2_mbus_framefmt *fmt;
+	const struct adap_pix_format *pix_format;
+
+	if (format->pad != MIPI_ADAP_PAD_SINK)
+		return v4l2_subdev_get_fmt(sd, state, format);
+
+	pix_format = c3_mipi_adap_find_format(format->format.code);
+	if (!pix_format)
+		pix_format = &c3_mipi_adap_formats[0];
+
+	fmt = v4l2_subdev_state_get_format(state, format->pad);
+	fmt->code = pix_format->code;
+	fmt->width = clamp_t(u32, format->format.width,
+			     MIPI_ADAP_MIN_WIDTH, MIPI_ADAP_MAX_WIDTH);
+	fmt->height = clamp_t(u32, format->format.height,
+			      MIPI_ADAP_MIN_HEIGHT, MIPI_ADAP_MAX_HEIGHT);
+
+	format->format = *fmt;
+
+	/* Synchronize the format to source pad */
+	fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SRC);
+	*fmt = format->format;
+
+	adap->format = *format;
+
+	return 0;
+}
+
+static int c3_mipi_adap_init_state(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *sink_fmt;
+	struct v4l2_mbus_framefmt *src_fmt;
+
+	sink_fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SINK);
+	src_fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SRC);
+
+	sink_fmt->width = MIPI_ADAP_DEFAULT_WIDTH;
+	sink_fmt->height = MIPI_ADAP_DEFAULT_HEIGHT;
+	sink_fmt->field = V4L2_FIELD_NONE;
+	sink_fmt->code = MIPI_ADAP_DEFAULT_FMT;
+	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
+	sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
+	sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
+	sink_fmt->quantization =
+		V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
+					      sink_fmt->ycbcr_enc);
+	*src_fmt = *sink_fmt;
+
+	return c3_mipi_adap_init_routing(sd, state);
+}
+
+static const struct v4l2_subdev_pad_ops c3_mipi_adap_pad_ops = {
+	.enum_mbus_code = c3_mipi_adap_enum_mbus_code,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = c3_mipi_adap_set_fmt,
+	.set_routing = c3_mipi_adap_set_routing,
+	.enable_streams = c3_mipi_adap_enable_streams,
+	.disable_streams = c3_mipi_adap_disable_streams,
+};
+
+static const struct v4l2_subdev_ops c3_mipi_adap_subdev_ops = {
+	.pad = &c3_mipi_adap_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops c3_mipi_adap_internal_ops = {
+	.init_state = c3_mipi_adap_init_state,
+};
+
+/* Media entity operations */
+static const struct media_entity_operations c3_mipi_adap_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+/* PM runtime */
+
+static int __maybe_unused c3_mipi_adap_runtime_suspend(struct device *dev)
+{
+	struct adap_device *adap = dev_get_drvdata(dev);
+
+	clk_bulk_disable_unprepare(adap->info->clock_num, adap->clks);
+
+	return 0;
+}
+
+static int __maybe_unused c3_mipi_adap_runtime_resume(struct device *dev)
+{
+	struct adap_device *adap = dev_get_drvdata(dev);
+
+	return clk_bulk_prepare_enable(adap->info->clock_num, adap->clks);
+}
+
+static const struct dev_pm_ops c3_mipi_adap_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				pm_runtime_force_resume)
+	SET_RUNTIME_PM_OPS(c3_mipi_adap_runtime_suspend,
+			   c3_mipi_adap_runtime_resume, NULL)
+};
+
+/* Probe/remove & platform driver */
+
+static int c3_mipi_adap_subdev_init(struct adap_device *adap)
+{
+	struct v4l2_subdev *sd = &adap->sd;
+	int ret;
+
+	v4l2_subdev_init(sd, &c3_mipi_adap_subdev_ops);
+	sd->owner = THIS_MODULE;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	sd->internal_ops = &c3_mipi_adap_internal_ops;
+	snprintf(sd->name, sizeof(sd->name), "%s", MIPI_ADAP_SUBDEV_NAME);
+
+	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+	sd->entity.ops = &c3_mipi_adap_entity_ops;
+
+	sd->dev = adap->dev;
+	v4l2_set_subdevdata(sd, adap);
+
+	adap->pads[MIPI_ADAP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+	adap->pads[MIPI_ADAP_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_pads_init(&sd->entity, MIPI_ADAP_PAD_MAX, adap->pads);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_init_finalize(sd);
+	if (ret) {
+		media_entity_cleanup(&sd->entity);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void c3_mipi_adap_subdev_deinit(struct adap_device *adap)
+{
+	v4l2_subdev_cleanup(&adap->sd);
+	media_entity_cleanup(&adap->sd.entity);
+}
+
+/* Subdev notifier register */
+static int c3_mipi_adap_notify_bound(struct v4l2_async_notifier *notifier,
+				     struct v4l2_subdev *sd,
+				     struct v4l2_async_connection *asc)
+{
+	struct adap_device *adap = v4l2_get_subdevdata(notifier->sd);
+	struct media_pad *sink = &adap->sd.entity.pads[MIPI_ADAP_PAD_SINK];
+	int ret;
+
+	ret = media_entity_get_fwnode_pad(&sd->entity,
+					  sd->fwnode, MEDIA_PAD_FL_SOURCE);
+	if (ret < 0) {
+		dev_err(adap->dev, "Failed to find pad for %s\n", sd->name);
+		return ret;
+	}
+
+	adap->src_sd = sd;
+	adap->src_sd_pad = ret;
+
+	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
+					       MEDIA_LNK_FL_IMMUTABLE);
+}
+
+static const struct v4l2_async_notifier_operations c3_mipi_adap_notify_ops = {
+	.bound = c3_mipi_adap_notify_bound,
+};
+
+static int c3_mipi_adap_async_register(struct adap_device *adap)
+{
+	struct v4l2_async_connection *asc;
+	struct fwnode_handle *ep;
+	int ret;
+
+	v4l2_async_subdev_nf_init(&adap->notifier, &adap->sd);
+
+	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(adap->dev), 0, 0,
+					     FWNODE_GRAPH_ENDPOINT_NEXT);
+	if (!ep)
+		return -ENOTCONN;
+
+	asc = v4l2_async_nf_add_fwnode_remote(&adap->notifier, ep,
+					      struct v4l2_async_connection);
+	if (IS_ERR(asc)) {
+		ret = PTR_ERR(asc);
+		goto err_put_handle;
+	}
+
+	adap->notifier.ops = &c3_mipi_adap_notify_ops;
+	ret = v4l2_async_nf_register(&adap->notifier);
+	if (ret)
+		goto err_cleanup_nf;
+
+	ret = v4l2_async_register_subdev(&adap->sd);
+	if (ret)
+		goto err_unregister_nf;
+
+	fwnode_handle_put(ep);
+
+	return 0;
+
+err_unregister_nf:
+	v4l2_async_nf_unregister(&adap->notifier);
+err_cleanup_nf:
+	v4l2_async_nf_cleanup(&adap->notifier);
+err_put_handle:
+	fwnode_handle_put(ep);
+	return ret;
+}
+
+static void c3_mipi_adap_async_unregister(struct adap_device *adap)
+{
+	v4l2_async_unregister_subdev(&adap->sd);
+	v4l2_async_nf_unregister(&adap->notifier);
+	v4l2_async_nf_cleanup(&adap->notifier);
+}
+
+static int c3_mipi_adap_ioremap_resource(struct adap_device *adap)
+{
+	struct device *dev = adap->dev;
+	struct platform_device *pdev = to_platform_device(dev);
+
+	adap->top = devm_platform_ioremap_resource_byname(pdev, "top");
+	if (IS_ERR(adap->top))
+		return PTR_ERR(adap->top);
+
+	adap->fd = devm_platform_ioremap_resource_byname(pdev, "fd");
+	if (IS_ERR(adap->fd))
+		return PTR_ERR(adap->fd);
+
+	adap->rd = devm_platform_ioremap_resource_byname(pdev, "rd");
+	if (IS_ERR(adap->rd))
+		return PTR_ERR(adap->rd);
+
+	return 0;
+}
+
+static int c3_mipi_adap_configure_clocks(struct adap_device *adap)
+{
+	const struct adap_info *info = adap->info;
+	int ret;
+	u32 i;
+
+	for (i = 0; i < info->clock_num; i++)
+		adap->clks[i].id = info->clocks[i];
+
+	ret = devm_clk_bulk_get(adap->dev, info->clock_num, adap->clks);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < info->clock_num; i++) {
+		if (!info->clock_rates[i])
+			continue;
+		ret = clk_set_rate(adap->clks[i].clk, info->clock_rates[i]);
+		if (ret) {
+			dev_err(adap->dev, "Failed to set %s rate %u\n", info->clocks[i],
+				info->clock_rates[i]);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int c3_mipi_adap_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct adap_device *adap;
+	int ret;
+
+	adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL);
+	if (!adap)
+		return -ENOMEM;
+
+	adap->info = of_device_get_match_data(dev);
+	adap->dev = dev;
+
+	ret = c3_mipi_adap_ioremap_resource(adap);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
+
+	ret = c3_mipi_adap_configure_clocks(adap);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to configure clocks\n");
+
+	platform_set_drvdata(pdev, adap);
+
+	mutex_init(&adap->lock);
+	pm_runtime_enable(dev);
+
+	ret = c3_mipi_adap_subdev_init(adap);
+	if (ret < 0)
+		goto err_disable_runtime_pm;
+
+	ret = c3_mipi_adap_async_register(adap);
+	if (ret < 0)
+		goto err_deinit_subdev;
+
+	return 0;
+
+err_deinit_subdev:
+	c3_mipi_adap_subdev_deinit(adap);
+err_disable_runtime_pm:
+	pm_runtime_disable(dev);
+	mutex_destroy(&adap->lock);
+	return ret;
+};
+
+static void c3_mipi_adap_remove(struct platform_device *pdev)
+{
+	struct adap_device *adap = platform_get_drvdata(pdev);
+
+	c3_mipi_adap_async_unregister(adap);
+	c3_mipi_adap_subdev_deinit(adap);
+
+	pm_runtime_disable(&pdev->dev);
+	mutex_destroy(&adap->lock);
+};
+
+static const struct adap_info c3_mipi_adap_info = {
+	.clocks = {"vapb", "isp0"},
+	.clock_rates = {0, 400000000},
+	.clock_num = 2
+};
+
+static const struct of_device_id c3_mipi_adap_of_match[] = {
+	{ .compatible = "amlogic,c3-mipi-adapter",
+	  .data = &c3_mipi_adap_info },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, c3_mipi_adap_of_match);
+
+static struct platform_driver c3_mipi_adap_driver = {
+	.probe = c3_mipi_adap_probe,
+	.remove = c3_mipi_adap_remove,
+	.driver = {
+		.name = "c3-mipi-adapter",
+		.of_match_table = c3_mipi_adap_of_match,
+		.pm = &c3_mipi_adap_pm_ops,
+	},
+};
+
+module_platform_driver(c3_mipi_adap_driver);
+
+MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
+MODULE_DESCRIPTION("Amlogic C3 MIPI adapter");
+MODULE_LICENSE("GPL");

-- 
2.46.1



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

* [PATCH v3 5/9] dt-bindings: media: Add amlogic,c3-isp.yaml
  2024-09-18  6:07 [PATCH v3 0/9] Amlogic C3 ISP support Keke Li via B4 Relay
                   ` (3 preceding siblings ...)
  2024-09-18  6:07 ` [PATCH v3 4/9] media: platform: Add c3 mipi adapter driver Keke Li via B4 Relay
@ 2024-09-18  6:07 ` Keke Li via B4 Relay
  2024-09-21  0:32   ` Rob Herring (Arm)
  2024-09-18  6:07 ` [PATCH v3 6/9] media: Add C3ISP_PARAMS and C3ISP_STATS meta formats Keke Li via B4 Relay
                   ` (3 subsequent siblings)
  8 siblings, 1 reply; 37+ messages in thread
From: Keke Li via B4 Relay @ 2024-09-18  6:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-media, devicetree, linux-kernel, kieran.bingham,
	laurent.pinchart, dan.scally, Keke Li

From: Keke Li <keke.li@amlogic.com>

c3-isp is used to process raw image.

Signed-off-by: Keke Li <keke.li@amlogic.com>
---
 .../devicetree/bindings/media/amlogic,c3-isp.yaml  | 85 ++++++++++++++++++++++
 1 file changed, 85 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml b/Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml
new file mode 100644
index 000000000000..64ae1946b99b
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml
@@ -0,0 +1,85 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/amlogic,c3-isp.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic C3 Image Signal Processing Unit
+
+maintainers:
+  - Keke Li <keke.li@amlogic.com>
+
+description:
+  Amlogic ISP is the RAW image processing module
+  and supports three channels image output.
+
+properties:
+  compatible:
+    enum:
+      - amlogic,c3-isp
+
+  reg:
+    maxItems: 1
+
+  reg-names:
+    items:
+      - const: isp
+
+  power-domains:
+    maxItems: 1
+
+  clocks:
+    maxItems: 2
+
+  clock-names:
+    items:
+      - const: vapb
+      - const: isp0
+
+  interrupts:
+    maxItems: 1
+
+  port:
+    $ref: /schemas/graph.yaml#/properties/port
+    description: input port node.
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - power-domains
+  - clocks
+  - clock-names
+  - interrupts
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/clock/amlogic,c3-peripherals-clkc.h>
+    #include <dt-bindings/power/amlogic,c3-pwrc.h>
+
+    soc {
+        #address-cells = <2>;
+        #size-cells = <2>;
+
+        isp: isp@ff000000 {
+            compatible = "amlogic,c3-isp";
+            reg = <0x0 0xff000000 0x0 0xf000>;
+            reg-names = "isp";
+            power-domains = <&pwrc PWRC_C3_ISP_TOP_ID>;
+            clocks = <&clkc_periphs CLKID_VAPB>,
+                     <&clkc_periphs CLKID_ISP0>;
+            clock-names = "vapb", "isp0";
+            interrupts = <GIC_SPI 145 IRQ_TYPE_EDGE_RISING>;
+
+            port {
+                c3_isp_in: endpoint {
+                    remote-endpoint = <&c3_adap_out>;
+                };
+            };
+        };
+    };
+...

-- 
2.46.1



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

* [PATCH v3 6/9] media: Add C3ISP_PARAMS and C3ISP_STATS meta formats
  2024-09-18  6:07 [PATCH v3 0/9] Amlogic C3 ISP support Keke Li via B4 Relay
                   ` (4 preceding siblings ...)
  2024-09-18  6:07 ` [PATCH v3 5/9] dt-bindings: media: Add amlogic,c3-isp.yaml Keke Li via B4 Relay
@ 2024-09-18  6:07 ` Keke Li via B4 Relay
  2024-09-18  6:07 ` [PATCH v3 7/9] media: platform: Add c3 ISP driver Keke Li via B4 Relay
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 37+ messages in thread
From: Keke Li via B4 Relay @ 2024-09-18  6:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-media, devicetree, linux-kernel, kieran.bingham,
	laurent.pinchart, dan.scally, Keke Li

From: Keke Li <keke.li@amlogic.com>

C3ISP_PARAMS is the C3 ISP Parameters format.
C3ISP_STATS is the C3 ISP Statistics format.

Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Signed-off-by: Keke Li <keke.li@amlogic.com>
---
 drivers/media/v4l2-core/v4l2-ioctl.c | 2 ++
 include/uapi/linux/videodev2.h       | 4 ++++
 2 files changed, 6 insertions(+)

diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
index e14db67be97c..d0e346f301c1 100644
--- a/drivers/media/v4l2-core/v4l2-ioctl.c
+++ b/drivers/media/v4l2-core/v4l2-ioctl.c
@@ -1459,6 +1459,8 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
 	case V4L2_META_FMT_RK_ISP1_PARAMS:	descr = "Rockchip ISP1 3A Parameters"; break;
 	case V4L2_META_FMT_RK_ISP1_STAT_3A:	descr = "Rockchip ISP1 3A Statistics"; break;
 	case V4L2_META_FMT_RK_ISP1_EXT_PARAMS:	descr = "Rockchip ISP1 Ext 3A Params"; break;
+	case V4L2_META_FMT_C3ISP_PARAMS:	descr = "Amlogic C3 ISP Parameters"; break;
+	case V4L2_META_FMT_C3ISP_STATS:		descr = "Amlogic C3 ISP Statistics"; break;
 	case V4L2_PIX_FMT_NV12_8L128:	descr = "NV12 (8x128 Linear)"; break;
 	case V4L2_PIX_FMT_NV12M_8L128:	descr = "NV12M (8x128 Linear)"; break;
 	case V4L2_PIX_FMT_NV12_10BE_8L128:	descr = "10-bit NV12 (8x128 Linear, BE)"; break;
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index 27239cb64065..7d0a3e502a47 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -857,6 +857,10 @@ struct v4l2_pix_format {
 #define V4L2_META_FMT_RK_ISP1_STAT_3A	v4l2_fourcc('R', 'K', '1', 'S') /* Rockchip ISP1 3A Statistics */
 #define V4L2_META_FMT_RK_ISP1_EXT_PARAMS	v4l2_fourcc('R', 'K', '1', 'E') /* Rockchip ISP1 3a Extensible Parameters */
 
+/* Vendor specific - used for C3_ISP */
+#define V4L2_META_FMT_C3ISP_PARAMS	v4l2_fourcc('C', 'P', 'R', 'M') /* Amlogic C3 ISP Parameters */
+#define V4L2_META_FMT_C3ISP_STATS	v4l2_fourcc('C', 'S', 'T', 'S') /* Amlogic C3 ISP Statistics */
+
 /* Vendor specific - used for RaspberryPi PiSP */
 #define V4L2_META_FMT_RPI_BE_CFG	v4l2_fourcc('R', 'P', 'B', 'C') /* PiSP BE configuration */
 

-- 
2.46.1



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

* [PATCH v3 7/9] media: platform: Add c3 ISP driver
  2024-09-18  6:07 [PATCH v3 0/9] Amlogic C3 ISP support Keke Li via B4 Relay
                   ` (5 preceding siblings ...)
  2024-09-18  6:07 ` [PATCH v3 6/9] media: Add C3ISP_PARAMS and C3ISP_STATS meta formats Keke Li via B4 Relay
@ 2024-09-18  6:07 ` Keke Li via B4 Relay
  2024-11-07 16:03   ` Jacopo Mondi
  2024-11-08 10:47   ` Dan Scally
  2024-09-18  6:07 ` [PATCH v3 8/9] Documentation: media: add documentation file metafmt-c3-isp.rst Keke Li via B4 Relay
  2024-09-18  6:07 ` [PATCH v3 9/9] Documentation: media: add documentation file c3-isp.rst Keke Li via B4 Relay
  8 siblings, 2 replies; 37+ messages in thread
From: Keke Li via B4 Relay @ 2024-09-18  6:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-media, devicetree, linux-kernel, kieran.bingham,
	laurent.pinchart, dan.scally, Keke Li

From: Keke Li <keke.li@amlogic.com>

The C3 ISP supports multi-camera and muti-exposure
high dynamic range (HDR). It brings together some
advanced imaging technologies to provide good image quality.
This driver mainly responsible for driving ISP pipeline
to process raw image.

Signed-off-by: Keke Li <keke.li@amlogic.com>
---
 drivers/media/platform/amlogic/Kconfig             |   1 +
 drivers/media/platform/amlogic/Makefile            |   1 +
 drivers/media/platform/amlogic/c3-isp/Kconfig      |  17 +
 drivers/media/platform/amlogic/c3-isp/Makefile     |  10 +
 .../media/platform/amlogic/c3-isp/c3-isp-capture.c | 759 ++++++++++++++++++
 .../media/platform/amlogic/c3-isp/c3-isp-common.h  | 327 ++++++++
 .../media/platform/amlogic/c3-isp/c3-isp-core.c    | 675 ++++++++++++++++
 drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c | 486 ++++++++++++
 .../media/platform/amlogic/c3-isp/c3-isp-params.c  | 857 +++++++++++++++++++++
 .../media/platform/amlogic/c3-isp/c3-isp-regs.h    | 683 ++++++++++++++++
 .../media/platform/amlogic/c3-isp/c3-isp-resizer.c | 768 ++++++++++++++++++
 .../media/platform/amlogic/c3-isp/c3-isp-stats.c   | 488 ++++++++++++
 .../amlogic/c3-isp/include/uapi/c3-isp-config.h    | 537 +++++++++++++
 13 files changed, 5609 insertions(+)

diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
index df09717b28d0..ebda6b7edc2d 100644
--- a/drivers/media/platform/amlogic/Kconfig
+++ b/drivers/media/platform/amlogic/Kconfig
@@ -2,6 +2,7 @@
 
 comment "Amlogic media platform drivers"
 
+source "drivers/media/platform/amlogic/c3-isp/Kconfig"
 source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
 source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
 source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
index b370154b090c..d0d9363d4d8d 100644
--- a/drivers/media/platform/amlogic/Makefile
+++ b/drivers/media/platform/amlogic/Makefile
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
+obj-y += c3-isp/
 obj-y += c3-mipi-adapter/
 obj-y += c3-mipi-csi2/
 obj-y += meson-ge2d/
diff --git a/drivers/media/platform/amlogic/c3-isp/Kconfig b/drivers/media/platform/amlogic/c3-isp/Kconfig
new file mode 100644
index 000000000000..e317c1e81750
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-isp/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VIDEO_C3_ISP
+	tristate "Amlogic C3 Image Signal Processor (ISP) driver"
+	depends on ARCH_MESON || COMPILE_TEST
+	depends on VIDEO_DEV
+	depends on OF
+	select MEDIA_CONTROLLER
+	select V4L2_FWNODE
+	select VIDEO_V4L2_SUBDEV_API
+	select VIDEOBUF2_DMA_CONTIG
+	help
+	  Video4Linux2 driver for Amlogic C3 ISP pipeline.
+	  C3 ISP pipeline mainly for processing raw image
+	  and output result to memory.
+
+	  To compile this driver as a module choose m here.
diff --git a/drivers/media/platform/amlogic/c3-isp/Makefile b/drivers/media/platform/amlogic/c3-isp/Makefile
new file mode 100644
index 000000000000..b1b064170b57
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-isp/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+c3-isp-objs := c3-isp-dev.o \
+	       c3-isp-params.o \
+	       c3-isp-stats.o \
+	       c3-isp-capture.o \
+	       c3-isp-core.o \
+	       c3-isp-resizer.o
+
+obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
new file mode 100644
index 000000000000..ee9a7a17a203
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
@@ -0,0 +1,759 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/cleanup.h>
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "c3-isp-common.h"
+#include "c3-isp-regs.h"
+
+static const struct c3_isp_capture_format cap_formats[] = {
+	{
+		.mbus_code = MEDIA_BUS_FMT_Y8_1X8,
+		.fourcc = V4L2_PIX_FMT_GREY,
+		.depth = 8,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
+		.fourcc = V4L2_PIX_FMT_NV12,
+		.depth = 12,
+	}, {
+		.mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
+		.fourcc = V4L2_PIX_FMT_NV21,
+		.depth = 12,
+	}, {
+		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+		.fourcc = V4L2_PIX_FMT_NV16,
+		.depth = 16,
+	}, {
+		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+		.fourcc = V4L2_PIX_FMT_NV61,
+		.depth = 16,
+	},
+};
+
+/* Hardware configuration */
+
+/* Set the address of wrmifx3(write memory interface) */
+static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
+{
+	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
+	struct c3_isp_vb2_buffer *buff = cap->buff;
+	u32 offset;
+
+	c3_isp_write(cap->isp,
+		     C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
+		     WRMIFX3_CH0_BADDR(buff->paddr));
+
+	if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
+	    pix->pixelformat == V4L2_PIX_FMT_NV21 ||
+	    pix->pixelformat == V4L2_PIX_FMT_NV16 ||
+	    pix->pixelformat == V4L2_PIX_FMT_NV61) {
+		offset = pix->width * pix->height;
+		c3_isp_write(cap->isp,
+			     C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
+			     WRMIFX3_CH1_BADDR(buff->paddr + offset));
+	}
+}
+
+static void c3_isp_cap_output_size(struct c3_isp_capture *cap)
+{
+	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
+
+	c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
+			   DISP_OUT_VSIZE_MASK, pix->height);
+	c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
+			   DISP_OUT_HSIZE_MASK, pix->width << DISP_OUT_HSIZE_SHIFT);
+}
+
+static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
+{
+	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
+	u32 stride;
+
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
+
+	/* Grey has 1 plane*/
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   WRMIFX3_FMT_MTX_PLANE_MASK,
+			   WRMIFX3_FMT_MTX_PLANE_X1 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
+
+	/* Set Y only as output format */
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   WRMIFX3_FMT_MODE_OUT_MASK,
+			   WRMIFX3_FMT_MODE_OUT_Y_ONLY << WRMIFX3_FMT_MODE_OUT_SHIFT);
+
+	/* The unit of stride is 128 bits */
+	stride = DIV_ROUND_UP(fmt->width * 8, 128);
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
+			   WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
+
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
+			   WRMIFX3_CH0_PIX_BITS_MODE_MASK,
+			   WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
+}
+
+static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, u32 swap_uv)
+{
+	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
+	u32 stride;
+
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   WRMIFX3_FMT_MTX_UV_SWAP_MASK,
+			   swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
+
+	/* NV12 or NV21 has 2 planes*/
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   WRMIFX3_FMT_MTX_PLANE_MASK,
+			   WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
+
+	/* Set YUV420 as output format */
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   WRMIFX3_FMT_MODE_OUT_MASK,
+			   WRMIFX3_FMT_MODE_OUT_YUV420 << WRMIFX3_FMT_MODE_OUT_SHIFT);
+
+	/* The unit of stride is 128 bits */
+	stride = DIV_ROUND_UP(fmt->width * 8, 128);
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
+			   WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
+			   WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
+
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
+			   WRMIFX3_CH0_PIX_BITS_MODE_MASK,
+			   WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
+			   WRMIFX3_CH1_PIX_BITS_MODE_MASK,
+			   WRMIFX3_CH1_PIX_BITS_16BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
+}
+
+static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, u32 swap_uv)
+{
+	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
+	u32 stride;
+
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_16BIT);
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   WRMIFX3_FMT_MTX_UV_SWAP_MASK,
+			   swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
+
+	/* NV16 or NV61 has 2 planes*/
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   WRMIFX3_FMT_MTX_PLANE_MASK,
+			   WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
+
+	/* Set YUV422 as output format */
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
+			   WRMIFX3_FMT_MODE_OUT_MASK,
+			   WRMIFX3_FMT_MODE_OUT_YUV422 << WRMIFX3_FMT_MODE_OUT_SHIFT);
+
+	/* The unit of stride is 128 bits */
+	stride = DIV_ROUND_UP(fmt->width * 16, 128);
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
+			   WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
+			   WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
+
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
+			   WRMIFX3_CH0_PIX_BITS_MODE_MASK,
+			   WRMIFX3_CH0_PIX_BITS_16BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
+			   WRMIFX3_CH1_PIX_BITS_MODE_MASK,
+			   WRMIFX3_CH1_PIX_BITS_32BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
+}
+
+static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
+{
+	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
+
+	if (fmt->pixelformat == V4L2_PIX_FMT_GREY) {
+		c3_isp_cap_wrmifx3_grey(cap);
+	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV12) {
+		c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
+	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV21) {
+		c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
+	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV16) {
+		c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
+	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV61) {
+		c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
+	} else {
+		dev_err(cap->isp->dev, "Invalid pixel format: 0x%x\n", fmt->pixelformat);
+		return;
+	}
+
+	c3_isp_write(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id),
+		     WRMIFX3_FMT_HSIZE(fmt->width) | WRMIFX3_FMT_VSIZE(fmt->height));
+
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
+			   WRMIFX3_WIN_LUMA_HEND_MASK,
+			   WRMIFX3_WIN_LUMA_HEND(fmt->width) << WRMIFX3_WIN_LUMA_HEND_SHIFT);
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
+			   WRMIFX3_WIN_LUMA_VEND_MASK,
+			   WRMIFX3_WIN_LUMA_VEND(fmt->height) << WRMIFX3_WIN_LUMA_VEND_SHIFT);
+
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_HSIZE, cap->id),
+			   WRMIFX3_CROP_HEND_MASK,
+			   WRMIFX3_CROP_HEND(fmt->width) << WRMIFX3_CROP_HEND_SHIFT);
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_VSIZE, cap->id),
+			   WRMIFX3_CROP_VEND_MASK,
+			   WRMIFX3_CROP_VEND(fmt->height) << WRMIFX3_CROP_VEND_SHIFT);
+
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
+			   WRMIFX3_WIN_CHROM_HEND_MASK,
+			   WRMIFX3_WIN_CHROM_HEND(fmt->width) << WRMIFX3_WIN_CHROM_HEND_SHIFT);
+	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
+			   WRMIFX3_WIN_CHROM_VEND_MASK,
+			   WRMIFX3_WIN_CHROM_VEND(fmt->height) << WRMIFX3_WIN_CHROM_VEND_SHIFT);
+}
+
+static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)
+{
+	cap->buff = list_first_entry_or_null(&cap->pending,
+					     struct c3_isp_vb2_buffer, list);
+	if (cap->buff) {
+		c3_isp_cap_wrmifx3_buff(cap);
+		list_del(&cap->buff->list);
+	}
+}
+
+static void c3_isp_cap_start(struct c3_isp_capture *cap)
+{
+	c3_isp_cap_cfg_buff(cap);
+
+	c3_isp_cap_output_size(cap);
+	c3_isp_cap_wrmifx3_size(cap);
+
+	c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN,
+			   TOP_WRMIF_EN(cap->id), TOP_WRMIF_EN(cap->id));
+
+	cap->is_streaming = true;
+}
+
+static void c3_isp_cap_stop(struct c3_isp_capture *cap)
+{
+	cap->is_streaming = false;
+
+	c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, TOP_WRMIF_EN(cap->id), 0);
+}
+
+static int c3_isp_cap_done(struct c3_isp_capture *cap)
+{
+	struct c3_isp_vb2_buffer *buff = cap->buff;
+	unsigned long flags;
+
+	if (!cap->is_streaming)
+		return -EINVAL;
+
+	spin_lock_irqsave(&cap->buff_lock, flags);
+
+	if (buff) {
+		buff->vb.sequence = cap->isp->frm_sequence;
+		buff->vb.vb2_buf.timestamp = ktime_get();
+		buff->vb.field = V4L2_FIELD_NONE;
+		vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
+	}
+
+	c3_isp_cap_cfg_buff(cap);
+
+	spin_unlock_irqrestore(&cap->buff_lock, flags);
+
+	return 0;
+}
+
+/* V4L2 video operations */
+
+static const struct c3_isp_capture_format
+*c3_cap_find_fmt(u32 fourcc)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
+		if (cap_formats[i].fourcc == fourcc)
+			return &cap_formats[i];
+	}
+
+	return NULL;
+}
+
+static void c3_cap_try_fmt(struct c3_isp_capture *cap,
+			   struct v4l2_pix_format *pix)
+{
+	const struct c3_isp_capture_format *fmt;
+
+	fmt = c3_cap_find_fmt(pix->pixelformat);
+	if (!fmt)
+		fmt = &cap_formats[0];
+
+	pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
+	pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
+	pix->pixelformat = fmt->fourcc;
+	pix->field = V4L2_FIELD_NONE;
+	pix->colorspace = V4L2_COLORSPACE_SRGB;
+	pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
+	pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
+
+	/* ISP hardware requires 16 bytes alignment */
+	pix->bytesperline = ALIGN(pix->width, 16);
+	pix->sizeimage = pix->bytesperline * pix->height * fmt->depth / 8;
+}
+
+static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
+				      enum vb2_buffer_state state)
+{
+	unsigned long flags;
+	struct c3_isp_vb2_buffer *buff;
+
+	spin_lock_irqsave(&cap->buff_lock, flags);
+
+	if (cap->buff) {
+		vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
+		cap->buff = NULL;
+	}
+
+	while (!list_empty(&cap->pending)) {
+		buff = list_first_entry(&cap->pending,
+					struct c3_isp_vb2_buffer, list);
+		list_del(&buff->list);
+		vb2_buffer_done(&buff->vb.vb2_buf, state);
+	}
+
+	spin_unlock_irqrestore(&cap->buff_lock, flags);
+}
+
+static int c3_isp_cap_querycap(struct file *file, void *fh,
+			       struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
+	strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
+
+	return 0;
+}
+
+static int c3_isp_cap_enum_fmt(struct file *file, void *fh,
+			       struct v4l2_fmtdesc *f)
+{
+	const struct c3_isp_capture_format *fmt;
+	unsigned int index = 0;
+	unsigned int i;
+
+	if (!f->mbus_code) {
+		if (f->index >= ARRAY_SIZE(cap_formats))
+			return -EINVAL;
+
+		fmt = &cap_formats[f->index];
+		f->pixelformat = fmt->fourcc;
+		return 0;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
+		fmt = &cap_formats[i];
+		if (f->mbus_code != fmt->mbus_code)
+			continue;
+
+		if (index++ == f->index) {
+			f->pixelformat = cap_formats[i].fourcc;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int c3_isp_cap_g_fmt(struct file *file, void *fh,
+			    struct v4l2_format *f)
+{
+	struct c3_isp_capture *cap = video_drvdata(file);
+
+	f->fmt.pix = cap->vfmt.fmt.pix;
+
+	return 0;
+}
+
+static int c3_isp_cap_s_fmt(struct file *file, void *fh,
+			    struct v4l2_format *f)
+{
+	struct c3_isp_capture *cap = video_drvdata(file);
+
+	c3_cap_try_fmt(cap, &f->fmt.pix);
+	cap->vfmt = *f;
+
+	return 0;
+}
+
+static int c3_isp_cap_try_fmt(struct file *file, void *fh,
+			      struct v4l2_format *f)
+{
+	struct c3_isp_capture *cap = video_drvdata(file);
+
+	c3_cap_try_fmt(cap, &f->fmt.pix);
+
+	return 0;
+}
+
+static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
+				   struct v4l2_frmsizeenum *fsize)
+{
+	const struct c3_isp_capture_format *fmt;
+
+	if (fsize->index)
+		return -EINVAL;
+
+	fmt = c3_cap_find_fmt(fsize->pixel_format);
+	if (!fmt)
+		return -EINVAL;
+
+	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+	fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
+	fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
+	fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
+	fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
+	fsize->stepwise.step_width = 2;
+	fsize->stepwise.step_height = 2;
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
+	.vidioc_querycap		= c3_isp_cap_querycap,
+	.vidioc_enum_fmt_vid_cap	= c3_isp_cap_enum_fmt,
+	.vidioc_g_fmt_vid_cap		= c3_isp_cap_g_fmt,
+	.vidioc_s_fmt_vid_cap		= c3_isp_cap_s_fmt,
+	.vidioc_try_fmt_vid_cap		= c3_isp_cap_try_fmt,
+	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
+	.vidioc_querybuf		= vb2_ioctl_querybuf,
+	.vidioc_qbuf			= vb2_ioctl_qbuf,
+	.vidioc_expbuf			= vb2_ioctl_expbuf,
+	.vidioc_dqbuf			= vb2_ioctl_dqbuf,
+	.vidioc_prepare_buf		= vb2_ioctl_prepare_buf,
+	.vidioc_create_bufs		= vb2_ioctl_create_bufs,
+	.vidioc_streamon		= vb2_ioctl_streamon,
+	.vidioc_streamoff		= vb2_ioctl_streamoff,
+	.vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
+	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_file_operations isp_cap_v4l2_fops = {
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release,
+	.poll = vb2_fop_poll,
+	.unlocked_ioctl = video_ioctl2,
+	.mmap = vb2_fop_mmap,
+};
+
+static int c3_isp_cap_link_validate(struct media_link *link)
+{
+	struct video_device *vdev =
+		media_entity_to_video_device(link->sink->entity);
+	struct v4l2_subdev *sd =
+		media_entity_to_v4l2_subdev(link->source->entity);
+	struct c3_isp_capture *cap = video_get_drvdata(vdev);
+	struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
+	struct v4l2_subdev_format src_fmt = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		.pad = link->source->index,
+	};
+	const struct c3_isp_capture_format *cap_fmt =
+				c3_cap_find_fmt(pix_fmt->pixelformat);
+	int ret;
+
+	ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
+	if (ret)
+		return ret;
+
+	if (src_fmt.format.width != pix_fmt->width ||
+	    src_fmt.format.height != pix_fmt->height ||
+	    src_fmt.format.code != cap_fmt->mbus_code) {
+		dev_err(cap->isp->dev,
+			"link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n",
+			link->source->entity->name, link->source->index,
+			link->sink->entity->name, link->sink->index,
+			src_fmt.format.code, src_fmt.format.width,
+			src_fmt.format.height, cap_fmt->mbus_code,
+			pix_fmt->width, pix_fmt->height);
+
+		return -EPIPE;
+	};
+
+	return 0;
+}
+
+static const struct media_entity_operations isp_cap_entity_ops = {
+	.link_validate = c3_isp_cap_link_validate,
+};
+
+static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
+				  unsigned int *num_buffers,
+				  unsigned int *num_planes,
+				  unsigned int sizes[],
+				  struct device *alloc_devs[])
+{
+	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
+	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
+
+	if (*num_planes) {
+		if (*num_planes != 1)
+			return -EINVAL;
+
+		if (sizes[0] < pix->sizeimage)
+			return -EINVAL;
+	} else {
+		*num_planes = 1;
+		sizes[0] = pix->sizeimage;
+	}
+
+	return 0;
+}
+
+static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+	struct c3_isp_vb2_buffer *buf =
+			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
+	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned long flags;
+
+	spin_lock_irqsave(&cap->buff_lock, flags);
+
+	list_add_tail(&buf->list, &cap->pending);
+
+	spin_unlock_irqrestore(&cap->buff_lock, flags);
+}
+
+static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned int size = cap->vfmt.fmt.pix.sizeimage;
+
+	if (vb2_plane_size(vb, 0) < size) {
+		dev_err(cap->isp->dev,
+			"User buffer too small (%ld < %u)\n",
+			vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+
+	vb2_set_plane_payload(vb, 0, size);
+
+	return 0;
+}
+
+static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+	struct c3_isp_vb2_buffer *buf =
+		container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
+	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
+
+	buf->vaddr = vb2_plane_vaddr(vb, 0);
+	buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+	memset(buf->vaddr, 0, cap->vfmt.fmt.pix.sizeimage);
+
+	return 0;
+}
+
+static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
+				      unsigned int count)
+{
+	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
+	int ret;
+
+	guard(mutex)(&cap->isp->lock);
+
+	ret = pm_runtime_resume_and_get(cap->isp->dev);
+	if (ret)
+		return ret;
+
+	ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
+	if (ret) {
+		dev_err(cap->isp->dev,
+			"Failed to start cap%u pipeline: %d\n", cap->id, ret);
+		goto err_pm_put;
+	}
+
+	if (c3_isp_pipeline_ready(cap->isp)) {
+		ret = v4l2_subdev_enable_streams(&cap->isp->core.sd,
+						 C3_ISP_CORE_PAD_SOURCE_VIDEO,
+						 BIT(0));
+		if (ret)
+			goto err_pipeline_stop;
+	}
+
+	c3_isp_rsz_start(cap->rsz);
+	c3_isp_cap_start(cap);
+
+	return 0;
+
+err_pipeline_stop:
+	video_device_pipeline_stop(&cap->vdev);
+err_pm_put:
+	pm_runtime_put(cap->isp->dev);
+	c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
+	return ret;
+}
+
+static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
+{
+	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
+
+	guard(mutex)(&cap->isp->lock);
+
+	c3_isp_cap_stop(cap);
+	c3_isp_rsz_stop(cap->rsz);
+	c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
+
+	if (cap->isp->pipe.start_count == 1)
+		v4l2_subdev_disable_streams(&cap->isp->core.sd,
+					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
+					    BIT(0));
+
+	video_device_pipeline_stop(&cap->vdev);
+	pm_runtime_put(cap->isp->dev);
+}
+
+static const struct vb2_ops isp_video_vb2_ops = {
+	.queue_setup = c3_isp_vb2_queue_setup,
+	.buf_queue = c3_isp_vb2_buf_queue,
+	.buf_prepare = c3_isp_vb2_buf_prepare,
+	.buf_init = c3_isp_vb2_buf_init,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.start_streaming = c3_isp_vb2_start_streaming,
+	.stop_streaming = c3_isp_vb2_stop_streaming,
+};
+
+static int c3_isp_register_capture(struct c3_isp_capture *cap)
+{
+	struct video_device *vdev = &cap->vdev;
+	struct vb2_queue *vb2_q = &cap->vb2_q;
+	int ret;
+
+	snprintf(vdev->name, sizeof(vdev->name), "isp-cap%u", cap->id);
+	vdev->fops = &isp_cap_v4l2_fops;
+	vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
+	vdev->v4l2_dev = &cap->isp->v4l2_dev;
+	vdev->entity.ops = &isp_cap_entity_ops;
+	vdev->lock = &cap->lock;
+	vdev->minor = -1;
+	vdev->queue = vb2_q;
+	vdev->release = video_device_release_empty;
+	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+	vdev->vfl_dir = VFL_DIR_RX;
+	video_set_drvdata(vdev, cap);
+
+	vb2_q->drv_priv = cap;
+	vb2_q->mem_ops = &vb2_dma_contig_memops;
+	vb2_q->ops = &isp_video_vb2_ops;
+	vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
+	vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
+	vb2_q->dev = cap->isp->dev;
+	vb2_q->lock = &cap->lock;
+	vb2_q->min_queued_buffers = 2;
+
+	ret = vb2_queue_init(vb2_q);
+	if (ret < 0)
+		goto err_destroy;
+
+	cap->pad.flags = MEDIA_PAD_FL_SINK;
+	ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
+	if (ret < 0)
+		goto err_queue_release;
+
+	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+	if (ret < 0) {
+		dev_err(cap->isp->dev,
+			"Failed to register %s: %d\n", vdev->name, ret);
+		goto err_entity_cleanup;
+	}
+
+	return 0;
+
+err_entity_cleanup:
+	media_entity_cleanup(&vdev->entity);
+err_queue_release:
+	vb2_queue_release(vb2_q);
+err_destroy:
+	mutex_destroy(&cap->lock);
+	return ret;
+}
+
+int c3_isp_captures_register(struct c3_isp_device *isp)
+{
+	int ret;
+	unsigned int i;
+	struct c3_isp_capture *cap;
+
+	for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
+		cap = &isp->caps[i];
+		memset(cap, 0, sizeof(*cap));
+
+		cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
+		cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
+		cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
+		cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
+		cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+
+		c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
+
+		cap->id = i;
+		if (cap->id == C3_ISP_CAP_DEV_0)
+			cap->rsz = &isp->resizers[C3_ISP_RSZ_0];
+		else if (cap->id == C3_ISP_CAP_DEV_1)
+			cap->rsz = &isp->resizers[C3_ISP_RSZ_1];
+		else
+			cap->rsz = &isp->resizers[C3_ISP_RSZ_2];
+
+		cap->isp = isp;
+		INIT_LIST_HEAD(&cap->pending);
+		spin_lock_init(&cap->buff_lock);
+		mutex_init(&cap->lock);
+
+		ret = c3_isp_register_capture(cap);
+		if (ret) {
+			cap->isp = NULL;
+			mutex_destroy(&cap->lock);
+			c3_isp_captures_unregister(isp);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+void c3_isp_captures_unregister(struct c3_isp_device *isp)
+{
+	unsigned int i;
+	struct c3_isp_capture *cap;
+
+	for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
+		cap = &isp->caps[i];
+
+		if (!cap->isp)
+			continue;
+		vb2_queue_release(&cap->vb2_q);
+		media_entity_cleanup(&cap->vdev.entity);
+		video_unregister_device(&cap->vdev);
+		mutex_destroy(&cap->lock);
+	}
+}
+
+void c3_isp_captures_done(struct c3_isp_device *isp)
+{
+	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
+	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
+	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
+}
diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
new file mode 100644
index 000000000000..19f2a3bc29c9
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
@@ -0,0 +1,327 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef __C3_ISP_COMMON_H__
+#define __C3_ISP_COMMON_H__
+
+#include <linux/clk.h>
+
+#include <media/media-device.h>
+#include <media/videobuf2-core.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+#include <media/videobuf2-v4l2.h>
+
+#define C3_ISP_DRIVER_NAME            "c3-isp"
+#define C3_ISP_CLOCK_NUM_MAX          3
+
+#define C3_ISP_CORE_DEF_SINK_PAD_FMT  MEDIA_BUS_FMT_SRGGB10_1X10
+#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUYV8_1_5X8
+#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUYV8_1_5X8
+#define C3_ISP_DEFAULT_WIDTH          1920
+#define C3_ISP_DEFAULT_HEIGHT         1080
+#define C3_ISP_MAX_WIDTH              2888
+#define C3_ISP_MAX_HEIGHT             2240
+#define C3_ISP_MIN_WIDTH              160
+#define C3_ISP_MIN_HEIGHT             120
+
+#define C3_DISP_INTER                 0x400
+#define C3_DISP_REG(base, id)         ((base) + (id) * C3_DISP_INTER)
+#define C3_WRMIFX3_INTER              0x100
+#define C3_WRMIFX3_REG(base, id)      ((base) + (id) * C3_WRMIFX3_INTER)
+#define C3_PPS_TAP4_S11_H_NUM         33
+#define C3_PPS_LUT_CTYPE_0            0
+#define C3_PPS_LUT_CTYPE_2            2
+#define C3_SCALE_EN                   1
+#define C3_SCALE_DIS                  0
+
+#define C3_ISP_PHASE_OFFSET_0         0
+#define C3_ISP_PHASE_OFFSET_1         1
+#define C3_ISP_PHASE_OFFSET_NONE      0xff
+
+enum c3_isp_core_pads {
+	C3_ISP_CORE_PAD_SINK_VIDEO,
+	C3_ISP_CORE_PAD_SINK_PARAMS,
+	C3_ISP_CORE_PAD_SOURCE_STATS,
+	C3_ISP_CORE_PAD_SOURCE_VIDEO,
+	C3_ISP_CORE_PAD_MAX
+};
+
+enum c3_isp_resizer_ids {
+	C3_ISP_RSZ_0,
+	C3_ISP_RSZ_1,
+	C3_ISP_RSZ_2,
+	C3_ISP_NUM_RSZ
+};
+
+enum c3_isp_resizer_pads {
+	C3_ISP_RESIZER_PAD_SINK,
+	C3_ISP_RESIZER_PAD_SOURCE,
+	C3_ISP_RESIZER_PAD_MAX
+};
+
+enum c3_isp_cap_devs {
+	C3_ISP_CAP_DEV_0,
+	C3_ISP_CAP_DEV_1,
+	C3_ISP_CAP_DEV_2,
+	C3_ISP_NUM_CAP_DEVS
+};
+
+/**
+ * struct c3_isp_pps_io_size - isp scaler input and output size
+ *
+ * @thsize: input horizontal size of after preprocessing
+ * @tvsize: input vertical size of after preprocessing
+ * @ohsize: output horizontal size
+ * @ovsize: output vertical size
+ * @ihsize: input horizontal size
+ * @max_hsize: maximum horizontal size
+ */
+struct c3_isp_pps_io_size {
+	u32 thsize;
+	u32 tvsize;
+	u32 ohsize;
+	u32 ovsize;
+	u32 ihsize;
+	u32 max_hsize;
+};
+
+/**
+ * @mbus_code: the mbus code
+ * @pads: save the pad flag of this mbus_code
+ * @xofst: horizontal phase offset of hardware
+ * @yofst: vertical phase offset of hardware
+ */
+struct c3_isp_mbus_format_info {
+	u32 mbus_code;
+	u32 pads;
+	u8 xofst;
+	u8 yofst;
+};
+
+/**
+ * @mbus_code: the mbus code
+ * @fourcc: pixel format
+ * @depth: pixel width
+ */
+struct c3_isp_capture_format {
+	u32 mbus_code;
+	u32 fourcc;
+	u8 depth;
+};
+
+/**
+ * struct c3_isp_vb2_buffer - A container of vb2 buffer
+ *
+ * @vb: vb2 buffer
+ * @vaddr: buffer virtual address
+ * @paddr: buffer physical address
+ * @list: entry of the buffer in the queue
+ */
+struct c3_isp_vb2_buffer {
+	struct vb2_v4l2_buffer vb;
+	void *vaddr;
+	dma_addr_t paddr;
+	struct list_head list;
+};
+
+/**
+ * struct c3_isp_core - ISP core subdev
+ *
+ * @sd: ISP sub-device
+ * @pads: ISP sub-device pads
+ * @src_sd: source sub-device
+ * @isp: pointer to c3_isp_device
+ * @src_sd_pad: source sub-device pad
+ */
+struct c3_isp_core {
+	struct v4l2_subdev sd;
+	struct media_pad pads[C3_ISP_CORE_PAD_MAX];
+	struct v4l2_subdev *src_sd;
+	u16 src_sd_pad;
+	struct c3_isp_device *isp;
+};
+
+/**
+ * struct c3_isp_resizer - ISP resizer subdev
+ *
+ * @id: resizer id
+ * @sd: resizer sub-device
+ * @pads: resizer sub-device pads
+ * @isp: pointer to c3_isp_device
+ * @cap: pointer to c3_isp_capture
+ */
+struct c3_isp_resizer {
+	enum c3_isp_resizer_ids id;
+	struct v4l2_subdev sd;
+	struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
+	struct c3_isp_device *isp;
+	struct c3_isp_capture *cap;
+};
+
+/**
+ * struct c3_isp_stats - ISP statistics device
+ *
+ * @vb2_q: vb2 buffer queue
+ * @vdev: video node
+ * @vfmt: v4l2_format of the metadata format
+ * @pad: media pad
+ * @lock: protects vb2_q, vdev
+ * @is_streaming: stats status
+ * @isp: pointer to c3_isp_device
+ * @buff: in use buffer
+ * @buff_lock: protects stats buffer
+ * @pending: stats buffer list head
+ */
+struct c3_isp_stats {
+	struct vb2_queue vb2_q;
+	struct video_device vdev;
+	struct v4l2_format vfmt;
+	struct media_pad pad;
+
+	struct mutex lock; /* Protects vb2_q, vdev */
+	bool is_streaming;
+	struct c3_isp_device *isp;
+
+	struct c3_isp_vb2_buffer *buff;
+	spinlock_t buff_lock; /* Protects stream buffer */
+	struct list_head pending;
+};
+
+/**
+ * struct c3_isp_params - ISP parameters device
+ *
+ * @vb2_q: vb2 buffer queue
+ * @vdev: video node
+ * @vfmt: v4l2_format of the metadata format
+ * @pad: media pad
+ * @lock: protects vb2_q, vdev
+ * @isp: pointer to c3_isp_device
+ * @buff: in use buffer
+ * @buff_lock: protects stats buffer
+ * @pending: stats buffer list head
+ */
+struct c3_isp_params {
+	struct vb2_queue vb2_q;
+	struct video_device vdev;
+	struct v4l2_format vfmt;
+	struct media_pad pad;
+
+	struct mutex lock; /* Protects vb2_q, vdev */
+	struct c3_isp_device *isp;
+
+	struct c3_isp_vb2_buffer *buff;
+	spinlock_t buff_lock; /* Protects stream buffer */
+	struct list_head pending;
+};
+
+/**
+ * struct c3_isp_capture - ISP capture device
+ *
+ * @id: capture device ID
+ * @vb2_q: vb2 buffer queue
+ * @vdev: video node
+ * @vfmt: v4l2_format of the capture format
+ * @pad: media pad
+ * @lock: protects vb2_q, vdev
+ * @is_streaming: capture device status
+ * @isp: pointer to c3_isp_device
+ * @rsz: pointer to c3_isp_resizer
+ * @buff: in use buffer
+ * @buff_lock: protects capture buffer
+ * @pending: capture buffer list head
+ */
+struct c3_isp_capture {
+	enum c3_isp_cap_devs id;
+	struct vb2_queue vb2_q;
+	struct video_device vdev;
+	struct v4l2_format vfmt;
+	struct media_pad pad;
+
+	struct mutex lock; /* Protects vb2_q, vdev */
+	bool is_streaming;
+	struct c3_isp_device *isp;
+	struct c3_isp_resizer *rsz;
+
+	struct c3_isp_vb2_buffer *buff;
+	spinlock_t buff_lock; /* Protects stream buffer */
+	struct list_head pending;
+};
+
+/**
+ * struct c3_isp_info - ISP information
+ *
+ * @clocks: array of ISP clock names
+ * @clock_rates: array of ISP clock rate
+ * @clock_num: actual clock number
+ */
+struct c3_isp_info {
+	char *clocks[C3_ISP_CLOCK_NUM_MAX];
+	u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
+	u32 clock_num;
+};
+
+/**
+ * struct c3_isp_device - ISP platform device
+ *
+ * @dev: pointer to the struct device
+ * @base: base register address
+ * @clks: array of clocks
+ * @notifier: notifier to register on the v4l2-async API
+ * @v4l2_dev: v4l2_device variable
+ * @media_dev: media device variable
+ * @pipe: media pipeline
+ * @core: ISP core subdev
+ * @resizer: ISP resizer subdev
+ * @stats: ISP stats device
+ * @params: ISP params device
+ * @caps: array of ISP capture device
+ * @frm_sequence: used to record frame id
+ * @lock: protect ISP device
+ * @info: version-specific ISP information
+ */
+struct c3_isp_device {
+	struct device *dev;
+	void __iomem *base;
+	struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
+
+	struct v4l2_async_notifier notifier;
+	struct v4l2_device v4l2_dev;
+	struct media_device media_dev;
+	struct media_pipeline pipe;
+
+	struct c3_isp_core core;
+	struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
+	struct c3_isp_stats stats;
+	struct c3_isp_params params;
+	struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
+
+	u32 frm_sequence;
+	struct mutex lock; /* Protect ISP device */
+	const struct c3_isp_info *info;
+};
+
+u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
+void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
+void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
+bool c3_isp_pipeline_ready(struct c3_isp_device *isp);
+
+int c3_isp_core_register(struct c3_isp_device *isp);
+void c3_isp_core_unregister(struct c3_isp_device *isp);
+int c3_isp_resizers_register(struct c3_isp_device *isp);
+void c3_isp_resizers_unregister(struct c3_isp_device *isp);
+void c3_isp_rsz_start(struct c3_isp_resizer *rsz);
+void c3_isp_rsz_stop(struct c3_isp_resizer *rsz);
+int c3_isp_captures_register(struct c3_isp_device *isp);
+void c3_isp_captures_unregister(struct c3_isp_device *isp);
+void c3_isp_captures_done(struct c3_isp_device *isp);
+int c3_isp_stats_register(struct c3_isp_device *isp);
+void c3_isp_stats_unregister(struct c3_isp_device *isp);
+int c3_isp_stats_done(struct c3_isp_device *isp);
+int c3_isp_params_register(struct c3_isp_device *isp);
+void c3_isp_params_unregister(struct c3_isp_device *isp);
+int c3_isp_params_done(struct c3_isp_device *isp);
+
+#endif
diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
new file mode 100644
index 000000000000..d3672aff9fd2
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
@@ -0,0 +1,675 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/pm_runtime.h>
+
+#include "c3-isp-common.h"
+#include "c3-isp-regs.h"
+#include "include/uapi/c3-isp-config.h"
+
+#define C3_ISP_CORE_SUBDEV_NAME        "isp-core"
+
+static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
+	/* RAW formats */
+	{
+		.mbus_code	= MEDIA_BUS_FMT_SBGGR10_1X10,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_0,
+		.yofst		= C3_ISP_PHASE_OFFSET_1,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SGBRG10_1X10,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_1,
+		.yofst		= C3_ISP_PHASE_OFFSET_1,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SGRBG10_1X10,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_0,
+		.yofst		= C3_ISP_PHASE_OFFSET_0,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SRGGB10_1X10,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_1,
+		.yofst		= C3_ISP_PHASE_OFFSET_0,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SBGGR12_1X12,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_0,
+		.yofst		= C3_ISP_PHASE_OFFSET_1,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SGBRG12_1X12,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_1,
+		.yofst		= C3_ISP_PHASE_OFFSET_1,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SGRBG12_1X12,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_0,
+		.yofst		= C3_ISP_PHASE_OFFSET_0,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_SRGGB12_1X12,
+		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_1,
+		.yofst		= C3_ISP_PHASE_OFFSET_0,
+	},
+	/* YUV formats */
+	{
+		.mbus_code	= MEDIA_BUS_FMT_Y8_1X8,
+		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
+		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_YUYV8_1_5X8,
+		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
+		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_YUYV8_2X8,
+		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
+		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
+		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
+	},
+};
+
+static const struct c3_isp_mbus_format_info
+*core_find_format_by_code(u32 code, u32 pad)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
+		const struct c3_isp_mbus_format_info *info =
+			&c3_isp_core_mbus_formats[i];
+
+		if (info->mbus_code == code && info->pads & BIT(pad))
+			return info;
+	}
+
+	return NULL;
+}
+
+static const struct c3_isp_mbus_format_info
+*core_find_format_by_index(u32 index, u32 pad)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
+		const struct c3_isp_mbus_format_info *info =
+			&c3_isp_core_mbus_formats[i];
+
+		if (!(info->pads & BIT(pad)))
+			continue;
+
+		if (!index)
+			return info;
+
+		index--;
+	}
+
+	return NULL;
+}
+
+static void c3_isp_core_enable(struct c3_isp_device *isp)
+{
+	/* Select the line sync signal */
+	c3_isp_update_bits(isp, ISP_TOP_IRQ_LINE_THRD,
+			   TOP_IRQ_DIN_HSYNC, TOP_IRQ_DIN_HSYNC);
+
+	/* Enable frame done and stats error irq */
+	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
+			   TOP_IRQ_FRAME_DONE, TOP_IRQ_FRAME_DONE);
+	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
+			   TOP_IRQ_STATS_ERR, TOP_IRQ_STATS_ERR);
+
+	/* Enable image data to ISP core */
+	c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
+			   TOP_DATA_PATH_MASK, TOP_PATH_MIPI_TO_CORE);
+}
+
+static void c3_isp_core_disable(struct c3_isp_device *isp)
+{
+	/* Disable image data to ISP core */
+	c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
+			   TOP_DATA_PATH_MASK, 0x0);
+
+	/* Disable all irq */
+	c3_isp_write(isp, ISP_TOP_IRQ_EN, 0x0);
+}
+
+/* Set the phase offset of blc, wb and lns */
+static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
+				  u8 xofst, u8 yofst)
+{
+	c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
+			   LSWB_BLC_XPHS_OFST_MASK,
+			   xofst << LSWB_BLC_XPHS_OFST_SHIFT);
+	c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
+			   LSWB_BLC_YPHS_OFST_MASK, yofst);
+
+	c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
+			   LSWB_WB_XPHS_OFST_MASK,
+			   xofst << LSWB_WB_XPHS_OFST_SHIFT);
+	c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
+			   LSWB_WB_YPHS_OFST_MASK, yofst);
+
+	c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
+			   LSWB_LNS_XPHS_OFST_MASK,
+			   xofst << LSWB_LNS_XPHS_OFST_SHIFT);
+	c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
+			   LSWB_LNS_YPHS_OFST_MASK, yofst);
+}
+
+/* Set the phase offset of af, ae and awb */
+static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
+				u8 xofst, u8 yofst)
+{
+	c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_XPHS_OFST_MASK,
+			   xofst << AF_CTRL_XPHS_OFST_SHIFT);
+	c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_YPHS_OFST_MASK,
+			   yofst << AF_CTRL_YPHS_OFST_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_XPHS_OFST_MASK,
+			   xofst << AE_CTRL_XPHS_OFST_SHIFT);
+	c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_YPHS_OFST_MASK,
+			   yofst << AE_CTRL_YPHS_OFST_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_XPHS_OFST_MASK,
+			   xofst << AWB_CTRL_XPHS_OFST_SHIFT);
+	c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_YPHS_OFST_MASK, yofst);
+}
+
+/* Set the phase offset of demosaic */
+static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
+				 u8 xofst, u8 yofst)
+{
+	c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_XPHS_OFST_MASK,
+			   xofst << DMS_COMMON_XPHS_OFST_SHIFT);
+	c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_YPHS_OFST_MASK, yofst);
+}
+
+static void c3_isp_core_cfg_ofst(struct c3_isp_device *isp,
+				 struct v4l2_mbus_framefmt *fmt)
+{
+	const struct c3_isp_mbus_format_info *isp_fmt =
+			core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
+
+	c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
+	c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
+	c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
+}
+
+/* Set format of the hardware control module */
+static void c3_isp_core_top_fmt(struct c3_isp_device *isp,
+				struct v4l2_mbus_framefmt *fmt)
+{
+	c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
+		     TOP_INPUT_HSIZE(fmt->width) | TOP_INPUT_VSIZE(fmt->height));
+
+	c3_isp_write(isp, ISP_TOP_FRM_SIZE,
+		     TOP_FRM_CORE_IHSIZE(fmt->width) | TOP_FRM_CORE_IVSIZE(fmt->height));
+
+	c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE, TOP_HOLD_HSIZE_MASK,
+			   fmt->width << TOP_HOLD_HSIZE_SHIFT);
+}
+
+static void c3_isp_core_af_fmt(struct c3_isp_device *isp,
+			       struct v4l2_mbus_framefmt *fmt)
+{
+	u32 hidx;
+	u32 vidx;
+	int i;
+
+	c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, AF_HV_STAT_HBLK_NUM_MASK,
+			   AF_STAT_BLKH_NUM << AF_HV_STAT_HBLK_NUM_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM,
+			   AF_HV_STAT_VBLK_NUM_MASK, AF_STAT_BLKV_NUM);
+
+	c3_isp_write(isp, ISP_AF_HV_SIZE,
+		     AF_HV_STAT_HSIZE(fmt->width) | AF_HV_STAT_VSIZE(fmt->height));
+
+	/* Set the index address to 0 position */
+	c3_isp_write(isp, ISP_AF_IDX_ADDR, 0);
+	/*
+	 * Calculate and set the coordinates of points in the grid.
+	 * hidx and vidx need to be aligned with 2.
+	 */
+	for (i = 0; i <= AF_STAT_BLKH_NUM; i++) {
+		hidx = i * fmt->width / AF_STAT_BLKH_NUM;
+		hidx = ALIGN_DOWN(hidx, 2);
+
+		vidx = i * fmt->height / AF_STAT_BLKV_NUM;
+		vidx = min(vidx, fmt->height);
+		vidx = ALIGN_DOWN(vidx, 2);
+		c3_isp_write(isp, ISP_AF_IDX_DATA,
+			     AF_IDX_HIDX_DATA(hidx) | AF_IDX_VIDX_DATA(vidx));
+	}
+}
+
+static void c3_isp_core_ae_fmt(struct c3_isp_device *isp,
+			       struct v4l2_mbus_framefmt *fmt)
+{
+	u32 hidx;
+	u32 vidx;
+	int i;
+
+	c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, AE_HV_STAT_HBLK_NUM_MASK,
+			   AE_STAT_BLKH_NUM << AE_HV_STAT_HBLK_NUM_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM,
+			   AE_HV_STAT_VBLK_NUM_MASK, AE_STAT_BLKV_NUM);
+
+	c3_isp_write(isp, ISP_AE_HV_SIZE,
+		     AE_HV_STAT_HSIZE(fmt->width) | AE_HV_STAT_VSIZE(fmt->height));
+
+	/* Set the index address to 0 position */
+	c3_isp_write(isp, ISP_AE_IDX_ADDR, 0);
+	/*
+	 * Calculate and set the coordinates of points in the grid.
+	 * hidx and vidx need to be aligned with 2.
+	 */
+	for (i = 0; i <= AE_STAT_BLKH_NUM; i++) {
+		hidx = i * fmt->width / AE_STAT_BLKH_NUM;
+		hidx = ALIGN_DOWN(hidx, 2);
+
+		vidx = i * fmt->height / AE_STAT_BLKV_NUM;
+		vidx = min(vidx, fmt->height);
+		vidx = ALIGN_DOWN(vidx, 2);
+
+		c3_isp_write(isp, ISP_AE_IDX_DATA,
+			     AE_IDX_HIDX_DATA(hidx) | AE_IDX_VIDX_DATA(vidx));
+	}
+}
+
+static void c3_isp_core_awb_fmt(struct c3_isp_device *isp,
+				struct v4l2_mbus_framefmt *fmt)
+{
+	u32 hidx;
+	u32 vidx;
+	int i;
+
+	c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, AWB_HV_STAT_HBLK_NUM_MASK,
+			   AWB_STAT_BLKH_NUM << AWB_HV_STAT_HBLK_NUM_SHIFT);
+	c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM,
+			   AWB_HV_STAT_VBLK_NUM_MASK, AWB_STAT_BLKV_NUM);
+
+	c3_isp_write(isp, ISP_AWB_HV_SIZE,
+		     AWB_HV_STAT_HSIZE(fmt->width) | AWB_HV_STAT_VSIZE(fmt->height));
+
+	/* Set the index address to 0 position */
+	c3_isp_write(isp, ISP_AWB_IDX_ADDR, 0);
+	/*
+	 * Calculate and set the coordinates of points in the grid.
+	 * hidx and vidx need to be aligned with 2.
+	 */
+	for (i = 0; i <= AWB_STAT_BLKH_NUM; i++) {
+		hidx = i * fmt->width / AWB_STAT_BLKH_NUM;
+		hidx = ALIGN_DOWN(hidx, 2);
+
+		vidx = i * fmt->height / AWB_STAT_BLKV_NUM;
+		vidx = min(vidx, fmt->height);
+		vidx = ALIGN_DOWN(vidx, 2);
+
+		c3_isp_write(isp, ISP_AWB_IDX_DATA,
+			     AWB_IDX_HIDX_DATA(hidx) | AWB_IDX_VIDX_DATA(vidx));
+	}
+}
+
+static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
+				   struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *fmt;
+
+	fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
+
+	c3_isp_core_cfg_ofst(isp, fmt);
+	c3_isp_core_top_fmt(isp, fmt);
+	c3_isp_core_af_fmt(isp, fmt);
+	c3_isp_core_ae_fmt(isp, fmt);
+	c3_isp_core_awb_fmt(isp, fmt);
+}
+
+static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
+				      struct v4l2_subdev_state *state,
+				      u32 pad, u64 streams_mask)
+{
+	struct c3_isp_core *core = v4l2_get_subdevdata(sd);
+	u64 sink_streams;
+	int ret;
+
+	core->isp->frm_sequence = 0;
+	c3_isp_core_cfg_format(core->isp, state);
+	c3_isp_core_enable(core->isp);
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+						       C3_ISP_CORE_PAD_SINK_VIDEO,
+						       &streams_mask);
+	ret = v4l2_subdev_enable_streams(core->src_sd,
+					 core->src_sd_pad, sink_streams);
+	if (ret) {
+		c3_isp_core_disable(core->isp);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
+				       struct v4l2_subdev_state *state,
+				       u32 pad, u64 streams_mask)
+{
+	struct c3_isp_core *core = v4l2_get_subdevdata(sd);
+	u64 sink_streams;
+	int ret;
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+						       C3_ISP_CORE_PAD_SINK_VIDEO,
+						       &streams_mask);
+	ret = v4l2_subdev_disable_streams(core->src_sd,
+					  core->src_sd_pad, sink_streams);
+	if (ret)
+		return ret;
+
+	c3_isp_core_disable(core->isp);
+
+	return 0;
+}
+
+static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state,
+				   struct v4l2_subdev_krouting *routing)
+{
+	static const struct v4l2_mbus_framefmt format = {
+		.width = C3_ISP_DEFAULT_WIDTH,
+		.height = C3_ISP_DEFAULT_HEIGHT,
+		.code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
+		.field = V4L2_FIELD_NONE,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.ycbcr_enc = V4L2_YCBCR_ENC_601,
+		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
+		.xfer_func = V4L2_XFER_FUNC_SRGB,
+	};
+	int ret;
+
+	ret = v4l2_subdev_routing_validate(sd, routing,
+					   V4L2_SUBDEV_ROUTING_NO_SOURCE_STREAM_MIX);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes[2];
+	struct v4l2_subdev_krouting routing;
+
+	routes[0].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
+	routes[0].sink_stream = 0;
+	routes[0].source_pad = C3_ISP_CORE_PAD_SOURCE_STATS;
+	routes[0].source_stream = 0;
+	routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
+
+	routes[1].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
+	routes[1].sink_stream = 0;
+	routes[1].source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
+	routes[1].source_stream = 0;
+	routes[1].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
+
+	routing.num_routes = ARRAY_SIZE(routes);
+	routing.routes = routes;
+
+	return c3_isp_core_cfg_routing(sd, state, &routing);
+}
+
+static int c3_isp_core_set_routing(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state,
+				   enum v4l2_subdev_format_whence which,
+				   struct v4l2_subdev_krouting *routing)
+{
+	bool is_streaming = v4l2_subdev_is_streaming(sd);
+
+	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
+		return -EBUSY;
+
+	return c3_isp_core_cfg_routing(sd, state, routing);
+}
+
+static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
+				      struct v4l2_subdev_state *state,
+				      struct v4l2_subdev_mbus_code_enum *code)
+{
+	const struct c3_isp_mbus_format_info *info;
+	int ret = 0;
+
+	switch (code->pad) {
+	case C3_ISP_CORE_PAD_SINK_VIDEO:
+	case C3_ISP_CORE_PAD_SOURCE_VIDEO:
+		info = core_find_format_by_index(code->index, code->pad);
+		if (!info)
+			ret = -EINVAL;
+		else
+			code->code = info->mbus_code;
+
+		break;
+	case C3_ISP_CORE_PAD_SINK_PARAMS:
+	case C3_ISP_CORE_PAD_SOURCE_STATS:
+		if (code->index)
+			ret = -EINVAL;
+		else
+			code->code = MEDIA_BUS_FMT_METADATA_FIXED;
+
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static void c3_isp_core_set_sink_fmt(struct v4l2_subdev_state *state,
+				     struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *sink_fmt;
+	const struct c3_isp_mbus_format_info *isp_fmt;
+
+	sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
+
+	isp_fmt = core_find_format_by_code(format->format.code, format->pad);
+	if (!isp_fmt)
+		sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
+	else
+		sink_fmt->code = format->format.code;
+
+	sink_fmt->width = clamp_t(u32, format->format.width,
+				  C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
+	sink_fmt->height = clamp_t(u32, format->format.height,
+				   C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
+
+	format->format = *sink_fmt;
+}
+
+static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
+				       struct v4l2_subdev_format *format)
+{
+	const struct c3_isp_mbus_format_info *isp_fmt;
+	struct v4l2_mbus_framefmt *sink_fmt;
+	struct v4l2_mbus_framefmt *src_fmt;
+
+	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
+	src_fmt = v4l2_subdev_state_get_format(state, format->pad);
+
+	isp_fmt = core_find_format_by_code(format->format.code, format->pad);
+	if (!isp_fmt)
+		src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
+	else
+		src_fmt->code = format->format.code;
+
+	/* The source size must be same with the sink size. */
+	src_fmt->width  = sink_fmt->width;
+	src_fmt->height = sink_fmt->height;
+
+	format->format = *src_fmt;
+}
+
+static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
+			       struct v4l2_subdev_state *state,
+			       struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *fmt;
+
+	switch (format->pad) {
+	case C3_ISP_CORE_PAD_SINK_VIDEO:
+		c3_isp_core_set_sink_fmt(state, format);
+		break;
+	case C3_ISP_CORE_PAD_SINK_PARAMS:
+	case C3_ISP_CORE_PAD_SOURCE_STATS:
+		fmt = v4l2_subdev_state_get_format(state, format->pad);
+		format->format = *fmt;
+		break;
+	case C3_ISP_CORE_PAD_SOURCE_VIDEO:
+		c3_isp_core_set_source_fmt(state, format);
+		break;
+	default:
+		dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
+		return -ENOTTY;
+	}
+
+	return 0;
+}
+
+static int c3_isp_core_init_state(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *sink_fmt;
+	struct v4l2_mbus_framefmt *src_fmt;
+
+	/* Video sink pad */
+	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
+	sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
+	sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
+	sink_fmt->field = V4L2_FIELD_NONE;
+	sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
+	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
+	sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
+	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
+	sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+
+	/* Video source pad */
+	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
+	src_fmt->width = C3_ISP_DEFAULT_WIDTH;
+	src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
+	src_fmt->field = V4L2_FIELD_NONE;
+	src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
+	src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
+	src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
+	src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
+	src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
+
+	/* Parameters pad */
+	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
+	sink_fmt->width = 0;
+	sink_fmt->height = 0;
+	sink_fmt->field = V4L2_FIELD_NONE;
+	sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
+
+	/* Statistics pad */
+	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
+	src_fmt->width = 0;
+	src_fmt->height = 0;
+	src_fmt->field = V4L2_FIELD_NONE;
+	src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
+
+	return c3_isp_core_init_routing(sd, state);
+}
+
+static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
+	.enum_mbus_code = c3_isp_core_enum_mbus_code,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = c3_isp_core_set_fmt,
+	.enable_streams = c3_isp_core_enable_streams,
+	.disable_streams = c3_isp_core_disable_streams,
+	.set_routing = c3_isp_core_set_routing,
+};
+
+static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
+	.pad = &c3_isp_core_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
+	.init_state = c3_isp_core_init_state,
+};
+
+static int c3_isp_core_link_validate(struct media_link *link)
+{
+	if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
+		return 0;
+
+	return v4l2_subdev_link_validate(link);
+}
+
+/* Media entity operations */
+static const struct media_entity_operations c3_isp_core_entity_ops = {
+	.link_validate = c3_isp_core_link_validate,
+};
+
+int c3_isp_core_register(struct c3_isp_device *isp)
+{
+	struct c3_isp_core *core = &isp->core;
+	struct v4l2_subdev *sd = &core->sd;
+	int ret;
+
+	v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
+	sd->owner = THIS_MODULE;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	sd->internal_ops = &c3_isp_core_internal_ops;
+	snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
+
+	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+	sd->entity.ops = &c3_isp_core_entity_ops;
+
+	core->isp = isp;
+	sd->dev = isp->dev;
+	v4l2_set_subdevdata(sd, core);
+
+	core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
+	core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
+	core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
+	core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_init_finalize(sd);
+	if (ret)
+		goto err_entity_cleanup;
+
+	ret = v4l2_device_register_subdev(&isp->v4l2_dev, sd);
+	if (ret)
+		goto err_subdev_cleanup;
+
+	return 0;
+
+err_subdev_cleanup:
+	v4l2_subdev_cleanup(sd);
+err_entity_cleanup:
+	media_entity_cleanup(&sd->entity);
+	return ret;
+}
+
+void c3_isp_core_unregister(struct c3_isp_device *isp)
+{
+	struct c3_isp_core *core = &isp->core;
+	struct v4l2_subdev *sd = &core->sd;
+
+	v4l2_device_unregister_subdev(sd);
+	v4l2_subdev_cleanup(sd);
+	media_entity_cleanup(&sd->entity);
+}
diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
new file mode 100644
index 000000000000..a57b9f8dbc3c
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
@@ -0,0 +1,486 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+
+#include "c3-isp-common.h"
+#include "c3-isp-regs.h"
+
+u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
+{
+	return readl(isp->base + reg);
+}
+
+void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
+{
+	writel(val, isp->base + reg);
+}
+
+void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
+{
+	u32 orig, tmp;
+
+	orig = c3_isp_read(isp, reg);
+
+	tmp = orig & ~mask;
+	tmp |= val & mask;
+
+	if (tmp != orig)
+		c3_isp_write(isp, reg, tmp);
+}
+
+bool c3_isp_pipeline_ready(struct c3_isp_device *isp)
+{
+	struct media_pipeline_entity_iter iter;
+	unsigned int n_video_devices = 0;
+	struct media_entity *entity;
+	int ret;
+
+	ret = media_pipeline_entity_iter_init(&isp->pipe, &iter);
+	if (ret)
+		return ret;
+
+	media_pipeline_for_each_entity(&isp->pipe, &iter, entity) {
+		if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
+			n_video_devices++;
+	}
+
+	media_pipeline_entity_iter_cleanup(&iter);
+
+	return n_video_devices == isp->pipe.start_count;
+}
+
+/* PM runtime suspend */
+static int __maybe_unused c3_isp_runtime_suspend(struct device *dev)
+{
+	struct c3_isp_device *isp = dev_get_drvdata(dev);
+
+	clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
+
+	return 0;
+}
+
+/* PM runtime resume */
+static int __maybe_unused c3_isp_runtime_resume(struct device *dev)
+{
+	struct c3_isp_device *isp = dev_get_drvdata(dev);
+
+	return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
+}
+
+static const struct dev_pm_ops c3_isp_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				pm_runtime_force_resume)
+	SET_RUNTIME_PM_OPS(c3_isp_runtime_suspend,
+			   c3_isp_runtime_resume, NULL)
+};
+
+/* IRQ handling */
+static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
+{
+	struct c3_isp_device *isp = dev;
+	u32 status;
+
+	/* Get irq status and clear irq status */
+	status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
+	c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
+
+	if (status & TOP_IRQ_FRAME_DONE) {
+		c3_isp_stats_done(isp);
+		c3_isp_params_done(isp);
+		c3_isp_captures_done(isp);
+		isp->frm_sequence++;
+	}
+
+	if (status & TOP_IRQ_STATS_ERR)
+		dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
+
+	return IRQ_HANDLED;
+}
+
+/* Subdev notifier register */
+static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
+			       struct v4l2_subdev *sd,
+			       struct v4l2_async_connection *asc)
+{
+	struct c3_isp_device *isp =
+		container_of(notifier, struct c3_isp_device, notifier);
+	struct c3_isp_core *core = &isp->core;
+	struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
+	int ret;
+
+	ret = media_entity_get_fwnode_pad(&sd->entity,
+					  sd->fwnode, MEDIA_PAD_FL_SOURCE);
+	if (ret < 0) {
+		dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
+		return ret;
+	}
+
+	core->src_sd = sd;
+	core->src_sd_pad = ret;
+
+	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
+					       MEDIA_LNK_FL_IMMUTABLE);
+}
+
+static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
+{
+	struct c3_isp_device *isp =
+		container_of(notifier, struct c3_isp_device, notifier);
+	int ret;
+
+	ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
+	if (ret < 0) {
+		dev_err(isp->dev,
+			"Failed to register subdev nodes: %d\n", ret);
+		return ret;
+	}
+
+	dev_info(isp->dev, "notify complete\n");
+
+	return media_device_register(&isp->media_dev);
+}
+
+static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
+{
+	struct c3_isp_device *isp =
+		container_of(asc->notifier, struct c3_isp_device, notifier);
+
+	media_device_unregister(&isp->media_dev);
+}
+
+static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
+	.bound = c3_isp_notify_bound,
+	.complete = c3_isp_notify_complete,
+	.destroy = c3_isp_notify_destroy,
+};
+
+static int c3_isp_async_nf_register(struct c3_isp_device *isp)
+{
+	struct v4l2_async_connection *asc;
+	struct fwnode_handle *ep;
+	int ret;
+
+	v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
+
+	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
+					     FWNODE_GRAPH_ENDPOINT_NEXT);
+	if (!ep)
+		return -ENOTCONN;
+
+	asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
+					      struct v4l2_async_connection);
+	if (IS_ERR(asc)) {
+		fwnode_handle_put(ep);
+		return PTR_ERR(asc);
+	}
+
+	fwnode_handle_put(ep);
+
+	isp->notifier.ops = &c3_isp_notify_ops;
+	ret = v4l2_async_nf_register(&isp->notifier);
+	if (ret)
+		v4l2_async_nf_cleanup(&isp->notifier);
+
+	return ret;
+}
+
+static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
+{
+	v4l2_async_nf_unregister(&isp->notifier);
+	v4l2_async_nf_cleanup(&isp->notifier);
+}
+
+static int c3_isp_v4l2_register(struct c3_isp_device *isp)
+{
+	struct media_device *media_dev = &isp->media_dev;
+	struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
+	int ret;
+
+	/* Initialize media device */
+	strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
+		sizeof(media_dev->model));
+	media_dev->dev = isp->dev;
+
+	media_device_init(media_dev);
+
+	/* Initialize v4l2 device */
+	v4l2_dev->mdev = media_dev;
+	strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
+		sizeof(v4l2_dev->name));
+
+	ret = v4l2_device_register(isp->dev, v4l2_dev);
+	if (ret) {
+		media_device_cleanup(media_dev);
+		dev_err(isp->dev,
+			"Failed to register V4L2 device: %d\n", ret);
+	}
+
+	return ret;
+}
+
+static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
+{
+	v4l2_device_unregister(&isp->v4l2_dev);
+	media_device_cleanup(&isp->media_dev);
+}
+
+static void c3_isp_remove_links(struct c3_isp_device *isp)
+{
+	unsigned int i;
+
+	media_entity_remove_links(&isp->core.sd.entity);
+
+	for (i = 0; i < C3_ISP_NUM_RSZ; i++)
+		media_entity_remove_links(&isp->resizers[i].sd.entity);
+
+	for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
+		media_entity_remove_links(&isp->caps[i].vdev.entity);
+}
+
+static int c3_isp_create_links(struct c3_isp_device *isp)
+{
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
+		ret = media_create_pad_link(&isp->resizers[i].sd.entity,
+					    C3_ISP_RESIZER_PAD_SOURCE,
+					    &isp->resizers[i].cap->vdev.entity,
+					    0, MEDIA_LNK_FL_ENABLED);
+		if (ret) {
+			dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
+			goto err_remove_links;
+		}
+
+		ret = media_create_pad_link(&isp->core.sd.entity,
+					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
+					    &isp->resizers[i].sd.entity,
+					    C3_ISP_RESIZER_PAD_SINK,
+					    MEDIA_LNK_FL_ENABLED);
+		if (ret) {
+			dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
+			goto err_remove_links;
+		}
+	}
+
+	ret = media_create_pad_link(&isp->core.sd.entity,
+				    C3_ISP_CORE_PAD_SOURCE_STATS,
+				    &isp->stats.vdev.entity,
+				    0, MEDIA_LNK_FL_ENABLED);
+	if (ret) {
+		dev_err(isp->dev, "Failed to link core and stats\n");
+		goto err_remove_links;
+	}
+
+	ret = media_create_pad_link(&isp->params.vdev.entity, 0,
+				    &isp->core.sd.entity,
+				    C3_ISP_CORE_PAD_SINK_PARAMS,
+				    MEDIA_LNK_FL_ENABLED);
+	if (ret) {
+		dev_err(isp->dev, "Failed to link params and core\n");
+		goto err_remove_links;
+	}
+
+	return 0;
+
+err_remove_links:
+	c3_isp_remove_links(isp);
+	return ret;
+}
+
+static int c3_isp_videos_register(struct c3_isp_device *isp)
+{
+	int ret;
+
+	ret = c3_isp_captures_register(isp);
+	if (ret)
+		return ret;
+
+	ret = c3_isp_stats_register(isp);
+	if (ret)
+		goto err_captures_unregister;
+
+	ret = c3_isp_params_register(isp);
+	if (ret)
+		goto err_stats_unregister;
+
+	ret = c3_isp_create_links(isp);
+	if (ret)
+		goto err_params_unregister;
+
+	return 0;
+
+err_params_unregister:
+	c3_isp_params_unregister(isp);
+err_stats_unregister:
+	c3_isp_stats_unregister(isp);
+err_captures_unregister:
+	c3_isp_captures_unregister(isp);
+	return ret;
+}
+
+static void c3_isp_videos_unregister(struct c3_isp_device *isp)
+{
+	c3_isp_remove_links(isp);
+	c3_isp_params_unregister(isp);
+	c3_isp_stats_unregister(isp);
+	c3_isp_captures_unregister(isp);
+}
+
+static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
+{
+	const struct c3_isp_info *info = isp->info;
+	int ret;
+	u32 i;
+
+	for (i = 0; i < info->clock_num; i++)
+		isp->clks[i].id = info->clocks[i];
+
+	ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < info->clock_num; i++) {
+		if (!info->clock_rates[i])
+			continue;
+		ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);
+		if (ret) {
+			dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
+				info->clock_rates[i]);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int c3_isp_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct c3_isp_device *isp;
+	int irq;
+	int ret;
+
+	isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
+	if (!isp)
+		return -ENOMEM;
+
+	isp->info = of_device_get_match_data(dev);
+	isp->dev = dev;
+
+	isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
+	if (IS_ERR(isp->base))
+		return dev_err_probe(dev, PTR_ERR(isp->base),
+				     "Failed to ioremap resource\n");
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	ret = c3_isp_cfg_clocks(isp);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to configure clocks\n");
+
+	platform_set_drvdata(pdev, isp);
+
+	pm_runtime_enable(dev);
+
+	ret = c3_isp_v4l2_register(isp);
+	if (ret)
+		goto err_runtime_disable;
+
+	ret = c3_isp_core_register(isp);
+	if (ret)
+		goto err_v4l2_unregister;
+
+	ret = c3_isp_resizers_register(isp);
+	if (ret)
+		goto err_core_unregister;
+
+	ret = c3_isp_async_nf_register(isp);
+	if (ret)
+		goto err_resizers_unregister;
+
+	ret = c3_isp_videos_register(isp);
+	if (ret)
+		goto err_nf_unregister;
+
+	ret = devm_request_irq(dev, irq,
+			       c3_isp_irq_handler, IRQF_SHARED,
+			       dev_driver_string(dev), isp);
+	if (ret)
+		goto err_streams_unregister;
+
+	mutex_init(&isp->lock);
+
+	return 0;
+
+err_streams_unregister:
+	c3_isp_videos_unregister(isp);
+err_nf_unregister:
+	c3_isp_async_nf_unregister(isp);
+err_resizers_unregister:
+	c3_isp_resizers_unregister(isp);
+err_core_unregister:
+	c3_isp_core_unregister(isp);
+err_v4l2_unregister:
+	c3_isp_v4l2_unregister(isp);
+err_runtime_disable:
+	pm_runtime_disable(dev);
+	return ret;
+};
+
+static void c3_isp_remove(struct platform_device *pdev)
+{
+	struct c3_isp_device *isp = platform_get_drvdata(pdev);
+
+	mutex_destroy(&isp->lock);
+	c3_isp_videos_unregister(isp);
+	c3_isp_async_nf_unregister(isp);
+	c3_isp_core_unregister(isp);
+	c3_isp_resizers_unregister(isp);
+	c3_isp_v4l2_unregister(isp);
+	pm_runtime_disable(isp->dev);
+};
+
+static const struct c3_isp_info isp_info = {
+	.clocks = {"vapb", "isp0"},
+	.clock_rates = {0, 400000000},
+	.clock_num = 2
+};
+
+static const struct of_device_id c3_isp_of_match[] = {
+	{ .compatible = "amlogic,c3-isp",
+	  .data = &isp_info },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, c3_isp_of_match);
+
+static struct platform_driver c3_isp_driver = {
+	.probe = c3_isp_probe,
+	.remove = c3_isp_remove,
+	.driver = {
+		.name = "c3-isp",
+		.of_match_table = c3_isp_of_match,
+		.pm = &c3_isp_pm_ops,
+	},
+};
+
+module_platform_driver(c3_isp_driver);
+
+MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
+MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
new file mode 100644
index 000000000000..8a6b7ce86eaf
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
@@ -0,0 +1,857 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "c3-isp-common.h"
+#include "c3-isp-regs.h"
+#include "include/uapi/c3-isp-config.h"
+
+typedef void (*block_handler)(struct c3_isp_device *isp,
+			      struct c3_isp_param_block_header *block);
+
+struct c3_isp_block_handler {
+	size_t size;
+	block_handler handler;
+};
+
+/* Hardware configuration */
+
+static void c3_isp_params_cfg_wb_change(struct c3_isp_device *isp,
+					struct c3_isp_param_block_header *block)
+{
+	struct wb_change_cfg *wb = (struct wb_change_cfg *)block;
+
+	if (!block->enabled) {
+		c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
+				   TOP_BEO_CTRL_WB_EN, false);
+		return;
+	}
+
+	c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
+			   TOP_BEO_CTRL_WB_EN, TOP_BEO_CTRL_WB_EN);
+
+	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, LSWB_WB_GAIN0_MASK,
+			   wb->wb_gain[0] << LSWB_WB_GAIN0_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0,
+			   LSWB_WB_GAIN1_MASK, wb->wb_gain[1]);
+
+	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, LSWB_WB_GAIN2_MASK,
+			   wb->wb_gain[2] << LSWB_WB_GAIN2_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1,
+			   LSWB_WB_GAIN3_MASK, wb->wb_gain[3]);
+
+	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN2,
+			   LSWB_WB_GAIN4_MASK, wb->wb_gain[4]);
+
+	c3_isp_write(isp, ISP_LSWB_WB_LIMIT0,
+		     LSWB_WB_LIMIT0(wb->wb_limit[0]) | LSWB_WB_LIMIT1(wb->wb_limit[1]));
+
+	c3_isp_write(isp, ISP_LSWB_WB_LIMIT1,
+		     LSWB_WB_LIMIT2(wb->wb_limit[2]) | LSWB_WB_LIMIT3(wb->wb_limit[3]));
+
+	c3_isp_update_bits(isp, ISP_LSWB_WB_LIMIT2, LSWB_WB_LIMIT4_MASK,
+			   wb->wb_limit[4]);
+
+	c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_BL12_GRBGI0_MASK,
+			   wb->ae_bl12_grbgi[0]);
+
+	c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_GAIN_GRBGI0_MASK,
+			   wb->ae_gain_grbgi[0] << AE_CRTL2_GAIN_GRBGI0_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_BL12_GRBGI1_MASK,
+			   wb->ae_bl12_grbgi[1]);
+
+	c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_GAIN_GRBGI1_MASK,
+			   wb->ae_gain_grbgi[1] << AE_CRTL2_GAIN_GRBGI1_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_BL12_GRBGI2_MASK,
+			   wb->ae_bl12_grbgi[2]);
+
+	c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_GAIN_GRBGI2_MASK,
+			   wb->ae_gain_grbgi[2] << AE_CRTL2_GAIN_GRBGI2_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_BL12_GRBGI3_MASK,
+			   wb->ae_bl12_grbgi[3]);
+
+	c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_GAIN_GRBGI3_MASK,
+			   wb->ae_gain_grbgi[3] << AE_CRTL2_GAIN_GRBGI3_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_BL12_GRBGI4_MASK,
+			   wb->ae_bl12_grbgi[4]);
+
+	c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_GAIN_GRBGI4_MASK,
+			   wb->ae_gain_grbgi[4] << AE_CRTL2_GAIN_GRBGI4_SHIFT);
+}
+
+static void c3_isp_params_cfg_wb_luma(struct c3_isp_device *isp,
+				      struct c3_isp_param_block_header *block)
+{
+	struct wb_luma_cfg *wb_luma = (struct wb_luma_cfg *)block;
+
+	if (!block->enabled)
+		return;
+
+	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_0,
+			   AWB_STAT_BLC20_GR_MASK, wb_luma->awb_stat_blc20[0]);
+	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_1,
+			   AWB_STAT_BLC20_R_MASK, wb_luma->awb_stat_blc20[1]);
+	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_2,
+			   AWB_STAT_BLC20_B_MASK, wb_luma->awb_stat_blc20[2]);
+	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_3,
+			   AWB_STAT_BLC20_GB_MASK, wb_luma->awb_stat_blc20[3]);
+
+	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_0,
+			   AWB_STAT_GAIN10_GR_MASK, wb_luma->awb_stat_gain10[0]);
+	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_1,
+			   AWB_STAT_GAIN10_R_MASK, wb_luma->awb_stat_gain10[1]);
+	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_2,
+			   AWB_STAT_GAIN10_B_MASK, wb_luma->awb_stat_gain10[2]);
+	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_3,
+			   AWB_STAT_GAIN10_GB_MASK, wb_luma->awb_stat_gain10[3]);
+
+	c3_isp_write(isp, ISP_AWB_STAT_SATUR_CTRL,
+		     AWB_STAT_SATUR_LOW(wb_luma->awb_stat_satur_low) |
+		     AWB_STAT_SATUR_HIGH(wb_luma->awb_stat_satur_high));
+}
+
+static void c3_isp_params_cfg_wb_triangle(struct c3_isp_device *isp,
+					  struct c3_isp_param_block_header *block)
+{
+	struct wb_triangle_cfg *wb = (struct wb_triangle_cfg *)block;
+
+	if (!block->enabled)
+		return;
+
+	c3_isp_update_bits(isp, ISP_AWB_STAT_CTRL2, AWB_STAT_SATUR_VALID_MASK,
+			   wb->awb_stat_satur_vald);
+
+	c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MIN_MASK,
+			   wb->awb_stat_rg_min);
+	c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MAX_MASK,
+			   wb->awb_stat_rg_max << AWB_STAT_RG_MAX_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MIN_MASK,
+			   wb->awb_stat_bg_min);
+	c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MAX_MASK,
+			   wb->awb_stat_bg_max << AWB_STAT_BG_MAX_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_LOW_MASK,
+			   wb->awb_stat_rg_low);
+	c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_HIGH_MASK,
+			   wb->awb_stat_rg_high << AWB_STAT_RG_HIGH_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_LOW_MASK,
+			   wb->awb_stat_bg_low);
+	c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_HIGH_MASK,
+			   wb->awb_stat_bg_high << AWB_STAT_BG_HIGH_SHIFT);
+}
+
+static void c3_isp_params_cfg_awb_stats(struct c3_isp_device *isp,
+					struct c3_isp_param_block_header *block)
+{
+	struct awb_stats_cfg *awb_stats = (struct awb_stats_cfg *)block;
+	u32 *weight = awb_stats->awb_stat_blk_weight;
+	int idx_base;
+	int group;
+	int i;
+
+	if (!block->enabled)
+		return;
+
+	c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AWB_STAT_SWITCH_MASK,
+			   awb_stats->awb_stat_switch << TOP_3A_AWB_STAT_SWITCH_SHIFT);
+
+	/* Calculate the group number */
+	group = AWB_BLOCK_WT_NUM / AWB_BLK_WT_DATA_NUM_OF_GROUP;
+
+	/* Set the weight address to 0 position */
+	c3_isp_write(isp, ISP_AWB_BLK_WT_ADDR, 0);
+	for (i = 0; i < group; i++) {
+		idx_base = i * AWB_BLK_WT_DATA_NUM_OF_GROUP;
+		c3_isp_write(isp, ISP_AWB_BLK_WT_DATA,
+			     AWB_BLK_WT_DATA0(weight[idx_base + 0]) |
+			     AWB_BLK_WT_DATA1(weight[idx_base + 1]) |
+			     AWB_BLK_WT_DATA2(weight[idx_base + 2]) |
+			     AWB_BLK_WT_DATA3(weight[idx_base + 3]) |
+			     AWB_BLK_WT_DATA4(weight[idx_base + 4]) |
+			     AWB_BLK_WT_DATA5(weight[idx_base + 5]) |
+			     AWB_BLK_WT_DATA6(weight[idx_base + 6]) |
+			     AWB_BLK_WT_DATA7(weight[idx_base + 7]));
+	}
+}
+
+static void c3_isp_params_cfg_ae_stats(struct c3_isp_device *isp,
+				       struct c3_isp_param_block_header *block)
+{
+	struct ae_stats_cfg *ae_stats = (struct ae_stats_cfg *)block;
+	u32 *weight = ae_stats->ae_stat_blk_weight;
+	int idx_base;
+	int group;
+	int i;
+
+	if (!block->enabled)
+		return;
+
+	c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AE_STAT_SWITCH_MASK,
+			   ae_stats->ae_stat_switch << TOP_3A_AE_STAT_SWITCH_SHIFT);
+
+	/* Calculate the group number */
+	group = AE_BLOCK_WT_NUM / AE_BLK_WT_DATA_NUM_OF_GROUP;
+
+	/* Set the weight address to 0 position */
+	c3_isp_write(isp, ISP_AE_BLK_WT_ADDR, 0);
+	for (i = 0; i < group; i++) {
+		idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
+		c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
+			     AE_BLK_WT_DATA0(weight[idx_base + 0]) |
+			     AE_BLK_WT_DATA1(weight[idx_base + 1]) |
+			     AE_BLK_WT_DATA2(weight[idx_base + 2]) |
+			     AE_BLK_WT_DATA3(weight[idx_base + 3]) |
+			     AE_BLK_WT_DATA4(weight[idx_base + 4]) |
+			     AE_BLK_WT_DATA5(weight[idx_base + 5]) |
+			     AE_BLK_WT_DATA6(weight[idx_base + 6]) |
+			     AE_BLK_WT_DATA7(weight[idx_base + 7]));
+	}
+
+	/* Write the last weight data */
+	idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
+	c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
+		     AE_BLK_WT_DATA0(weight[idx_base + 0]) |
+		     AE_BLK_WT_DATA1(weight[idx_base + 1]) |
+		     AE_BLK_WT_DATA2(weight[idx_base + 2]) |
+		     AE_BLK_WT_DATA3(weight[idx_base + 3]) |
+		     AE_BLK_WT_DATA4(weight[idx_base + 4]) |
+		     AE_BLK_WT_DATA5(weight[idx_base + 5]) |
+		     AE_BLK_WT_DATA6(weight[idx_base + 6]));
+}
+
+static void c3_isp_params_cfg_af_stats(struct c3_isp_device *isp,
+				       struct c3_isp_param_block_header *block)
+{
+	struct af_stats_cfg *af_stats = (struct af_stats_cfg *)block;
+
+	if (!block->enabled)
+		return;
+
+	c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AF_STAT_SWITCH_MASK,
+			   af_stats->af_stat_switch << TOP_3A_AF_STAT_SWITCH_SHIFT);
+}
+
+static void c3_isp_params_cfg_pst_gamma(struct c3_isp_device *isp,
+					struct c3_isp_param_block_header *block)
+{
+	struct pst_gamma_cfg *gamma = (struct pst_gamma_cfg *)block;
+	int idx_base;
+	int i, j;
+
+	if (!block->enabled) {
+		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, false);
+		return;
+	}
+
+	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, TOP_BED_GAMMA_EN);
+
+	for (j = 0; j < GAMMA_LUT_GROUP_NUM; j++) {
+		c3_isp_write(isp, ISP_PST_GAMMA_LUT_ADDR, PST_GAMMA_LUT_ADDR(j));
+
+		/* Calculate the block number */
+		for (i = 0; i < GAMMA_LUT_POINT_NUM / GAMMA_LUT_DATA_NUM_OF_GROUP; i++) {
+			idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
+			c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
+				     PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]) |
+				     PST_GAMMA_LUT_DATA1(gamma->pst_gamma_lut[j][idx_base + 1]));
+		}
+
+		/* Write the last one lut data of group j */
+		idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
+		c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
+			     PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]));
+	}
+}
+
+static void c3_isp_params_cfg_dmsc(struct c3_isp_device *isp,
+				   struct c3_isp_param_block_header *block)
+{
+	if (!block->enabled) {
+		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, false);
+		return;
+	}
+
+	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, TOP_BED_DMSC_EN);
+}
+
+/* Configure 4 x 3 ccm matrix */
+static void c3_isp_params_cfg_ccm(struct c3_isp_device *isp,
+				  struct c3_isp_param_block_header *block)
+{
+	struct ccm_cfg *ccm = (struct ccm_cfg *)block;
+
+	if (!block->enabled) {
+		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, false);
+		return;
+	}
+
+	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, TOP_BED_CCM_EN);
+
+	c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_00_MASK,
+			   ccm->ccm_4x3matrix[0][0]);
+	c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_01_MASK,
+			   ccm->ccm_4x3matrix[0][1] << CCM_MTX_01_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_02_MASK,
+			   ccm->ccm_4x3matrix[0][2]);
+	c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_03_MASK,
+			   ccm->ccm_4x3matrix[0][3] << CCM_MTX_03_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_10_MASK,
+			   ccm->ccm_4x3matrix[1][0]);
+	c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_11_MASK,
+			   ccm->ccm_4x3matrix[1][1] << CCM_MTX_11_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_12_MASK,
+			   ccm->ccm_4x3matrix[1][2]);
+	c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_13_MASK,
+			   ccm->ccm_4x3matrix[1][3] << CCM_MTX_13_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_20_MASK,
+			   ccm->ccm_4x3matrix[2][0]);
+	c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_21_MASK,
+			   ccm->ccm_4x3matrix[2][1] << CCM_MTX_21_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_22_MASK,
+			   ccm->ccm_4x3matrix[2][2]);
+	c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_23_MASK,
+			   ccm->ccm_4x3matrix[2][3] << CCM_MTX_23_SHIFT);
+}
+
+/* Configure color space conversion matrix parameters */
+static void c3_isp_params_cfg_csc(struct c3_isp_device *isp,
+				  struct c3_isp_param_block_header *block)
+{
+	struct csc_cfg *csc = (struct csc_cfg *)block;
+
+	if (!block->enabled) {
+		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, false);
+		return;
+	}
+
+	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, TOP_BED_CM0_EN);
+
+	c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST0_MASK,
+			   csc->cm0_offset_inp[0]);
+	c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST1_MASK,
+			   csc->cm0_offset_inp[1] << CM0_INP_OFST1_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_CM0_INP_OFST2, CM0_INP_OFST2_MASK,
+			   csc->cm0_offset_inp[2]);
+
+	c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_00_MASK,
+			   csc->cm0_3x3matrix[0][0]);
+	c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_01_MASK,
+			   csc->cm0_3x3matrix[0][1] << CM0_MTX_01_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_02_MASK,
+			   csc->cm0_3x3matrix[0][2]);
+	c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_10_MASK,
+			   csc->cm0_3x3matrix[1][0] << CM0_MTX_10_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_11_MASK,
+			   csc->cm0_3x3matrix[1][1]);
+	c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_12_MASK,
+			   csc->cm0_3x3matrix[1][2] << CM0_MTX_12_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_20_MASK,
+			   csc->cm0_3x3matrix[2][0]);
+	c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_21_MASK,
+			   csc->cm0_3x3matrix[2][1] << CM0_MTX_21_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_MTX_22_MASK,
+			   csc->cm0_3x3matrix[2][2]);
+	c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_OFST_OUP0_MASK,
+			   csc->cm0_offset_oup[0] << CM0_OFST_OUP0_SHIFT);
+
+	c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP1_MASK,
+			   csc->cm0_offset_oup[1]);
+	c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP2_MASK,
+			   csc->cm0_offset_oup[2] << CM0_OFST_OUP2_SHIFT);
+	c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_MTX_RS_MASK,
+			   csc->cm0_3x3mtrx_rs << CM0_MTX_RS_SHIFT);
+}
+
+/* Set blc offset of each color channel */
+static void c3_isp_params_cfg_blc(struct c3_isp_device *isp,
+				  struct c3_isp_param_block_header *block)
+{
+	struct blc_cfg *blc = (struct blc_cfg *)block;
+
+	if (!block->enabled) {
+		c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN, false);
+		return;
+	}
+
+	c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN,
+			   TOP_BEO_CTRL_BLC_EN);
+
+	c3_isp_write(isp, ISP_FED_BL_OFST_GR, FED_BL_GR_OFST(blc->fe_bl_ofst[0]));
+	c3_isp_write(isp, ISP_FED_BL_OFST_R, FED_BL_R_OFST(blc->fe_bl_ofst[1]));
+	c3_isp_write(isp, ISP_FED_BL_OFST_B, FED_BL_B_OFST(blc->fe_bl_ofst[2]));
+	c3_isp_write(isp, ISP_FED_BL_OFST_GB, FED_BL_GB_OFST(blc->fe_bl_ofst[3]));
+	c3_isp_write(isp, ISP_FED_BL_OFST_IR, FED_BL_IR_OFST(blc->fe_bl_ofst[4]));
+
+	c3_isp_write(isp, ISP_LSWB_BLC_OFST0,
+		     LSWB_BLC_OFST0(blc->blc_ofst[0]) | LSWB_BLC_OFST1(blc->blc_ofst[1]));
+	c3_isp_write(isp, ISP_LSWB_BLC_OFST1,
+		     LSWB_BLC_OFST2(blc->blc_ofst[2]) | LSWB_BLC_OFST3(blc->blc_ofst[3]));
+	c3_isp_write(isp, ISP_LSWB_BLC_OFST2, LSWB_BLC_OFST4(blc->blc_ofst[4]));
+}
+
+static const struct c3_isp_block_handler c3_isp_block_handlers[] = {
+	[C3_ISP_PARAM_BLOCK_WB_CHANGE] = {
+		.size = sizeof(struct wb_change_cfg),
+		.handler = c3_isp_params_cfg_wb_change,
+	},
+	[C3_ISP_PARAM_BLOCK_WB_LUMA] = {
+		.size = sizeof(struct wb_luma_cfg),
+		.handler = c3_isp_params_cfg_wb_luma,
+	},
+	[C3_ISP_PARAM_BLOCK_WB_TRIANGLE] = {
+		.size = sizeof(struct wb_triangle_cfg),
+		.handler = c3_isp_params_cfg_wb_triangle,
+	},
+	[C3_ISP_PARAM_BLOCK_AWB_STATS] = {
+		.size = sizeof(struct awb_stats_cfg),
+		.handler = c3_isp_params_cfg_awb_stats,
+	},
+	[C3_ISP_PARAM_BLOCK_AE_STATS] = {
+		.size = sizeof(struct ae_stats_cfg),
+		.handler = c3_isp_params_cfg_ae_stats,
+	},
+	[C3_ISP_PARAM_BLOCK_AF_STATS] = {
+		.size = sizeof(struct af_stats_cfg),
+		.handler = c3_isp_params_cfg_af_stats,
+	},
+	[C3_ISP_PARAM_BLOCK_PST_GAMMA] = {
+		.size = sizeof(struct pst_gamma_cfg),
+		.handler = c3_isp_params_cfg_pst_gamma,
+	},
+	[C3_ISP_PARAM_BLOCK_DMSC] = {
+		.size = sizeof(struct dmsc_cfg),
+		.handler = c3_isp_params_cfg_dmsc,
+	},
+	[C3_ISP_PARAM_BLOCK_CCM] = {
+		.size = sizeof(struct ccm_cfg),
+		.handler = c3_isp_params_cfg_ccm,
+	},
+	[C3_ISP_PARAM_BLOCK_CSC] = {
+		.size = sizeof(struct csc_cfg),
+		.handler = c3_isp_params_cfg_csc,
+	},
+	[C3_ISP_PARAM_BLOCK_BLC] = {
+		.size = sizeof(struct blc_cfg),
+		.handler = c3_isp_params_cfg_blc,
+	},
+};
+
+static enum vb2_buffer_state
+c3_isp_params_cfg_blocks(struct c3_isp_params *params)
+{
+	struct c3_isp_params_buffer *config = params->buff->vaddr;
+	enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
+	size_t block_offset = 0;
+	size_t max_offset = 0;
+
+	if (config->total_size > C3_ISP_PARAMS_MAX_SIZE) {
+		dev_dbg(params->isp->dev, "Invalid parameters buffer size %lu\n",
+			config->total_size);
+		state = VB2_BUF_STATE_ERROR;
+		goto err_return_state;
+	}
+
+	/* Ensure config->data has a full struct c3_isp_param_block_header */
+	max_offset = config->total_size - sizeof(struct c3_isp_param_block_header);
+
+	while (block_offset <= max_offset) {
+		const struct c3_isp_block_handler *block_handler;
+		struct c3_isp_param_block_header *block;
+
+		block = (struct c3_isp_param_block_header *)
+			 &config->data[block_offset];
+
+		if (block->type >= C3_ISP_PARAM_BLOCK_SENTINEL) {
+			dev_dbg(params->isp->dev, "Invalid parameters block type\n");
+			state = VB2_BUF_STATE_ERROR;
+			goto err_return_state;
+		}
+
+		block_handler = &c3_isp_block_handlers[block->type];
+		if (block->size != block_handler->size) {
+			dev_dbg(params->isp->dev, "Invalid parameters block size\n");
+			state = VB2_BUF_STATE_ERROR;
+			goto err_return_state;
+		}
+
+		block_handler->handler(params->isp, block);
+
+		block_offset += block->size;
+	}
+
+err_return_state:
+	return state;
+}
+
+/* Initialize ISP pipeline */
+static int c3_isp_params_start(struct c3_isp_params *params)
+{
+	enum vb2_buffer_state state;
+	unsigned long flags;
+
+	/* Reset these controllers */
+	c3_isp_write(params->isp, ISP_TOP_FEO_CTRL0, TOP_FEO_CTRL0_ALL_DIS);
+	c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_0, TOP_FEO_CTRL1_0_ALL_DIS);
+	c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_1, TOP_FEO_CTRL1_1_ALL_DIS);
+	c3_isp_write(params->isp, ISP_TOP_FED_CTRL, TOP_FED_CTRL_ALL_DIS);
+	c3_isp_write(params->isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_ALL_DIS);
+	c3_isp_write(params->isp, ISP_TOP_BED_CTRL, TOP_BED_CTRL_ALL_DIS);
+
+	spin_lock_irqsave(&params->buff_lock, flags);
+
+	/* Only use the first buffer to initialize ISP */
+	params->buff = list_first_entry_or_null(&params->pending,
+						struct c3_isp_vb2_buffer, list);
+	if (!params->buff) {
+		spin_unlock_irqrestore(&params->buff_lock, flags);
+		return -EINVAL;
+	}
+
+	state = c3_isp_params_cfg_blocks(params);
+
+	spin_unlock_irqrestore(&params->buff_lock, flags);
+
+	return 0;
+}
+
+/* V4L2 video operations */
+
+static void c3_isp_params_return_buffers(struct c3_isp_params *params,
+					 enum vb2_buffer_state state)
+{
+	unsigned long flags;
+	struct c3_isp_vb2_buffer *buff;
+
+	spin_lock_irqsave(&params->buff_lock, flags);
+
+	while (!list_empty(&params->pending)) {
+		buff = list_first_entry(&params->pending,
+					struct c3_isp_vb2_buffer, list);
+		list_del(&buff->list);
+		vb2_buffer_done(&buff->vb.vb2_buf, state);
+	}
+
+	spin_unlock_irqrestore(&params->buff_lock, flags);
+}
+
+static int c3_isp_params_querycap(struct file *file, void *fh,
+				  struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
+	strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
+
+	return 0;
+}
+
+static int c3_isp_params_enum_fmt(struct file *file, void *fh,
+				  struct v4l2_fmtdesc *f)
+{
+	if (f->index)
+		return -EINVAL;
+
+	f->pixelformat = V4L2_META_FMT_C3ISP_PARAMS;
+
+	return 0;
+}
+
+static int c3_isp_params_g_fmt(struct file *file, void *fh,
+			       struct v4l2_format *f)
+{
+	struct c3_isp_params *params = video_drvdata(file);
+
+	f->fmt.meta = params->vfmt.fmt.meta;
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops isp_params_v4l2_ioctl_ops = {
+	.vidioc_querycap                = c3_isp_params_querycap,
+	.vidioc_enum_fmt_meta_out       = c3_isp_params_enum_fmt,
+	.vidioc_g_fmt_meta_out          = c3_isp_params_g_fmt,
+	.vidioc_s_fmt_meta_out          = c3_isp_params_g_fmt,
+	.vidioc_try_fmt_meta_out        = c3_isp_params_g_fmt,
+	.vidioc_reqbufs                 = vb2_ioctl_reqbufs,
+	.vidioc_querybuf                = vb2_ioctl_querybuf,
+	.vidioc_qbuf                    = vb2_ioctl_qbuf,
+	.vidioc_expbuf                  = vb2_ioctl_expbuf,
+	.vidioc_dqbuf                   = vb2_ioctl_dqbuf,
+	.vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
+	.vidioc_create_bufs             = vb2_ioctl_create_bufs,
+	.vidioc_streamon                = vb2_ioctl_streamon,
+	.vidioc_streamoff               = vb2_ioctl_streamoff,
+	.vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_file_operations isp_params_v4l2_fops = {
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release,
+	.poll = vb2_fop_poll,
+	.unlocked_ioctl = video_ioctl2,
+	.mmap = vb2_fop_mmap,
+};
+
+static int c3_isp_params_vb2_queue_setup(struct vb2_queue *q,
+					 unsigned int *num_buffers,
+					 unsigned int *num_planes,
+					 unsigned int sizes[],
+					 struct device *alloc_devs[])
+{
+	if (*num_planes) {
+		if (*num_planes != 1)
+			return -EINVAL;
+
+		if (sizes[0] < sizeof(struct c3_isp_params_buffer))
+			return -EINVAL;
+	} else {
+		*num_planes = 1;
+		sizes[0] = sizeof(struct c3_isp_params_buffer);
+	}
+
+	return 0;
+}
+
+static void c3_isp_params_vb2_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+	struct c3_isp_vb2_buffer *buf =
+			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
+	struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned long flags;
+
+	spin_lock_irqsave(&params->buff_lock, flags);
+
+	list_add_tail(&buf->list, &params->pending);
+
+	spin_unlock_irqrestore(&params->buff_lock, flags);
+}
+
+static int c3_isp_params_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+	struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned int size = params->vfmt.fmt.meta.buffersize;
+
+	if (vb2_plane_size(vb, 0) < size) {
+		dev_err(params->isp->dev,
+			"User buffer too small (%ld < %u)\n",
+			vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+
+	vb2_set_plane_payload(vb, 0, size);
+
+	return 0;
+}
+
+static int c3_isp_params_vb2_buf_init(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+	struct c3_isp_vb2_buffer *buf =
+		container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
+	struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
+
+	buf->vaddr = vb2_plane_vaddr(vb, 0);
+	buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+	memset(buf->vaddr, 0, params->vfmt.fmt.meta.buffersize);
+
+	return 0;
+}
+
+static int c3_isp_params_vb2_start_streaming(struct vb2_queue *q,
+					     unsigned int count)
+{
+	struct c3_isp_params *params = vb2_get_drv_priv(q);
+	int ret;
+
+	guard(mutex)(&params->isp->lock);
+
+	ret = pm_runtime_resume_and_get(params->isp->dev);
+	if (ret)
+		return ret;
+
+	ret = video_device_pipeline_start(&params->vdev, &params->isp->pipe);
+	if (ret) {
+		dev_err(params->isp->dev,
+			"Failed to start params pipeline: %d\n", ret);
+		goto err_pm_put;
+	}
+
+	if (c3_isp_pipeline_ready(params->isp)) {
+		ret = v4l2_subdev_enable_streams(&params->isp->core.sd,
+						 C3_ISP_CORE_PAD_SOURCE_VIDEO,
+						 BIT(0));
+		if (ret)
+			goto err_pipeline_stop;
+	}
+
+	c3_isp_params_start(params);
+
+	return 0;
+
+err_pipeline_stop:
+	video_device_pipeline_stop(&params->vdev);
+err_pm_put:
+	pm_runtime_put(params->isp->dev);
+	c3_isp_params_return_buffers(params, VB2_BUF_STATE_QUEUED);
+	return ret;
+}
+
+static void c3_isp_params_vb2_stop_streaming(struct vb2_queue *q)
+{
+	struct c3_isp_params *params = vb2_get_drv_priv(q);
+
+	guard(mutex)(&params->isp->lock);
+
+	c3_isp_params_return_buffers(params, VB2_BUF_STATE_ERROR);
+
+	if (params->isp->pipe.start_count == 1)
+		v4l2_subdev_disable_streams(&params->isp->core.sd,
+					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
+					    BIT(0));
+
+	video_device_pipeline_stop(&params->vdev);
+	pm_runtime_put(params->isp->dev);
+}
+
+static const struct vb2_ops isp_params_vb2_ops = {
+	.queue_setup = c3_isp_params_vb2_queue_setup,
+	.buf_queue = c3_isp_params_vb2_buf_queue,
+	.buf_prepare = c3_isp_params_vb2_buf_prepare,
+	.buf_init = c3_isp_params_vb2_buf_init,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.start_streaming = c3_isp_params_vb2_start_streaming,
+	.stop_streaming = c3_isp_params_vb2_stop_streaming,
+};
+
+int c3_isp_params_register(struct c3_isp_device *isp)
+{
+	struct c3_isp_params *params = &isp->params;
+	struct video_device *vdev = &params->vdev;
+	struct vb2_queue *vb2_q = &params->vb2_q;
+	int ret;
+
+	memset(params, 0, sizeof(*params));
+	params->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_PARAMS;
+	params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
+	params->isp = isp;
+	INIT_LIST_HEAD(&params->pending);
+	spin_lock_init(&params->buff_lock);
+	mutex_init(&params->lock);
+
+	snprintf(vdev->name, sizeof(vdev->name), "isp-params");
+	vdev->fops = &isp_params_v4l2_fops;
+	vdev->ioctl_ops = &isp_params_v4l2_ioctl_ops;
+	vdev->v4l2_dev = &isp->v4l2_dev;
+	vdev->lock = &params->lock;
+	vdev->minor = -1;
+	vdev->queue = vb2_q;
+	vdev->release = video_device_release_empty;
+	vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING;
+	vdev->vfl_dir = VFL_DIR_TX;
+	video_set_drvdata(vdev, params);
+
+	vb2_q->drv_priv = params;
+	vb2_q->mem_ops = &vb2_dma_contig_memops;
+	vb2_q->ops = &isp_params_vb2_ops;
+	vb2_q->type = V4L2_BUF_TYPE_META_OUTPUT;
+	vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
+	vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
+	vb2_q->dev = isp->dev;
+	vb2_q->lock = &params->lock;
+	vb2_q->min_queued_buffers = 1;
+
+	ret = vb2_queue_init(vb2_q);
+	if (ret)
+		goto err_detroy;
+
+	params->pad.flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_pads_init(&vdev->entity, 1, &params->pad);
+	if (ret)
+		goto err_queue_release;
+
+	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+	if (ret < 0) {
+		dev_err(isp->dev,
+			"Failed to register %s: %d\n", vdev->name, ret);
+		goto err_entity_cleanup;
+	}
+
+	return 0;
+
+err_entity_cleanup:
+	media_entity_cleanup(&vdev->entity);
+err_queue_release:
+	vb2_queue_release(vb2_q);
+err_detroy:
+	mutex_destroy(&params->lock);
+	return ret;
+}
+
+void c3_isp_params_unregister(struct c3_isp_device *isp)
+{
+	struct c3_isp_params *params = &isp->params;
+
+	vb2_queue_release(&params->vb2_q);
+	media_entity_cleanup(&params->vdev.entity);
+	video_unregister_device(&params->vdev);
+	mutex_destroy(&params->lock);
+}
+
+int c3_isp_params_done(struct c3_isp_device *isp)
+{
+	struct c3_isp_params *params = &isp->params;
+	enum vb2_buffer_state state;
+	unsigned long flags;
+
+	spin_lock_irqsave(&params->buff_lock, flags);
+
+	params->buff = list_first_entry_or_null(&params->pending,
+						struct c3_isp_vb2_buffer, list);
+	if (!params->buff) {
+		spin_unlock_irqrestore(&params->buff_lock, flags);
+		return -EINVAL;
+	}
+
+	list_del(&params->buff->list);
+
+	state = c3_isp_params_cfg_blocks(params);
+
+	params->buff->vb.sequence = params->isp->frm_sequence;
+	params->buff->vb.vb2_buf.timestamp = ktime_get();
+	params->buff->vb.field = V4L2_FIELD_NONE;
+	vb2_buffer_done(&params->buff->vb.vb2_buf, state);
+
+	spin_unlock_irqrestore(&params->buff_lock, flags);
+
+	return 0;
+}
diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
new file mode 100644
index 000000000000..de1938f7c354
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
@@ -0,0 +1,683 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef __C3_ISP_REGS_H__
+#define __C3_ISP_REGS_H__
+
+#define ISP_TOP_INPUT_SIZE                       0x0000
+#define TOP_INPUT_HSIZE(x)                       (((x) & 0xffff) << 16)
+#define TOP_INPUT_VSIZE(x)                       ((x) & 0xffff)
+
+#define ISP_TOP_FRM_SIZE                         0x0004
+#define TOP_FRM_CORE_IHSIZE(x)                   (((x) & 0xffff) << 16)
+#define TOP_FRM_CORE_IVSIZE(x)                   ((x) & 0xffff)
+
+#define ISP_TOP_HOLD_SIZE                        0x0008
+#define TOP_HOLD_HSIZE_MASK                      GENMASK(31, 16)
+#define TOP_HOLD_HSIZE_SHIFT                     16
+
+#define ISP_TOP_PATH_EN                          0x0010
+#define TOP_DISP_EN(x)                           BIT((x))
+#define TOP_WRMIF_EN(x)                          BIT((x) + 8)
+
+#define ISP_TOP_PATH_SEL                         0x0014
+#define TOP_DATA_PATH_MASK                       GENMASK(18, 16)
+#define TOP_PATH_MIPI_TO_CORE                    BIT(16)
+
+#define ISP_TOP_IRQ_EN                           0x0080
+#define TOP_IRQ_FRAME_DONE                       BIT(0)
+#define TOP_IRQ_STATS_ERR                        BIT(5)
+
+#define ISP_TOP_IRQ_CLR                          0x0084
+#define ISP_TOP_IRQ_LINE_THRD                    0x0088
+#define TOP_IRQ_DIN_HSYNC                        BIT(16)
+
+#define ISP_TOP_RO_IRQ_STAT                      0x01c4
+#define ISP_TOP_MODE_CTRL                        0x0400
+#define ISP_TOP_FEO_CTRL0                        0x040c
+#define TOP_FEO_CTRL0_ALL_DIS                    0
+#define TOP_FEO_INPUT_FMT_EN                     BIT(8)
+
+#define ISP_TOP_FEO_CTRL1_0                      0x0410
+#define TOP_FEO_CTRL1_0_ALL_DIS                  0
+
+#define ISP_TOP_FEO_CTRL1_1                      0x0414
+#define TOP_FEO_CTRL1_1_ALL_DIS                  0
+
+#define ISP_TOP_FED_CTRL                         0x0418
+#define TOP_FED_CTRL_ALL_DIS                     0
+
+#define ISP_TOP_BEO_CTRL                         0x041c
+#define TOP_BEO_CTRL_ALL_DIS                     0
+#define TOP_BEO_CTRL_GTM_EN                      BIT(2)
+#define TOP_BEO_CTRL_WB_EN                       BIT(6)
+#define TOP_BEO_CTRL_BLC_EN                      BIT(7)
+#define TOP_BEO_CTRL_IDGAIN_EN                   BIT(8)
+#define TOP_BEO_CTRL_EOTF_EN                     BIT(9)
+
+#define ISP_TOP_BED_CTRL                         0x0420
+#define TOP_BED_CTRL_ALL_DIS                     0
+#define TOP_BED_CM0_EN                           BIT(14)
+#define TOP_BED_GAMMA_EN                         BIT(16)
+#define TOP_BED_CCM_EN                           BIT(18)
+#define TOP_BED_DMSC_EN                          BIT(19)
+
+#define ISP_TOP_3A_STAT_CRTL                     0x0424
+#define TOP_3A_AE_STAT_EN                        BIT(0)
+#define TOP_3A_AWB_STAT_EN                       BIT(1)
+#define TOP_3A_AF_STAT_EN                        BIT(2)
+#define TOP_3A_AWB_STAT_SWITCH_MASK              GENMASK(6, 4)
+#define TOP_3A_AWB_STAT_SWITCH_SHIFT             4
+#define TOP_3A_AWB_STAT_SWITCH_BEFORE_WB         2
+
+#define TOP_3A_AE_STAT_SWITCH_MASK               GENMASK(9, 8)
+#define TOP_3A_AE_STAT_SWITCH_SHIFT              8
+#define TOP_3A_AE_STAT_SWITCH_FROM_MLS           1
+#define TOP_3A_AF_STAT_SWITCH_MASK               GENMASK(13, 12)
+#define TOP_3A_AF_STAT_SWITCH_SHIFT              12
+#define TOP_3A_AF_STAT_SWITCH_FROM_SNR            0
+
+#define ISP_FED_BL_OFST_GR                       0x2018
+#define FED_BL_GR_OFST(x)                        ((x) & 0x1fffff)
+
+#define ISP_FED_BL_OFST_R                        0x201c
+#define FED_BL_R_OFST(x)                         ((x) & 0x1fffff)
+
+#define ISP_FED_BL_OFST_B                        0x2020
+#define FED_BL_B_OFST(x)                         ((x) & 0x1fffff)
+
+#define ISP_FED_BL_OFST_GB                       0x2024
+#define FED_BL_GB_OFST(x)                        ((x) & 0x1fffff)
+
+#define ISP_FED_BL_OFST_IR                       0x2028
+#define FED_BL_IR_OFST(x)                        ((x) & 0x1fffff)
+
+#define ISP_LSWB_BLC_OFST0                       0x4028
+#define LSWB_BLC_OFST1_MASK                      GENMASK(15, 0)
+#define LSWB_BLC_OFST1(x)                        ((x) & 0xffff)
+#define LSWB_BLC_OFST1_EXT(x)                    ((x) & 0xffff)
+#define LSWB_BLC_OFST0_MASK                      GENMASK(31, 16)
+#define LSWB_BLC_OFST0(x)                        (((x) & 0xffff) << 16)
+#define LSWB_BLC_OFST0_EXT(x)                    (((x) >> 16) & 0xffff)
+
+#define ISP_LSWB_BLC_OFST1                       0x402c
+#define LSWB_BLC_OFST3_MASK                      GENMASK(15, 0)
+#define LSWB_BLC_OFST3(x)                        ((x) & 0xffff)
+#define LSWB_BLC_OFST3_EXT(x)                    ((x) & 0xffff)
+#define LSWB_BLC_OFST2_MASK                      GENMASK(31, 16)
+#define LSWB_BLC_OFST2(x)                        (((x) & 0xffff) << 16)
+#define LSWB_BLC_OFST2_EXT(x)                    (((x) >> 16) & 0xffff)
+
+#define ISP_LSWB_BLC_OFST2                       0x4030
+#define LSWB_BLC_OFST4_MASK                      GENMASK(15, 0)
+#define LSWB_BLC_OFST4(x)                        ((x) & 0xffff)
+#define LSWB_BLC_OFST4_EXT(x)                    ((x) & 0xffff)
+
+#define ISP_LSWB_BLC_PHSOFST                     0x4034
+#define LSWB_BLC_YPHS_OFST_MASK                  GENMASK(1, 0)
+#define LSWB_BLC_XPHS_OFST_MASK                  GENMASK(3, 2)
+#define LSWB_BLC_XPHS_OFST_SHIFT                 2
+
+#define ISP_LSWB_WB_GAIN0                        0x4038
+#define LSWB_WB_GAIN1_MASK                       GENMASK(11, 0)
+#define LSWB_WB_GAIN0_MASK                       GENMASK(27, 16)
+#define LSWB_WB_GAIN0_SHIFT                      16
+
+#define ISP_LSWB_WB_GAIN1                        0x403c
+#define LSWB_WB_GAIN3_MASK                       GENMASK(11, 0)
+#define LSWB_WB_GAIN2_MASK                       GENMASK(27, 16)
+#define LSWB_WB_GAIN2_SHIFT                      16
+
+#define ISP_LSWB_WB_GAIN2                        0x4040
+#define LSWB_WB_GAIN4_MASK                       GENMASK(11, 0)
+
+#define ISP_LSWB_WB_LIMIT0                       0x4044
+#define LSWB_WB_LIMIT1(x)                        ((x) & 0xffff)
+#define LSWB_WB_LIMIT0(x)                        (((x) & 0xffff) << 16)
+
+#define ISP_LSWB_WB_LIMIT1                       0x4048
+#define LSWB_WB_LIMIT3(x)                        ((x) & 0xffff)
+#define LSWB_WB_LIMIT2(x)                        (((x) & 0xffff) << 16)
+
+#define ISP_LSWB_WB_LIMIT2                       0x404c
+#define LSWB_WB_LIMIT4_MASK                      GENMASK(15, 0)
+
+#define ISP_LSWB_WB_PHSOFST                      0x4050
+#define LSWB_WB_YPHS_OFST_MASK                   GENMASK(1, 0)
+#define LSWB_WB_XPHS_OFST_MASK                   GENMASK(3, 2)
+#define LSWB_WB_XPHS_OFST_SHIFT                  2
+
+#define ISP_LSWB_LNS_PHSOFST                     0x4054
+#define LSWB_LNS_YPHS_OFST_MASK                  GENMASK(1, 0)
+#define LSWB_LNS_XPHS_OFST_MASK                  GENMASK(3, 2)
+#define LSWB_LNS_XPHS_OFST_SHIFT                 2
+
+#define ISP_DMS_COMMON_PARAM0                    0x5000
+#define DMS_COMMON_YPHS_OFST_MASK                GENMASK(1, 0)
+#define DMS_COMMON_XPHS_OFST_MASK                GENMASK(3, 2)
+#define DMS_COMMON_XPHS_OFST_SHIFT               2
+
+#define ISP_CM0_INP_OFST01                       0x6040
+#define CM0_INP_OFST0_MASK                       GENMASK(12, 0)
+#define CM0_INP_OFST1_MASK                       GENMASK(28, 16)
+#define CM0_INP_OFST1_SHIFT                      16
+
+#define ISP_CM0_INP_OFST2                        0x6044
+#define CM0_INP_OFST2_MASK                       GENMASK(12, 0)
+
+#define ISP_CM0_COEF00_01                        0x6048
+#define CM0_MTX_00_MASK                          GENMASK(12, 0)
+#define CM0_MTX_01_MASK                          GENMASK(28, 16)
+#define CM0_MTX_01_SHIFT                         16
+
+#define ISP_CM0_COEF02_10                        0x604c
+#define CM0_MTX_02_MASK                          GENMASK(12, 0)
+#define CM0_MTX_10_MASK                          GENMASK(28, 16)
+#define CM0_MTX_10_SHIFT                         16
+
+#define ISP_CM0_COEF11_12                        0x6050
+#define CM0_MTX_11_MASK                          GENMASK(12, 0)
+#define CM0_MTX_12_MASK                          GENMASK(28, 16)
+#define CM0_MTX_12_SHIFT                         16
+
+#define ISP_CM0_COEF20_21                        0x6054
+#define CM0_MTX_20_MASK                          GENMASK(12, 0)
+#define CM0_MTX_21_MASK                          GENMASK(28, 16)
+#define CM0_MTX_21_SHIFT                         16
+
+#define ISP_CM0_COEF22_OUP_OFST0                 0x6058
+#define CM0_MTX_22_MASK                          GENMASK(12, 0)
+#define CM0_OFST_OUP0_MASK                       GENMASK(28, 16)
+#define CM0_OFST_OUP0_SHIFT                      16
+
+#define ISP_CM0_OUP_OFST12_RS                    0x605c
+#define CM0_OFST_OUP1_MASK                       GENMASK(12, 0)
+#define CM0_OFST_OUP2_MASK                       GENMASK(28, 16)
+#define CM0_OFST_OUP2_SHIFT                      16
+#define CM0_MTX_RS_MASK                          GENMASK(31, 30)
+#define CM0_MTX_RS_SHIFT                         30
+
+#define ISP_CCM_MTX_00_01                        0x6098
+#define CCM_MTX_00_MASK                          GENMASK(12, 0)
+#define CCM_MTX_01_MASK                          GENMASK(28, 16)
+#define CCM_MTX_01_SHIFT                         16
+
+#define ISP_CCM_MTX_02_03                        0x609c
+#define CCM_MTX_02_MASK                          GENMASK(12, 0)
+#define CCM_MTX_03_MASK                          GENMASK(28, 16)
+#define CCM_MTX_03_SHIFT                         16
+
+#define ISP_CCM_MTX_10_11                        0x60A0
+#define CCM_MTX_10_MASK                          GENMASK(12, 0)
+#define CCM_MTX_11_MASK                          GENMASK(28, 16)
+#define CCM_MTX_11_SHIFT                         16
+
+#define ISP_CCM_MTX_12_13                        0x60A4
+#define CCM_MTX_12_MASK                          GENMASK(12, 0)
+#define CCM_MTX_13_MASK                          GENMASK(28, 16)
+#define CCM_MTX_13_SHIFT                         16
+
+#define ISP_CCM_MTX_20_21                        0x60A8
+#define CCM_MTX_20_MASK                          GENMASK(12, 0)
+#define CCM_MTX_21_MASK                          GENMASK(28, 16)
+#define CCM_MTX_21_SHIFT                         16
+
+#define ISP_CCM_MTX_22_23_RS                     0x60Ac
+#define CCM_MTX_22_MASK                          GENMASK(12, 0)
+#define CCM_MTX_23_MASK                          GENMASK(28, 16)
+#define CCM_MTX_23_SHIFT                         16
+
+#define ISP_PST_GAMMA_MODE                       0x60C0
+#define PST_GAMMA_MODE                           BIT(0)
+#define PST_GAMMA_MODE_EXT(x)                    ((x) & 0x1)
+
+#define ISP_PST_GAMMA_LUT_ADDR                   0x60cc
+#define PST_GAMMA_LUT_ADDR(x)                    ((x) << 7)
+
+#define ISP_PST_GAMMA_LUT_DATA                   0x60d0
+#define PST_GAMMA_LUT_DATA0(x)                   ((x) & 0xffff)
+#define PST_GAMMA_LUT_DATA1(x)                   (((x) & 0xffff) << 16)
+
+#define DISP0_TOP_TOP_CTRL                       0x8000
+#define DISP_CRP2_EN                             BIT(5)
+
+#define DISP0_TOP_CRP2_START                     0x8004
+#define DISP_CRP2_VSTART_MASK                    GENMASK(15, 0)
+#define DISP_CRP2_VSTART(x)                      ((x) & 0xffff)
+#define DISP_CRP2_HSTART_MASK                    GENMASK(31, 16)
+#define DISP_CRP2_HSTART(x)                      (((x) & 0xffff) << 16)
+
+#define DISP0_TOP_CRP2_SIZE                      0x8008
+#define DISP_CRP2_VSIZE_MASK                     GENMASK(15, 0)
+#define DISP_CRP2_VSIZE(x)                       ((x) & 0xffff)
+#define DISP_CRP2_HSIZE_MASK                     GENMASK(31, 16)
+#define DISP_CRP2_HSIZE(x)                       (((x) & 0xffff) << 16)
+
+#define DISP0_TOP_OUT_SIZE                       0x800c
+#define DISP_OUT_VSIZE_MASK                      GENMASK(12, 0)
+#define DISP_OUT_HSIZE_MASK                      GENMASK(28, 16)
+#define DISP_OUT_HSIZE_SHIFT                     16
+
+#define ISP_DISP0_TOP_IN_SIZE                    0x804c
+#define DISP_TOP_IN_VSIZE(x)                     ((x) & 0x1fff)
+#define DISP_TOP_IN_HSIZE(x)                     (((x) & 0x1fff) << 16)
+
+#define DISP0_PPS_SCALE_EN                       0x8200
+#define PPS_VSC_TAP_NUM_MASK                     GENMASK(3, 0)
+#define PPS_HSC_TAP_NUM_MASK                     GENMASK(7, 4)
+#define PPS_HSC_TAP_NUM_SHIFT                    4
+#define PPS_HSC_TAP_NUM_INIT                     4
+#define PPS_PREVSC_FLT_NUM_MASK                  GENMASK(11, 8)
+#define PPS_PREVSC_FLT_NUM_SHIFT                 8
+#define PPS_PREHSC_FLT_NUM_MASK                  GENMASK(15, 12)
+#define PPS_PREHSC_FLT_NUM_SHIFT                 12
+#define PPS_PREHSC_FLT_NUM_INIT                  8
+#define PPS_PREVSC_RATE_MASK                     GENMASK(17, 16)
+#define PPS_PREVSC_RATE_SHIFT                    16
+#define PPS_PREHSC_RATE_MASK                     GENMASK(19, 18)
+#define PPS_PREHSC_RATE_SHIFT                    18
+#define PPS_HSC_EN_MASK                          BIT(20)
+#define PPS_HSC_EN_SHIFT                         20
+#define PPS_VSC_EN_MASK                          BIT(21)
+#define PPS_VSC_EN_SHIFT                         21
+#define PPS_PREVSC_EN_MASK                       BIT(22)
+#define PPS_PREVSC_EN_SHIFT                      22
+#define PPS_PREHSC_EN_MASK                       BIT(23)
+#define PPS_PREHSC_EN_SHIFT                      23
+#define PPS_HSC_NOR_RS_BITS_MASK                 GENMASK(27, 24)
+#define PPS_HSC_NOR_RS_BITS_SHIFT                24
+#define PPS_HSC_NOR_RS_BITS_INIT                 9
+#define PPS_VSC_NOR_RS_BITS_MASK                 GENMASK(31, 28)
+#define PPS_VSC_NOR_RS_BITS_SHIFT                28
+#define PPS_VSC_NOR_RS_BITS_INIT                 9
+
+#define DISP0_PPS_PRE_HSCALE_COEF_0              0x8204
+#define PPS_PREHSC_LUMA_COEF0_MASK               GENMASK(9, 0)
+#define PPS_PREHSC_LUMA_COEF0_INIT               128
+#define PPS_PREHSC_LUMA_COEF1_MASK               GENMASK(25, 16)
+#define PPS_PREHSC_LUMA_COEF1_SHIFT              16
+#define PPS_PREHSC_LUMA_COEF1_INIT               128
+
+#define DISP0_PPS_PRE_HSCALE_COEF_1              0x8208
+#define PPS_PREHSC_LUMA_COEF2_MASK               GENMASK(9, 0)
+#define PPS_PREHSC_LUMA_COEF2_INIT               32
+#define PPS_PREHSC_LUMA_COEF3_MASK               GENMASK(25, 16)
+#define PPS_PREHSC_LUMA_COEF3_SHIFT              16
+#define PPS_PREHSC_LUMA_COEF3_INIT               32
+
+#define DISP0_PPS_VSC_START_PHASE_STEP           0x8224
+#define PPS_VSC_FRACTION_PART_MASK               GENMASK(23, 0)
+#define PPS_VSC_INTEGER_PART_MASK                GENMASK(27, 24)
+#define PPS_VSC_INTEGER_PART_SHIFT               24
+
+#define DISP0_PPS_HSC_START_PHASE_STEP           0x8230
+#define PPS_HSC_FRACTION_PART_MASK               GENMASK(23, 0)
+#define PPS_HSC_INTEGER_PART_MASK                GENMASK(27, 24)
+#define PPS_HSC_INTEGER_PART_SHIFT               24
+
+#define DISP0_PPS_444TO422                       0x823c
+#define PPS_444TO422_EN_MASK                     BIT(0)
+
+#define ISP_SCALE0_COEF_IDX_LUMA                 0x8240
+#define SCALE_LUMA_COEF_S11_MODE                 BIT(9)
+#define SCALE_LUMA_CTYPE(x)                      (((x) & 0x7) << 10)
+
+#define ISP_SCALE0_COEF_LUMA                     0x8244
+#define SCALE_COEF_LUMA_DATA1(x)                 ((x) & 0x7ff)
+#define SCALE_COEF_LUMA_DATA0(x)                 (((x) & 0x7ff) << 16)
+
+#define ISP_SCALE0_COEF_IDX_CHRO                 0x8248
+#define SCALE_CHRO_COEF_S11_MODE                 BIT(9)
+#define SCALE_CHRO_CTYPE(x)                      (((x) & 0x7) << 10)
+
+#define ISP_SCALE0_COEF_CHRO                     0x824c
+#define SCALE_COEF_CHRO_DATA1(x)                 ((x) & 0x7ff)
+#define SCALE_COEF_CHRO_DATA0(x)                 (((x) & 0x7ff) << 16)
+
+#define ISP_AF_ROI0_WIN01                        0xa00c
+#define AF_ROI_XYWIN_00_INIT                     (50 & 0xffff)
+#define AF_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
+
+#define ISP_AF_ROI1_WIN01                        0xa010
+#define AF_ROI_XYWIN_10_INIT                     (100 & 0xffff)
+#define AF_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
+
+#define ISP_AF_ROI0_WIN23                        0xa014
+#define AF_ROI_XYWIN_02_INIT                     (80 & 0xffff)
+#define AF_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
+
+#define ISP_AF_ROI1_WIN23                        0xa018
+#define AF_ROI_XYWIN_12_INIT                     (80 & 0xffff)
+#define AF_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
+
+#define ISP_AF_CTRL                              0xa044
+#define AF_CTRL_YPHS_OFST_MASK                   GENMASK(15, 14)
+#define AF_CTRL_YPHS_OFST_SHIFT                  14
+#define AF_CTRL_XPHS_OFST_MASK                   GENMASK(17, 16)
+#define AF_CTRL_XPHS_OFST_SHIFT                  16
+
+#define ISP_AF_HV_SIZE                           0xa04c
+#define AF_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
+#define AF_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
+
+#define ISP_AF_HV_BLKNUM                         0xa050
+#define AF_HV_STAT_VBLK_NUM_MASK                 GENMASK(5, 0)
+#define AF_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x3f)
+#define AF_HV_STAT_HBLK_NUM_MASK                 GENMASK(21, 16)
+#define AF_HV_STAT_HBLK_NUM_SHIFT                16
+
+#define ISP_AF_EN_CTRL                           0xa054
+#define AF_STAT_SELECT                           BIT(21)
+#define AF_STAT_SELECT_SHIFT                     21
+
+#define ISP_RO_AF_GLB_STAT_PCK0                  0xa0e0
+#define ISP_RO_AF_GLB_STAT_PCK1                  0xa0e4
+#define ISP_RO_AF_GLB_STAT_PCK2                  0xa0e8
+#define ISP_RO_AF_GLB_STAT_PCK3                  0xa0eC
+#define ISP_AF_IDX_ADDR                          0xa1c0
+#define ISP_AF_IDX_DATA                          0xa1c4
+#define AF_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
+#define AF_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
+
+#define ISP_AE_ROI0_WIN01                        0xa40c
+#define AE_ROI_XYWIN_00_INIT                     (50 & 0xffff)
+#define AE_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
+
+#define ISP_AE_ROI1_WIN01                        0xa410
+#define AE_ROI_XYWIN_10_INIT                     (100 & 0xffff)
+#define AE_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
+
+#define ISP_AE_ROI0_WIN23                        0xa414
+#define AE_ROI_XYWIN_02_INIT                     (80 & 0xffff)
+#define AE_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
+
+#define ISP_AE_ROI1_WIN23                        0xa418
+#define AE_ROI_XYWIN_12_INIT                     (80 & 0xffff)
+#define AE_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
+
+#define ISP_RO_AE_ROI_STAT_PCK0_0                0xa424
+#define ISP_RO_AE_ROI_STAT_PCK1_0                0xa428
+#define ISP_RO_AE_ROI_STAT_PCK0_1                0xa42c
+#define ISP_RO_AE_ROI_STAT_PCK1_1                0xa430
+#define ISP_AE_CTRL                              0xa448
+#define AE_CTRL_AE_STAT_LOCAL_MODE               GENMASK(3, 2)
+#define AE_CTRL_AE_STAT_LOCAL_MODE_EXT(x)        (((x) >> 2) & 0x3)
+#define AE_CTRL_INPUT_2LINE_TOGETHER             BIT(7)
+#define AE_CTRL_LUMA_MODE_MASK                   GENMASK(9, 8)
+#define AE_CTRL_LUMA_MODE_SHIFT                  8
+#define AE_CTRL_LUMA_MODE_FILTER                 2
+#define AE_CTRL_YPHS_OFST_MASK                   GENMASK(25, 24)
+#define AE_CTRL_YPHS_OFST_SHIFT                  24
+#define AE_CTRL_XPHS_OFST_MASK                   GENMASK(27, 26)
+#define AE_CTRL_XPHS_OFST_SHIFT                  26
+
+#define ISP_AE_CRTL2_0                           0xa44c
+#define AE_CRTL2_BL12_GRBGI0_MASK                GENMASK(11, 0)
+#define AE_CRTL2_GAIN_GRBGI0_MASK                GENMASK(23, 16)
+#define AE_CRTL2_GAIN_GRBGI0_SHIFT               16
+
+#define ISP_AE_CRTL2_1                           0xa450
+#define AE_CRTL2_BL12_GRBGI1_MASK                GENMASK(11, 0)
+#define AE_CRTL2_GAIN_GRBGI1_MASK                GENMASK(23, 16)
+#define AE_CRTL2_GAIN_GRBGI1_SHIFT               16
+
+#define ISP_AE_CRTL2_2                           0xa454
+#define AE_CRTL2_BL12_GRBGI2_MASK                GENMASK(11, 0)
+#define AE_CRTL2_GAIN_GRBGI2_MASK                GENMASK(23, 16)
+#define AE_CRTL2_GAIN_GRBGI2_SHIFT               16
+
+#define ISP_AE_CRTL2_3                           0xa458
+#define AE_CRTL2_BL12_GRBGI3_MASK                GENMASK(11, 0)
+#define AE_CRTL2_GAIN_GRBGI3_MASK                GENMASK(23, 16)
+#define AE_CRTL2_GAIN_GRBGI3_SHIFT               16
+
+#define ISP_AE_CRTL2_4                           0xa45C
+#define AE_CRTL2_BL12_GRBGI4_MASK                GENMASK(11, 0)
+#define AE_CRTL2_GAIN_GRBGI4_MASK                GENMASK(23, 16)
+#define AE_CRTL2_GAIN_GRBGI4_SHIFT               16
+
+#define ISP_AE_HV_SIZE                           0xa464
+#define AE_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
+#define AE_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
+
+#define ISP_AE_HV_BLKNUM                         0xa468
+#define AE_HV_STAT_VBLK_NUM_MASK                 GENMASK(6, 0)
+#define AE_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x7f)
+#define AE_HV_STAT_HBLK_NUM_MASK                 GENMASK(22, 16)
+#define AE_HV_STAT_HBLK_NUM_SHIFT                16
+
+#define ISP_AE_STAT_THD01                        0xa46c
+#define AE_STAT_THRD0_INIT                       (13107 & 0xffff)
+#define AE_STAT_THRD1_INIT                       ((26214 & 0xffff) << 16)
+
+#define ISP_AE_STAT_THD23                        0xa470
+#define AE_STAT_THRD2_INIT                       (39321 & 0xffff)
+#define AE_STAT_THRD3_INIT                       ((52428 & 0xffff) << 16)
+
+#define ISP_RO_AE_STAT_GPNUM                     0xa49c
+#define ISP_AE_IDX_ADDR                          0xa600
+#define ISP_AE_IDX_DATA                          0xa604
+#define AE_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
+#define AE_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
+
+#define ISP_AE_BLK_WT_ADDR                       0xa608
+#define ISP_AE_BLK_WT_DATA                       0xa60c
+#define AE_BLK_WT_DATA0(x)                       ((x) & 0xf)
+#define AE_BLK_WT_DATA1(x)                       (((x) & 0xf) << 4)
+#define AE_BLK_WT_DATA2(x)                       (((x) & 0xf) << 8)
+#define AE_BLK_WT_DATA3(x)                       (((x) & 0xf) << 12)
+#define AE_BLK_WT_DATA4(x)                       (((x) & 0xf) << 16)
+#define AE_BLK_WT_DATA5(x)                       (((x) & 0xf) << 20)
+#define AE_BLK_WT_DATA6(x)                       (((x) & 0xf) << 24)
+#define AE_BLK_WT_DATA7(x)                       (((x) & 0xf) << 28)
+
+#define ISP_AWB_CTRL                             0xa834
+#define AWB_CTRL_YPHS_OFST_MASK                  GENMASK(1, 0)
+#define AWB_CTRL_XPHS_OFST_MASK                  GENMASK(3, 2)
+#define AWB_CTRL_XPHS_OFST_SHIFT                 2
+#define AWB_CTRL_STAT_RATIO_MODE                 BIT(4)
+
+#define ISP_AWB_HV_SIZE                          0xa83c
+#define AWB_HV_STAT_VSIZE(x)                     ((x) & 0xffff)
+#define AWB_HV_STAT_HSIZE(x)                     (((x) & 0xffff) << 16)
+
+#define ISP_AWB_STAT_ROI0_WIN01                  0xa80c
+#define AWB_ROI_XYWIN_00_INIT                    (32 & 0xffff)
+#define AWB_ROI_XYWIN_01_INIT                    ((62 & 0xffff) << 16)
+
+#define ISP_AWB_STAT_ROI1_WIN01                  0xa810
+#define AWB_ROI_XYWIN_10_INIT                    (32 & 0xffff)
+#define AWB_ROI_XYWIN_11_INIT                    ((82 & 0xffff) << 16)
+
+#define ISP_AWB_STAT_ROI0_WIN23                  0xa814
+#define AWB_ROI_XYWIN_02_INIT                    (32 & 0xffff)
+#define AWB_ROI_XYWIN_03_INIT                    ((42 & 0xffff) << 16)
+
+#define ISP_AWB_STAT_ROI1_WIN23                  0xa818
+#define AWB_ROI_XYWIN_12_INIT                    (64 & 0xffff)
+#define AWB_ROI_XYWIN_13_INIT                    ((52 & 0xffff) << 16)
+
+#define ISP_AWB_HV_BLKNUM                        0xa840
+#define AWB_HV_STAT_VBLK_NUM_MASK                GENMASK(5, 0)
+#define AWB_HV_STAT_VBLK_NUM_EXT(x)              ((x) & 0x3f)
+#define AWB_HV_STAT_HBLK_NUM_MASK                GENMASK(21, 16)
+#define AWB_HV_STAT_HBLK_NUM_SHIFT               16
+
+#define ISP_AWB_STAT_RG                          0xa848
+#define AWB_STAT_RG_MIN_MASK                     GENMASK(11, 0)
+#define AWB_STAT_RG_MAX_MASK                     GENMASK(27, 16)
+#define AWB_STAT_RG_MAX_SHIFT                    16
+
+#define ISP_AWB_STAT_BG                          0xa84c
+#define AWB_STAT_BG_MIN_MASK                     GENMASK(11, 0)
+#define AWB_STAT_BG_MAX_MASK                     GENMASK(27, 16)
+#define AWB_STAT_BG_MAX_SHIFT                    16
+
+#define ISP_AWB_STAT_RG_HL                       0xa850
+#define AWB_STAT_RG_LOW_MASK                     GENMASK(11, 0)
+#define AWB_STAT_RG_HIGH_MASK                    GENMASK(27, 16)
+#define AWB_STAT_RG_HIGH_SHIFT                   16
+
+#define ISP_AWB_STAT_BG_HL                       0xa854
+#define AWB_STAT_BG_LOW_MASK                     GENMASK(11, 0)
+#define AWB_STAT_BG_HIGH_MASK                    GENMASK(27, 16)
+#define AWB_STAT_BG_HIGH_SHIFT                   16
+
+#define ISP_AWB_STAT_CTRL2                       0xa858
+#define AWB_STAT_SATUR_VALID_MASK                BIT(0)
+#define AWB_STAT_LOCAL_MODE                      BIT(2)
+#define AWB_STAT_LOCAL_MODE_EXT(x)               (((x) >> 2) & 0x1)
+#define AWB_STAT_LUMA_DIV_MODE                   GENMASK(4, 3)
+#define AWB_STAT_LUMA_DIV_MODE_EXT(x)            (((x) >> 3) & 0x3)
+
+#define ISP_AWB_STAT_BLC20_0                     0xa85c
+#define AWB_STAT_BLC20_GR_MASK                   GENMASK(19, 0)
+
+#define ISP_AWB_STAT_BLC20_1                     0xa860
+#define AWB_STAT_BLC20_R_MASK                    GENMASK(19, 0)
+
+#define ISP_AWB_STAT_BLC20_2                     0xa864
+#define AWB_STAT_BLC20_B_MASK                    GENMASK(19, 0)
+
+#define ISP_AWB_STAT_BLC20_3                     0xa868
+#define AWB_STAT_BLC20_GB_MASK                   GENMASK(19, 0)
+
+#define ISP_AWB_STAT_GAIN10_0                    0xa86c
+#define AWB_STAT_GAIN10_GR_MASK                  GENMASK(9, 0)
+
+#define ISP_AWB_STAT_GAIN10_1                    0xa870
+#define AWB_STAT_GAIN10_R_MASK                   GENMASK(9, 0)
+
+#define ISP_AWB_STAT_GAIN10_2                    0xa874
+#define AWB_STAT_GAIN10_B_MASK                   GENMASK(9, 0)
+
+#define ISP_AWB_STAT_GAIN10_3                    0xa878
+#define AWB_STAT_GAIN10_GB_MASK                  GENMASK(9, 0)
+
+#define ISP_AWB_STAT_SATUR_CTRL                  0xa884
+#define AWB_STAT_SATUR_LOW(x)                    ((x) & 0xffff)
+#define AWB_STAT_SATUR_HIGH(x)                   (((x) & 0xffff) << 16)
+
+#define ISP_AWB_IDX_ADDR                         0xaa00
+#define ISP_AWB_IDX_DATA                         0xaa04
+#define AWB_IDX_VIDX_DATA(x)                     ((x) & 0xffff)
+#define AWB_IDX_HIDX_DATA(x)                     (((x) & 0xffff) << 16)
+
+#define ISP_AWB_BLK_WT_ADDR                      0xaa08
+#define ISP_AWB_BLK_WT_DATA                      0xaa0c
+#define AWB_BLK_WT_DATA0(x)                      ((x) & 0xf)
+#define AWB_BLK_WT_DATA1(x)                      (((x) & 0xf) << 4)
+#define AWB_BLK_WT_DATA2(x)                      (((x) & 0xf) << 8)
+#define AWB_BLK_WT_DATA3(x)                      (((x) & 0xf) << 12)
+#define AWB_BLK_WT_DATA4(x)                      (((x) & 0xf) << 16)
+#define AWB_BLK_WT_DATA5(x)                      (((x) & 0xf) << 20)
+#define AWB_BLK_WT_DATA6(x)                      (((x) & 0xf) << 24)
+#define AWB_BLK_WT_DATA7(x)                      (((x) & 0xf) << 28)
+
+#define ISP_WRMIFX3_0_CH0_CTRL0                  0xc400
+#define WRMIFX3_CH0_STRIDE_MASK                  GENMASK(28, 16)
+#define WRMIFX3_CH0_STRIDE_SHIFT                 16
+
+#define ISP_WRMIFX3_0_CH0_CTRL1                  0xc404
+#define WRMIFX3_CH0_PIX_BITS_MODE_MASK           GENMASK(30, 27)
+#define WRMIFX3_CH0_PIX_BITS_MODE_SHIFT          27
+#define WRMIFX3_CH0_PIX_BITS_8BITS               1
+#define WRMIFX3_CH0_PIX_BITS_16BITS              2
+
+#define ISP_WRMIFX3_0_CH1_CTRL0                  0xc408
+#define WRMIFX3_CH1_STRIDE_MASK                  GENMASK(28, 16)
+#define WRMIFX3_CH1_STRIDE_SHIFT                 16
+
+#define ISP_WRMIFX3_0_CH1_CTRL1                  0xc40c
+#define WRMIFX3_CH1_PIX_BITS_MODE_MASK           GENMASK(30, 27)
+#define WRMIFX3_CH1_PIX_BITS_MODE_SHIFT          27
+#define WRMIFX3_CH1_PIX_BITS_16BITS              2
+#define WRMIFX3_CH1_PIX_BITS_32BITS              3
+
+#define ISP_WRMIFX3_0_CH0_BADDR                  0xc440
+/* WRMIF base address need 16 bits alignment */
+#define WRMIFX3_CH0_BADDR(x)                     (((x) >> 4) & 0xffffffff)
+
+#define ISP_WRMIFX3_0_CH1_BADDR                  0xc444
+#define WRMIFX3_CH1_BADDR(x)                     (((x) >> 4) & 0xffffffff)
+
+#define ISP_WRMIFX3_0_FMT_SIZE                   0xc464
+#define WRMIFX3_FMT_HSIZE(x)                     ((x) & 0xffff)
+#define WRMIFX3_FMT_VSIZE(x)                     (((x) & 0xffff) << 16)
+
+#define ISP_WRMIFX3_0_FMT_CTRL                   0xc468
+#define WRMIFX3_FMT_MTX_IBITS_MASK               GENMASK(1, 0)
+#define WRMIFX3_FMT_MTX_IBITS_8BIT               0
+#define WRMIFX3_FMT_MTX_IBITS_10BIT              1
+#define WRMIFX3_FMT_MTX_IBITS_12BIT              2
+#define WRMIFX3_FMT_MTX_IBITS_16BIT              3
+#define WRMIFX3_FMT_MTX_UV_SWAP_MASK             BIT(2)
+#define WRMIFX3_FMT_MTX_UV_SWAP_SHIFT            2
+#define WRMIFX3_FMT_MTX_UV_SWAP_VU               0
+#define WRMIFX3_FMT_MTX_UV_SWAP_UV               1
+#define WRMIFX3_FMT_MTX_PLANE_MASK               GENMASK(5, 4)
+#define WRMIFX3_FMT_MTX_PLANE_SHIFT              4
+#define WRMIFX3_FMT_MTX_PLANE_X1                 0
+#define WRMIFX3_FMT_MTX_PLANE_X2                 1
+#define WRMIFX3_FMT_MODE_OUT_MASK                GENMASK(18, 16)
+#define WRMIFX3_FMT_MODE_OUT_SHIFT               16
+#define WRMIFX3_FMT_MODE_OUT_YUV422              1
+#define WRMIFX3_FMT_MODE_OUT_YUV420              2
+#define WRMIFX3_FMT_MODE_OUT_Y_ONLY              3
+
+#define ISP_WRMIFX3_0_WIN_LUMA_H                 0xc420
+#define WRMIFX3_WIN_LUMA_HEND_MASK               GENMASK(28, 16)
+#define WRMIFX3_WIN_LUMA_HEND_SHIFT              16
+#define WRMIFX3_WIN_LUMA_HEND(x)                 ((x) - 1)
+
+#define ISP_WRMIFX3_0_WIN_LUMA_V                 0xc424
+#define WRMIFX3_WIN_LUMA_VEND_MASK               GENMASK(28, 16)
+#define WRMIFX3_WIN_LUMA_VEND_SHIFT              16
+#define WRMIFX3_WIN_LUMA_VEND(x)                 ((x) - 1)
+
+#define ISP_WRMIFX3_0_WIN_CHROM_H                0xc428
+#define WRMIFX3_WIN_CHROM_HEND_MASK              GENMASK(28, 16)
+#define WRMIFX3_WIN_CHROM_HEND_SHIFT             16
+#define WRMIFX3_WIN_CHROM_HEND(x)                (((x) >> 1) - 1)
+
+#define ISP_WRMIFX3_0_WIN_CHROM_V                0xc42c
+#define WRMIFX3_WIN_CHROM_VEND_MASK              GENMASK(28, 16)
+#define WRMIFX3_WIN_CHROM_VEND_SHIFT             16
+#define WRMIFX3_WIN_CHROM_VEND(x)                (((x) >> 1) - 1)
+
+#define ISP_WRMIFX3_0_CRP_HSIZE                  0xc48c
+#define WRMIFX3_CROP_HEND_MASK                   GENMASK(31, 16)
+#define WRMIFX3_CROP_HEND_SHIFT                  16
+#define WRMIFX3_CROP_HEND(x)                     ((x) - 1)
+
+#define ISP_WRMIFX3_0_CRP_VSIZE                  0xc490
+#define WRMIFX3_CROP_VEND_MASK                   GENMASK(31, 16)
+#define WRMIFX3_CROP_VEND_SHIFT                  16
+#define WRMIFX3_CROP_VEND(x)                     ((x) - 1)
+
+#define VIU_DMAWR_BADDR0                         0xc840
+#define VIU_DMAWR_AF_BADDR_MASK                  GENMASK(27, 0)
+/* AF base address need 16 bits alignment */
+#define VIU_DMAWR_AF_BADDR(x)                    ((x) >> 4)
+
+#define VIU_DMAWR_BADDR1                         0xc844
+#define VIU_DMAWR_AWB_BADDR_MASK                  GENMASK(27, 0)
+/* AWB base address need 16 bits alignment */
+#define VIU_DMAWR_AWB_BADDR(x)                    ((x) >> 4)
+
+#define VIU_DMAWR_BADDR2                         0xc848
+#define VIU_DMAWR_AE_BADDR_MASK                  GENMASK(27, 0)
+/* AE base address need 16 bits alignment */
+#define VIU_DMAWR_AE_BADDR(x)                    ((x) >> 4)
+
+#define VIU_DMAWR_SIZE0                          0xc854
+#define VIU_DMAWR_SIZE_AF_MASK                   GENMASK(15, 0)
+#define VIU_DMAWR_SIZE_AWB_MASK                  GENMASK(31, 16)
+#define VIU_DMAWR_SIZE_AWB_SHIFT                 16
+
+#define VIU_DMAWR_SIZE1                          0xc858
+#define VIU_DMAWR_SIZE_AE_MASK                   GENMASK(15, 0)
+
+#endif
diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
new file mode 100644
index 000000000000..01d99b66cb32
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
@@ -0,0 +1,768 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/pm_runtime.h>
+
+#include "c3-isp-common.h"
+#include "c3-isp-regs.h"
+
+static const struct c3_isp_mbus_format_info c3_isp_rsz_mbus_formats[] = {
+	/* YUV formats */
+	{
+		.mbus_code	= MEDIA_BUS_FMT_Y8_1X8,
+		.pads		= BIT(C3_ISP_RESIZER_PAD_SINK)
+				| BIT(C3_ISP_RESIZER_PAD_SOURCE),
+		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
+		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_YUYV8_1_5X8,
+		.pads		= BIT(C3_ISP_RESIZER_PAD_SINK)
+				| BIT(C3_ISP_RESIZER_PAD_SOURCE),
+		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
+		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
+	}, {
+		.mbus_code	= MEDIA_BUS_FMT_YUYV8_2X8,
+		.pads		= BIT(C3_ISP_RESIZER_PAD_SINK)
+				| BIT(C3_ISP_RESIZER_PAD_SOURCE),
+		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
+		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
+	},
+};
+
+/* The normal parameters of pps module */
+static const int pps_lut_tap4_s11[C3_PPS_TAP4_S11_H_NUM][4] =  {
+	{  0, 511,   0,   0}, { -5, 511,   5,   0}, {-10, 511,  11,   0},
+	{-14, 510,  17,  -1}, {-18, 508,  23,  -1}, {-22, 506,  29,  -1},
+	{-25, 503,  36,  -2}, {-28, 500,  43,  -3}, {-32, 496,  51,  -3},
+	{-34, 491,  59,  -4}, {-37, 487,  67,  -5}, {-39, 482,  75,  -6},
+	{-41, 476,  84,  -7}, {-42, 470,  92,  -8}, {-44, 463, 102,  -9},
+	{-45, 456, 111, -10}, {-45, 449, 120, -12}, {-47, 442, 130, -13},
+	{-47, 434, 140, -15}, {-47, 425, 151, -17}, {-47, 416, 161, -18},
+	{-47, 407, 172, -20}, {-47, 398, 182, -21}, {-47, 389, 193, -23},
+	{-46, 379, 204, -25}, {-45, 369, 215, -27}, {-44, 358, 226, -28},
+	{-43, 348, 237, -30}, {-43, 337, 249, -31}, {-41, 326, 260, -33},
+	{-40, 316, 271, -35}, {-39, 305, 282, -36}, {-37, 293, 293, -37}
+};
+
+static const struct c3_isp_mbus_format_info
+*rsz_find_format_by_code(u32 code, u32 pad)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(c3_isp_rsz_mbus_formats); i++) {
+		const struct c3_isp_mbus_format_info *info =
+			&c3_isp_rsz_mbus_formats[i];
+
+		if (info->mbus_code == code && info->pads & BIT(pad))
+			return info;
+	}
+
+	return NULL;
+}
+
+static void c3_isp_rsz_cfg_fmt(struct c3_isp_resizer *rsz,
+			       struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *fmt;
+
+	fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
+
+	c3_isp_write(rsz->isp, C3_DISP_REG(ISP_DISP0_TOP_IN_SIZE, rsz->id),
+		     DISP_TOP_IN_HSIZE(fmt->width) | DISP_TOP_IN_VSIZE(fmt->height));
+}
+
+static void c3_isp_rsz_crop_enable(struct c3_isp_resizer *rsz,
+				   struct v4l2_subdev_state *state)
+{
+	struct v4l2_rect *crop;
+
+	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
+
+	c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_START, rsz->id),
+		     DISP_CRP2_VSTART(crop->top) | DISP_CRP2_HSTART(crop->left));
+	c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_SIZE, rsz->id),
+		     DISP_CRP2_VSIZE(crop->height) | DISP_CRP2_HSIZE(crop->width));
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
+			   DISP_CRP2_EN, DISP_CRP2_EN);
+}
+
+static void c3_isp_rsz_pps_size(struct c3_isp_resizer *rsz,
+				struct c3_isp_pps_io_size *io_size)
+{
+	int thsize = io_size->thsize;
+	int tvsize = io_size->tvsize;
+	u32 ohsize = io_size->ohsize;
+	u32 ovsize = io_size->ovsize;
+	u32 ihsize = io_size->ihsize;
+	u32 max_hsize = io_size->max_hsize;
+	int step_h_integer, step_v_integer;
+	int step_h_fraction, step_v_fraction;
+	int yuv444to422_en;
+
+	/* Calculate the integer part of horizonal scaler step */
+	step_h_integer = thsize / ohsize;
+
+	/* Calculate the vertical part of horizonal scaler step */
+	step_v_integer = tvsize / ovsize;
+
+	/*
+	 * Calculate the fraction part of horizonal scaler step.
+	 * step_h_fraction = (source / dest) * 2^24,
+	 * so step_h_fraction = ((source << 12) / dest) << 12.
+	 */
+	step_h_fraction = ((thsize << 12) / ohsize) << 12;
+
+	/*
+	 * Calculate the fraction part of vertical scaler step
+	 * step_v_fraction = (source / dest) * 2^24,
+	 * so step_v_fraction = ((source << 12) / dest) << 12.
+	 */
+	step_v_fraction = ((tvsize << 12) / ovsize) << 12;
+
+	yuv444to422_en = ihsize > (max_hsize / 2) ? 1 : 0;
+
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_444TO422, rsz->id),
+			   PPS_444TO422_EN_MASK, yuv444to422_en);
+
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
+			   PPS_VSC_FRACTION_PART_MASK, step_v_fraction);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
+			   PPS_VSC_INTEGER_PART_MASK,
+			   step_v_integer << PPS_VSC_INTEGER_PART_SHIFT);
+
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
+			   PPS_HSC_FRACTION_PART_MASK, step_h_fraction);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
+			   PPS_HSC_INTEGER_PART_MASK,
+			   step_h_integer << PPS_HSC_INTEGER_PART_SHIFT);
+
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
+			   PPS_PREHSC_LUMA_COEF0_MASK, PPS_PREHSC_LUMA_COEF0_INIT);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
+			   PPS_PREHSC_LUMA_COEF1_MASK,
+			   PPS_PREHSC_LUMA_COEF1_INIT << PPS_PREHSC_LUMA_COEF1_SHIFT);
+
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
+			   PPS_PREHSC_LUMA_COEF2_MASK, PPS_PREHSC_LUMA_COEF2_INIT);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
+			   PPS_PREHSC_LUMA_COEF3_MASK,
+			   PPS_PREHSC_LUMA_COEF3_INIT << PPS_PREHSC_LUMA_COEF3_SHIFT);
+}
+
+static void c3_isp_rsz_pps_lut(struct c3_isp_resizer *rsz, u32 ctype)
+{
+	int i;
+
+	/*
+	 * Default value of this register is 0,
+	 * so only need to set SCALE_LUMA_COEF_S11_MODE
+	 * and SCALE_LUMA_CTYPE.
+	 * This register needs to be written in one time.
+	 */
+	c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_LUMA, rsz->id),
+		     SCALE_LUMA_COEF_S11_MODE | SCALE_LUMA_CTYPE(ctype));
+
+	for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
+		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
+			     SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
+			     SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
+		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
+			     SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
+			     SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][3]));
+	}
+
+	/*
+	 * Default value of this register is 0,
+	 * so only need to set SCALE_CHRO_COEF_S11_MODE
+	 * and SCALE_CHRO_CTYPE.
+	 * This register needs to be written in one time.
+	 */
+	c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_CHRO, rsz->id),
+		     SCALE_CHRO_COEF_S11_MODE | SCALE_CHRO_CTYPE(ctype));
+
+	for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
+		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
+			     SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
+			     SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
+		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
+			     SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
+			     SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][3]));
+	}
+}
+
+static void c3_isp_rsz_pps_disable(struct c3_isp_resizer *rsz)
+{
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_HSC_EN_MASK, 0);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_VSC_EN_MASK, 0);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_PREVSC_EN_MASK, 0);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_PREHSC_EN_MASK, 0);
+}
+
+static int c3_isp_rsz_pps_enable(struct c3_isp_resizer *rsz,
+				 struct v4l2_subdev_state *state)
+{
+	struct v4l2_rect *crop;
+	struct v4l2_rect *cmps;
+	int max_hsize;
+	int hsc_en, vsc_en;
+	int preh_en, prev_en;
+	u32 reg_prehsc_rate;
+	u32 reg_prevsc_flt_num;
+	int pre_vscale_max_hsize;
+	u32 ihsize_after_pre_hsc;
+	u32 ihsize_after_pre_hsc_alt;
+	u32 reg_vsc_tap_num_alt;
+	u32 ihsize;
+	u32 ivsize;
+	struct c3_isp_pps_io_size io_size;
+
+	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
+	cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
+
+	ihsize = crop->width;
+	ivsize = crop->height;
+
+	hsc_en = (ihsize == cmps->width) ? C3_SCALE_DIS : C3_SCALE_EN;
+	vsc_en = (ivsize == cmps->height) ? C3_SCALE_DIS : C3_SCALE_EN;
+
+	/* Disable pps when there no need to use pps */
+	if (!hsc_en && !vsc_en) {
+		c3_isp_rsz_pps_disable(rsz);
+		return 0;
+	}
+
+	/*
+	 * Pre-scale needs to be enable
+	 * if the down scaling factor exceeds 4.
+	 */
+	preh_en = (ihsize > cmps->width * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
+	prev_en = (ivsize > cmps->height * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
+
+	if (rsz->id == C3_ISP_RSZ_2) {
+		max_hsize = C3_ISP_MAX_WIDTH;
+		/*
+		 * Set vertical tap number and
+		 * the max hsize of pre-vertical scale.
+		 */
+		reg_prevsc_flt_num = 4;
+		pre_vscale_max_hsize = max_hsize / 2;
+	} else {
+		max_hsize = C3_ISP_DEFAULT_WIDTH;
+		preh_en  = (ihsize > max_hsize) ? C3_SCALE_EN : C3_SCALE_DIS;
+		/*
+		 * Set vertical tap number and
+		 * the max hsize of pre-vertical scale.
+		 */
+		if (ihsize > (max_hsize / 2) &&
+		    ihsize <= max_hsize && prev_en) {
+			reg_prevsc_flt_num = 2;
+			pre_vscale_max_hsize = max_hsize;
+		} else {
+			reg_prevsc_flt_num = 4;
+			pre_vscale_max_hsize = max_hsize / 2;
+		}
+	}
+
+	/*
+	 * Set pre-horizonal scale rate and
+	 * the hsize of after pre-horizonal scale.
+	 */
+	if (preh_en) {
+		reg_prehsc_rate = 1;
+		ihsize_after_pre_hsc = DIV_ROUND_UP(ihsize, 2);
+	} else {
+		reg_prehsc_rate = 0;
+		ihsize_after_pre_hsc = ihsize;
+	}
+
+	/* Change pre-horizonal scale rate */
+	if (prev_en && ihsize_after_pre_hsc >= pre_vscale_max_hsize)
+		reg_prehsc_rate += 1;
+
+	/* Set the actual hsize of after pre-horizonal scale */
+	if (preh_en)
+		ihsize_after_pre_hsc_alt =
+			DIV_ROUND_UP(ihsize, 1 << reg_prehsc_rate);
+	else
+		ihsize_after_pre_hsc_alt = ihsize;
+
+	/* Set vertical scaler bank length */
+	if (ihsize_after_pre_hsc_alt <= (max_hsize / 2))
+		reg_vsc_tap_num_alt = 4;
+	else if (ihsize_after_pre_hsc_alt <= max_hsize)
+		reg_vsc_tap_num_alt = prev_en ? 2 : 4;
+	else
+		reg_vsc_tap_num_alt = prev_en ? 4 : 2;
+
+	io_size.thsize = ihsize_after_pre_hsc_alt;
+	io_size.tvsize = prev_en ? DIV_ROUND_UP(ivsize, 2) : ivsize;
+	io_size.ohsize = cmps->width;
+	io_size.ovsize = cmps->height;
+	io_size.ihsize = ihsize;
+	io_size.max_hsize = max_hsize;
+
+	c3_isp_rsz_pps_size(rsz, &io_size);
+	c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_0);
+	c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_2);
+
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_VSC_TAP_NUM_MASK, reg_vsc_tap_num_alt);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_HSC_TAP_NUM_MASK,
+			   PPS_HSC_TAP_NUM_INIT << PPS_HSC_TAP_NUM_SHIFT);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_PREVSC_FLT_NUM_MASK,
+			   reg_prevsc_flt_num << PPS_PREVSC_FLT_NUM_SHIFT);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_PREHSC_FLT_NUM_MASK,
+			   PPS_PREHSC_FLT_NUM_INIT << PPS_PREHSC_FLT_NUM_SHIFT);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_PREVSC_RATE_MASK, prev_en << PPS_PREVSC_RATE_SHIFT);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_PREHSC_RATE_MASK, reg_prehsc_rate << PPS_PREHSC_RATE_SHIFT);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_HSC_EN_MASK, hsc_en << PPS_HSC_EN_SHIFT);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_VSC_EN_MASK, vsc_en << PPS_VSC_EN_SHIFT);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_PREVSC_EN_MASK, prev_en << PPS_PREVSC_EN_SHIFT);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_PREHSC_EN_MASK, preh_en << PPS_PREHSC_EN_SHIFT);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_HSC_NOR_RS_BITS_MASK,
+			   PPS_HSC_NOR_RS_BITS_INIT << PPS_HSC_NOR_RS_BITS_SHIFT);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
+			   PPS_VSC_NOR_RS_BITS_MASK,
+			   PPS_VSC_NOR_RS_BITS_INIT << PPS_VSC_NOR_RS_BITS_SHIFT);
+
+	return 0;
+}
+
+void c3_isp_rsz_start(struct c3_isp_resizer *rsz)
+{
+	struct v4l2_subdev_state *state;
+
+	state = v4l2_subdev_lock_and_get_active_state(&rsz->sd);
+
+	c3_isp_rsz_cfg_fmt(rsz, state);
+	c3_isp_rsz_crop_enable(rsz, state);
+	c3_isp_rsz_pps_enable(rsz, state);
+
+	c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN,
+			   TOP_DISP_EN(rsz->id), TOP_DISP_EN(rsz->id));
+
+	v4l2_subdev_unlock_state(state);
+}
+
+void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
+{
+	c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, TOP_DISP_EN(rsz->id), 0);
+	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
+			   DISP_CRP2_EN, 0x0);
+
+	c3_isp_rsz_pps_disable(rsz);
+}
+
+static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state,
+				  struct v4l2_subdev_krouting *routing)
+{
+	static const struct v4l2_mbus_framefmt format = {
+		.width = C3_ISP_DEFAULT_WIDTH,
+		.height = C3_ISP_DEFAULT_HEIGHT,
+		.code = C3_ISP_RSZ_DEF_PAD_FMT,
+		.field = V4L2_FIELD_NONE,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.ycbcr_enc = V4L2_YCBCR_ENC_601,
+		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
+		.xfer_func = V4L2_XFER_FUNC_SRGB,
+	};
+	int ret;
+
+	ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes;
+	struct v4l2_subdev_krouting routing;
+
+	routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
+	routes.sink_stream = 0;
+	routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
+	routes.source_stream = 0;
+	routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
+
+	routing.num_routes = 1;
+	routing.routes = &routes;
+
+	return c3_isp_rsz_cfg_routing(sd, state, &routing);
+}
+
+static int c3_isp_rsz_set_routing(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state,
+				  enum v4l2_subdev_format_whence which,
+				  struct v4l2_subdev_krouting *routing)
+{
+	bool is_streaming = v4l2_subdev_is_streaming(sd);
+
+	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
+		return -EBUSY;
+
+	return c3_isp_rsz_cfg_routing(sd, state, routing);
+}
+
+static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
+				     struct v4l2_subdev_state *state,
+				     struct v4l2_subdev_mbus_code_enum *code)
+{
+	if (code->index >= ARRAY_SIZE(c3_isp_rsz_mbus_formats))
+		return -EINVAL;
+
+	code->code = c3_isp_rsz_mbus_formats[code->index].mbus_code;
+
+	return 0;
+}
+
+static void c3_isp_rsz_set_sink_fmt(struct v4l2_subdev_state *state,
+				    struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *sink_fmt;
+	const struct c3_isp_mbus_format_info *isp_fmt;
+	struct v4l2_rect *sink_crop;
+	struct v4l2_rect *sink_cmps;
+
+	sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
+	sink_crop = v4l2_subdev_state_get_crop(state, format->pad);
+	sink_cmps = v4l2_subdev_state_get_compose(state, format->pad);
+
+	isp_fmt = rsz_find_format_by_code(format->format.code, format->pad);
+	if (!isp_fmt)
+		sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
+	else
+		sink_fmt->code = format->format.code;
+
+	sink_fmt->width = clamp_t(u32, format->format.width,
+				  C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
+	sink_fmt->height = clamp_t(u32, format->format.height,
+				   C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
+
+	sink_crop->width = sink_fmt->width;
+	sink_crop->height = sink_fmt->height;
+	sink_crop->left = 0;
+	sink_crop->top = 0;
+
+	sink_cmps->width = sink_crop->width;
+	sink_cmps->height = sink_crop->height;
+	sink_cmps->left = 0;
+	sink_cmps->top = 0;
+
+	format->format = *sink_fmt;
+}
+
+static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state,
+				      struct v4l2_subdev_format *format)
+{
+	const struct c3_isp_mbus_format_info *rsz_fmt;
+	struct v4l2_mbus_framefmt *src_fmt;
+	struct v4l2_rect *sink_crop;
+	struct v4l2_rect *sink_cmps;
+
+	src_fmt = v4l2_subdev_state_get_format(state, format->pad);
+	sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
+	sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
+
+	rsz_fmt = rsz_find_format_by_code(format->format.code, format->pad);
+	if (!rsz_fmt)
+		src_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
+	else
+		src_fmt->code = format->format.code;
+
+	src_fmt->width = clamp_t(u32, format->format.width,
+				 C3_ISP_MIN_WIDTH, sink_crop->width);
+	src_fmt->height = clamp_t(u32, format->format.height,
+				  C3_ISP_MIN_HEIGHT, sink_crop->height);
+
+	/* The sink compose size must be same with the source size. */
+	sink_cmps->width = src_fmt->width;
+	sink_cmps->height = src_fmt->height;
+
+	format->format = *src_fmt;
+}
+
+static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd,
+			      struct v4l2_subdev_state *state,
+			      struct v4l2_subdev_format *format)
+{
+	if (format->pad == C3_ISP_RESIZER_PAD_SINK) {
+		c3_isp_rsz_set_sink_fmt(state, format);
+	} else if (format->pad == C3_ISP_RESIZER_PAD_SOURCE) {
+		c3_isp_rsz_set_source_fmt(state, format);
+	} else {
+		dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
+		return -ENOTTY;
+	}
+
+	return 0;
+}
+
+static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *state,
+				    struct v4l2_subdev_selection *sel)
+{
+	struct v4l2_mbus_framefmt *fmt;
+	struct v4l2_rect *crop;
+	struct v4l2_rect *cmps;
+
+	if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
+		return -EINVAL;
+
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		fmt = v4l2_subdev_state_get_format(state, sel->pad);
+		sel->r.width = fmt->width;
+		sel->r.height = fmt->height;
+		sel->r.left = 0;
+		sel->r.top = 0;
+		break;
+	case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+		crop = v4l2_subdev_state_get_crop(state, sel->pad);
+		sel->r.width = crop->width;
+		sel->r.height = crop->height;
+		sel->r.left = 0;
+		sel->r.top = 0;
+		break;
+	case V4L2_SEL_TGT_CROP:
+		crop = v4l2_subdev_state_get_crop(state, sel->pad);
+		sel->r = *crop;
+		break;
+	case V4L2_SEL_TGT_COMPOSE:
+		cmps = v4l2_subdev_state_get_compose(state, sel->pad);
+		sel->r = *cmps;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *state,
+				    struct v4l2_subdev_selection *sel)
+{
+	struct v4l2_mbus_framefmt *fmt;
+	struct v4l2_rect *crop;
+	struct v4l2_rect *cmps;
+
+	if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
+		return -EINVAL;
+
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP:
+		fmt = v4l2_subdev_state_get_format(state, sel->pad);
+		crop = v4l2_subdev_state_get_crop(state, sel->pad);
+
+		sel->r.left = clamp_t(s32, sel->r.left, 0, fmt->width - 1);
+		sel->r.top = clamp_t(s32, sel->r.top, 0, fmt->height - 1);
+		sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH,
+				     fmt->width - sel->r.left);
+		sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT,
+				      fmt->height - sel->r.top);
+
+		crop->width = ALIGN(sel->r.width, 2);
+		crop->height = ALIGN(sel->r.height, 2);
+		crop->left = sel->r.left;
+		crop->top = sel->r.top;
+
+		sel->r = *crop;
+		break;
+	case V4L2_SEL_TGT_COMPOSE:
+		crop = v4l2_subdev_state_get_crop(state, sel->pad);
+		cmps = v4l2_subdev_state_get_compose(state, sel->pad);
+
+		sel->r.left = 0;
+		sel->r.top = 0;
+		sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH, crop->width);
+		sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT, crop->height);
+
+		cmps->width = ALIGN(sel->r.width, 2);
+		cmps->height = ALIGN(sel->r.height, 2);
+		cmps->left = sel->r.left;
+		cmps->top = sel->r.top;
+
+		sel->r = *cmps;
+
+		fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
+		fmt->width = cmps->width;
+		fmt->height = cmps->height;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int c3_isp_rsz_init_state(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *sink_fmt;
+	struct v4l2_mbus_framefmt *src_fmt;
+	struct v4l2_rect *crop;
+	struct v4l2_rect *cmps;
+
+	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
+	sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
+	sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
+	sink_fmt->field = V4L2_FIELD_NONE;
+	sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
+	sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
+	sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
+	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
+	sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
+
+	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
+	crop->width = C3_ISP_DEFAULT_WIDTH;
+	crop->height = C3_ISP_DEFAULT_HEIGHT;
+	crop->left = 0;
+	crop->top = 0;
+
+	cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
+	cmps->width = C3_ISP_DEFAULT_WIDTH;
+	cmps->height = C3_ISP_DEFAULT_HEIGHT;
+	cmps->left = 0;
+	cmps->top = 0;
+
+	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
+	*src_fmt = *sink_fmt;
+
+	return c3_isp_rsz_init_routing(sd, state);
+}
+
+static const struct v4l2_subdev_pad_ops c3_isp_rsz_pad_ops = {
+	.enum_mbus_code = c3_isp_rsz_enum_mbus_code,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = c3_isp_rsz_set_fmt,
+	.get_selection = c3_isp_rsz_get_selection,
+	.set_selection = c3_isp_rsz_set_selection,
+	.set_routing = c3_isp_rsz_set_routing,
+};
+
+static const struct v4l2_subdev_ops c3_isp_rsz_subdev_ops = {
+	.pad = &c3_isp_rsz_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops c3_isp_rsz_internal_ops = {
+	.init_state = c3_isp_rsz_init_state,
+};
+
+/* Media entity operations */
+static const struct media_entity_operations c3_isp_rsz_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+static int c3_isp_rsz_register(struct c3_isp_resizer *rsz)
+{
+	struct v4l2_subdev *sd = &rsz->sd;
+	int ret;
+
+	v4l2_subdev_init(sd, &c3_isp_rsz_subdev_ops);
+	sd->owner = THIS_MODULE;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	sd->internal_ops = &c3_isp_rsz_internal_ops;
+	snprintf(sd->name, sizeof(sd->name), "isp-resizer%u", rsz->id);
+
+	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
+	sd->entity.ops = &c3_isp_rsz_entity_ops;
+
+	sd->dev = rsz->isp->dev;
+	v4l2_set_subdevdata(sd, rsz);
+
+	rsz->pads[C3_ISP_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+	rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_PAD_MAX, rsz->pads);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_init_finalize(sd);
+	if (ret)
+		goto err_entity_cleanup;
+
+	ret = v4l2_device_register_subdev(&rsz->isp->v4l2_dev, sd);
+	if (ret)
+		goto err_subdev_cleanup;
+
+	return 0;
+
+err_subdev_cleanup:
+	v4l2_subdev_cleanup(sd);
+err_entity_cleanup:
+	media_entity_cleanup(&sd->entity);
+	return ret;
+}
+
+static void c3_isp_rsz_unregister(struct c3_isp_resizer *rsz)
+{
+	struct v4l2_subdev *sd = &rsz->sd;
+
+	v4l2_device_unregister_subdev(sd);
+	v4l2_subdev_cleanup(sd);
+	media_entity_cleanup(&sd->entity);
+}
+
+int c3_isp_resizers_register(struct c3_isp_device *isp)
+{
+	u32 i;
+	int ret;
+
+	for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
+		struct c3_isp_resizer *rsz = &isp->resizers[i];
+
+		rsz->id = i;
+		rsz->isp = isp;
+
+		if (rsz->id == C3_ISP_RSZ_0)
+			rsz->cap = &isp->caps[C3_ISP_CAP_DEV_0];
+		else if (rsz->id == C3_ISP_RSZ_1)
+			rsz->cap = &isp->caps[C3_ISP_CAP_DEV_1];
+		else
+			rsz->cap = &isp->caps[C3_ISP_CAP_DEV_2];
+
+		ret = c3_isp_rsz_register(rsz);
+		if (ret) {
+			rsz->isp = NULL;
+			c3_isp_resizers_unregister(isp);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+void c3_isp_resizers_unregister(struct c3_isp_device *isp)
+{
+	u32 i;
+
+	for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
+		struct c3_isp_resizer *rsz = &isp->resizers[i];
+
+		if (rsz->isp)
+			c3_isp_rsz_unregister(rsz);
+	};
+}
diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
new file mode 100644
index 000000000000..72024442d48f
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
@@ -0,0 +1,488 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/cleanup.h>
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "c3-isp-common.h"
+#include "c3-isp-regs.h"
+#include "include/uapi/c3-isp-config.h"
+
+/* Hardware configuration */
+
+static void c3_isp_stats_af_init(struct c3_isp_stats *stats)
+{
+	c3_isp_write(stats->isp, ISP_AF_ROI0_WIN01,
+		     AF_ROI_XYWIN_00_INIT | AF_ROI_XYWIN_01_INIT);
+
+	c3_isp_write(stats->isp, ISP_AF_ROI0_WIN23,
+		     AF_ROI_XYWIN_02_INIT | AF_ROI_XYWIN_03_INIT);
+
+	c3_isp_write(stats->isp, ISP_AF_ROI1_WIN01,
+		     AF_ROI_XYWIN_10_INIT | AF_ROI_XYWIN_11_INIT);
+
+	c3_isp_write(stats->isp, ISP_AF_ROI1_WIN23,
+		     AF_ROI_XYWIN_12_INIT | AF_ROI_XYWIN_13_INIT);
+
+	/* 0: old statistics output, 1: new statistics output. */
+	c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL,
+			   AF_STAT_SELECT, 0x1 << AF_STAT_SELECT_SHIFT);
+}
+
+static void c3_isp_stats_ae_init(struct c3_isp_stats *stats)
+{
+	c3_isp_write(stats->isp, ISP_AE_ROI0_WIN01,
+		     AE_ROI_XYWIN_00_INIT | AE_ROI_XYWIN_01_INIT);
+
+	c3_isp_write(stats->isp, ISP_AE_ROI0_WIN23,
+		     AE_ROI_XYWIN_02_INIT | AE_ROI_XYWIN_03_INIT);
+
+	c3_isp_write(stats->isp, ISP_AE_ROI1_WIN01,
+		     AE_ROI_XYWIN_10_INIT | AE_ROI_XYWIN_11_INIT);
+
+	c3_isp_write(stats->isp, ISP_AE_ROI1_WIN23,
+		     AE_ROI_XYWIN_12_INIT | AE_ROI_XYWIN_13_INIT);
+
+	c3_isp_write(stats->isp, ISP_AE_STAT_THD01,
+		     AE_STAT_THRD0_INIT | AE_STAT_THRD1_INIT);
+
+	c3_isp_write(stats->isp, ISP_AE_STAT_THD23,
+		     AE_STAT_THRD2_INIT | AE_STAT_THRD3_INIT);
+
+	/* Set 0 when ae_stat_switch is not 0 */
+	c3_isp_update_bits(stats->isp, ISP_AE_CTRL,
+			   AE_CTRL_INPUT_2LINE_TOGETHER, 0);
+
+	/* Configure ae luma mode */
+	c3_isp_update_bits(stats->isp, ISP_AE_CTRL, AE_CTRL_LUMA_MODE_MASK,
+			   AE_CTRL_LUMA_MODE_FILTER << AE_CTRL_LUMA_MODE_SHIFT);
+}
+
+static void c3_isp_stats_awb_init(struct c3_isp_stats *stats)
+{
+	/* Initialize the awb statistics rectangle of image */
+	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN01,
+		     AWB_ROI_XYWIN_00_INIT | AWB_ROI_XYWIN_01_INIT);
+	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN23,
+		     AWB_ROI_XYWIN_02_INIT | AWB_ROI_XYWIN_03_INIT);
+
+	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN01,
+		     AWB_ROI_XYWIN_10_INIT | AWB_ROI_XYWIN_11_INIT);
+	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN23,
+		     AWB_ROI_XYWIN_12_INIT | AWB_ROI_XYWIN_13_INIT);
+}
+
+static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
+{
+	struct c3_isp_device *isp = stats->isp;
+	struct c3_isp_stats_info *stats_info = stats->buff->vaddr;
+	u32 awb_dma_size = sizeof(stats_info->awb_stats);
+	u32 ae_dma_size = sizeof(stats_info->ae_stats);
+	u32 awb_dma_addr = stats->buff->paddr;
+	u32 af_dma_addr;
+	u32 ae_dma_addr;
+
+	ae_dma_addr = awb_dma_addr + awb_dma_size;
+	af_dma_addr = ae_dma_addr + ae_dma_size;
+
+	c3_isp_update_bits(isp, VIU_DMAWR_BADDR0, VIU_DMAWR_AF_BADDR_MASK,
+			   VIU_DMAWR_AF_BADDR(af_dma_addr));
+	c3_isp_update_bits(isp, VIU_DMAWR_BADDR1, VIU_DMAWR_AWB_BADDR_MASK,
+			   VIU_DMAWR_AWB_BADDR(awb_dma_addr));
+	c3_isp_update_bits(isp, VIU_DMAWR_BADDR2, VIU_DMAWR_AE_BADDR_MASK,
+			   VIU_DMAWR_AE_BADDR(ae_dma_addr));
+}
+
+static void c3_isp_stats_enable(struct c3_isp_stats *stats)
+{
+	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
+			   TOP_3A_AE_STAT_EN, TOP_3A_AE_STAT_EN);
+	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
+			   TOP_3A_AWB_STAT_EN, TOP_3A_AWB_STAT_EN);
+	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
+			   TOP_3A_AF_STAT_EN, TOP_3A_AF_STAT_EN);
+}
+
+static void c3_isp_stats_disable(struct c3_isp_stats *stats)
+{
+	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
+			   TOP_3A_AE_STAT_EN, 0);
+	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
+			   TOP_3A_AWB_STAT_EN, 0);
+	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
+			   TOP_3A_AF_STAT_EN, 0);
+}
+
+/* The unit of dma_size is 16 bytes */
+static void c3_isp_stats_cfg_dmawr_size(struct c3_isp_stats *stats)
+{
+	u32 dma_size;
+
+	dma_size = sizeof(struct af_stats_info) / 16;
+	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0,
+			   VIU_DMAWR_SIZE_AF_MASK, dma_size);
+
+	dma_size = sizeof(struct awb_stats_info) / 16;
+	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE_AWB_MASK,
+			   dma_size << VIU_DMAWR_SIZE_AWB_SHIFT);
+
+	dma_size = sizeof(struct ae_stats_info) / 16;
+	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1,
+			   VIU_DMAWR_SIZE_AE_MASK, dma_size);
+}
+
+static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
+{
+	stats->buff = list_first_entry_or_null(&stats->pending,
+					       struct c3_isp_vb2_buffer, list);
+	if (stats->buff) {
+		c3_isp_stats_cfg_dmawr_addr(stats);
+		list_del(&stats->buff->list);
+	}
+}
+
+static void c3_isp_stats_start(struct c3_isp_stats *stats)
+{
+	c3_isp_stats_af_init(stats);
+	c3_isp_stats_ae_init(stats);
+	c3_isp_stats_awb_init(stats);
+
+	c3_isp_stats_cfg_dmawr_size(stats);
+	c3_isp_stats_cfg_buff(stats);
+	c3_isp_stats_enable(stats);
+
+	stats->is_streaming = true;
+}
+
+static void c3_isp_stats_stop(struct c3_isp_stats *stats)
+{
+	stats->is_streaming = false;
+
+	c3_isp_stats_disable(stats);
+}
+
+static void c3_isp_stats_return_buffers(struct c3_isp_stats *stats,
+					enum vb2_buffer_state state)
+{
+	unsigned long flags;
+	struct c3_isp_vb2_buffer *buff;
+
+	spin_lock_irqsave(&stats->buff_lock, flags);
+
+	if (stats->buff) {
+		vb2_buffer_done(&stats->buff->vb.vb2_buf, state);
+		stats->buff = NULL;
+	}
+
+	while (!list_empty(&stats->pending)) {
+		buff = list_first_entry(&stats->pending,
+					struct c3_isp_vb2_buffer, list);
+		list_del(&buff->list);
+		vb2_buffer_done(&buff->vb.vb2_buf, state);
+	}
+
+	spin_unlock_irqrestore(&stats->buff_lock, flags);
+}
+
+static int c3_isp_stats_querycap(struct file *file, void *fh,
+				 struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
+	strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
+
+	return 0;
+}
+
+static int c3_isp_stats_enum_fmt(struct file *file, void *fh,
+				 struct v4l2_fmtdesc *f)
+{
+	struct c3_isp_stats *stats = video_drvdata(file);
+
+	if (f->index > 0 || f->type != stats->vb2_q.type)
+		return -EINVAL;
+
+	f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
+
+	return 0;
+}
+
+static int c3_isp_stats_g_fmt(struct file *file, void *fh,
+			      struct v4l2_format *f)
+{
+	struct c3_isp_stats *stats = video_drvdata(file);
+
+	f->fmt.meta = stats->vfmt.fmt.meta;
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
+	.vidioc_querycap                = c3_isp_stats_querycap,
+	.vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
+	.vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
+	.vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
+	.vidioc_try_fmt_meta_cap        = c3_isp_stats_g_fmt,
+	.vidioc_reqbufs	                = vb2_ioctl_reqbufs,
+	.vidioc_querybuf                = vb2_ioctl_querybuf,
+	.vidioc_qbuf                    = vb2_ioctl_qbuf,
+	.vidioc_expbuf                  = vb2_ioctl_expbuf,
+	.vidioc_dqbuf                   = vb2_ioctl_dqbuf,
+	.vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
+	.vidioc_create_bufs             = vb2_ioctl_create_bufs,
+	.vidioc_streamon                = vb2_ioctl_streamon,
+	.vidioc_streamoff               = vb2_ioctl_streamoff,
+	.vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_file_operations isp_stats_v4l2_fops = {
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release,
+	.poll = vb2_fop_poll,
+	.unlocked_ioctl = video_ioctl2,
+	.mmap = vb2_fop_mmap,
+};
+
+static int c3_isp_stats_vb2_queue_setup(struct vb2_queue *q,
+					unsigned int *num_buffers,
+					unsigned int *num_planes,
+					unsigned int sizes[],
+					struct device *alloc_devs[])
+{
+	if (*num_planes) {
+		if (*num_planes != 1)
+			return -EINVAL;
+
+		if (sizes[0] < sizeof(struct c3_isp_stats_info))
+			return -EINVAL;
+	} else {
+		*num_planes = 1;
+		sizes[0] = sizeof(struct c3_isp_stats_info);
+	}
+
+	return 0;
+}
+
+static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+	struct c3_isp_vb2_buffer *buf =
+			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
+	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned long flags;
+
+	spin_lock_irqsave(&stats->buff_lock, flags);
+
+	list_add_tail(&buf->list, &stats->pending);
+
+	spin_unlock_irqrestore(&stats->buff_lock, flags);
+}
+
+static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned int size = stats->vfmt.fmt.meta.buffersize;
+
+	if (vb2_plane_size(vb, 0) < size) {
+		dev_err(stats->isp->dev,
+			"User buffer too small (%ld < %u)\n",
+			vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+
+	vb2_set_plane_payload(vb, 0, size);
+
+	return 0;
+}
+
+static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+	struct c3_isp_vb2_buffer *buf =
+			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
+	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
+
+	buf->vaddr = vb2_plane_vaddr(vb, 0);
+	buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+	memset(buf->vaddr, 0, stats->vfmt.fmt.meta.buffersize);
+
+	return 0;
+}
+
+static int c3_isp_stats_vb2_start_streaming(struct vb2_queue *q,
+					    unsigned int count)
+{
+	struct c3_isp_stats *stats = vb2_get_drv_priv(q);
+	int ret;
+
+	guard(mutex)(&stats->isp->lock);
+
+	ret = pm_runtime_resume_and_get(stats->isp->dev);
+	if (ret)
+		return ret;
+
+	ret = video_device_pipeline_start(&stats->vdev, &stats->isp->pipe);
+	if (ret) {
+		dev_err(stats->isp->dev,
+			"Failed to start stats pipeline: %d\n", ret);
+		goto err_pm_put;
+	}
+
+	if (c3_isp_pipeline_ready(stats->isp)) {
+		ret = v4l2_subdev_enable_streams(&stats->isp->core.sd,
+						 C3_ISP_CORE_PAD_SOURCE_STATS,
+						 BIT(0));
+		if (ret)
+			goto err_pipeline_stop;
+	}
+
+	c3_isp_stats_start(stats);
+
+	return 0;
+
+err_pipeline_stop:
+	video_device_pipeline_stop(&stats->vdev);
+err_pm_put:
+	pm_runtime_put(stats->isp->dev);
+	c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_QUEUED);
+	return ret;
+}
+
+static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
+{
+	struct c3_isp_stats *stats = vb2_get_drv_priv(q);
+
+	guard(mutex)(&stats->isp->lock);
+
+	c3_isp_stats_stop(stats);
+	c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_ERROR);
+
+	if (stats->isp->pipe.start_count == 1)
+		v4l2_subdev_disable_streams(&stats->isp->core.sd,
+					    C3_ISP_CORE_PAD_SOURCE_STATS,
+					    BIT(0));
+
+	video_device_pipeline_stop(&stats->vdev);
+	pm_runtime_put(stats->isp->dev);
+}
+
+static const struct vb2_ops isp_stats_vb2_ops = {
+	.queue_setup = c3_isp_stats_vb2_queue_setup,
+	.buf_queue = c3_isp_stats_vb2_buf_queue,
+	.buf_prepare = c3_isp_stats_vb2_buf_prepare,
+	.buf_init = c3_isp_stats_vb2_buf_init,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.start_streaming = c3_isp_stats_vb2_start_streaming,
+	.stop_streaming = c3_isp_stats_vb2_stop_streaming,
+};
+
+int c3_isp_stats_register(struct c3_isp_device *isp)
+{
+	struct c3_isp_stats *stats = &isp->stats;
+	struct video_device *vdev = &stats->vdev;
+	struct vb2_queue *vb2_q = &stats->vb2_q;
+	int ret;
+
+	memset(stats, 0, sizeof(*stats));
+	stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
+	stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_info);
+	stats->isp = isp;
+	INIT_LIST_HEAD(&stats->pending);
+	spin_lock_init(&stats->buff_lock);
+
+	mutex_init(&stats->lock);
+
+	snprintf(vdev->name, sizeof(vdev->name), "isp-stats");
+	vdev->fops = &isp_stats_v4l2_fops;
+	vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
+	vdev->v4l2_dev = &isp->v4l2_dev;
+	vdev->lock = &stats->lock;
+	vdev->minor = -1;
+	vdev->queue = vb2_q;
+	vdev->release = video_device_release_empty;
+	vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
+	vdev->vfl_dir = VFL_DIR_RX;
+	video_set_drvdata(vdev, stats);
+
+	vb2_q->drv_priv = stats;
+	vb2_q->mem_ops = &vb2_dma_contig_memops;
+	vb2_q->ops = &isp_stats_vb2_ops;
+	vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
+	vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
+	vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
+	vb2_q->dev = isp->dev;
+	vb2_q->lock = &stats->lock;
+	vb2_q->min_queued_buffers = 2;
+
+	ret = vb2_queue_init(vb2_q);
+	if (ret)
+		goto err_destroy;
+
+	stats->pad.flags = MEDIA_PAD_FL_SINK;
+	ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
+	if (ret)
+		goto err_queue_release;
+
+	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+	if (ret) {
+		dev_err(isp->dev,
+			"Failed to register %s: %d\n", vdev->name, ret);
+		goto err_entity_cleanup;
+	}
+
+	return 0;
+
+err_entity_cleanup:
+	media_entity_cleanup(&vdev->entity);
+err_queue_release:
+	vb2_queue_release(vb2_q);
+err_destroy:
+	mutex_destroy(&stats->lock);
+	return ret;
+}
+
+void c3_isp_stats_unregister(struct c3_isp_device *isp)
+{
+	struct c3_isp_stats *stats = &isp->stats;
+
+	vb2_queue_release(&stats->vb2_q);
+	media_entity_cleanup(&stats->vdev.entity);
+	video_unregister_device(&stats->vdev);
+	mutex_destroy(&stats->lock);
+}
+
+int c3_isp_stats_done(struct c3_isp_device *isp)
+{
+	struct c3_isp_stats *stats = &isp->stats;
+	struct c3_isp_vb2_buffer *buff = stats->buff;
+	unsigned long flags;
+
+	if (!stats->is_streaming)
+		return -EINVAL;
+
+	spin_lock_irqsave(&stats->buff_lock, flags);
+
+	if (buff) {
+		buff->vb.sequence = stats->isp->frm_sequence;
+		buff->vb.vb2_buf.timestamp = ktime_get();
+		buff->vb.field = V4L2_FIELD_NONE;
+		vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
+	}
+
+	c3_isp_stats_cfg_buff(stats);
+
+	spin_unlock_irqrestore(&stats->buff_lock, flags);
+
+	return 0;
+}
diff --git a/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
new file mode 100644
index 000000000000..84ff5741357a
--- /dev/null
+++ b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
@@ -0,0 +1,537 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef __C3_ISP_CONFIG_H__
+#define __C3_ISP_CONFIG_H__
+
+#define AF_STAT_BLKH_NUM             17
+#define AF_STAT_BLKV_NUM             15
+#define AF_STAT_BLK_NUM              (AF_STAT_BLKH_NUM * AF_STAT_BLKV_NUM)
+/* AF stats block size need to be aligned with 2 */
+#define AF_STAT_BLK_SIZE             ALIGN(AF_STAT_BLK_NUM, 2)
+#define AE_HISTOGRAM_SIZE	     1024
+#define AE_STAT_BLKH_NUM             17
+#define AE_STAT_BLKV_NUM             15
+#define AE_STAT_BLK_NUM              (AE_STAT_BLKH_NUM * AE_STAT_BLKV_NUM)
+/* AE stats block size need to be aligned with 2 */
+#define AE_STAT_BLK_SIZE             ALIGN(AE_STAT_BLK_NUM, 2)
+#define AE_BLOCK_WT_NUM              255
+#define AE_BLK_WT_DATA_NUM_OF_GROUP  8
+#define AWB_STAT_BLKH_NUM            32
+#define AWB_STAT_BLKV_NUM            24
+#define AWB_STAT_BLK_NUM             (AWB_STAT_BLKH_NUM * AWB_STAT_BLKV_NUM)
+/* AWB stats block size need to be aligned with 2 */
+#define AWB_STAT_BLK_SIZE            ALIGN(AWB_STAT_BLK_NUM, 2)
+#define AWB_BLOCK_WT_NUM             768
+#define AWB_BLK_WT_DATA_NUM_OF_GROUP 8
+#define AWB_STAT_BLC20_NUM           4
+#define AWB_STAT_GAIN10_NUM          4
+#define BLC_OFFSET_NUM               5
+#define GAMMA_LUT_GROUP_NUM          4
+#define GAMMA_LUT_POINT_NUM          129
+#define GAMMA_LUT_DATA_NUM_OF_GROUP  2
+
+/**
+ * struct awb_zone_stats - AWB statistics of a block
+ *
+ * AWB zone stats is aligned with 8 bytes
+ *
+ * @rg: the ratio of R / G in a zone
+ * @bg: the ratio of B / G in a zone
+ * @pixel_sum: the total number of pixels used in a zone
+ */
+struct awb_zone_stats {
+	u16 rg;
+	u16 bg;
+	u32 pixel_sum;
+};
+
+/**
+ * struct awb_stats_info - Auto white balance statistics information.
+ *
+ * AWB statistical information of all blocks.
+ *
+ * @awb_stats: array of auto white balance statistics
+ */
+struct awb_stats_info {
+	struct awb_zone_stats awb_stats[AWB_STAT_BLK_SIZE];
+};
+
+/**
+ * struct ae_zone_stats - AE statistics of a block
+ *
+ * AE zone stats is aligned with 8 bytes.
+ * This is a 5-bin histogram and the total sum is
+ * normalized to 0xffff.
+ * So hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4)
+ *
+ * @hist0: the global normalized pixel count for bin 0
+ * @hist1: the global normalized pixel count for bin 1
+ * @hist3: the global normalized pixel count for bin 3
+ * @hist4: the global normalized pixel count for bin 4
+ */
+struct ae_zone_stats {
+	u16 hist0;
+	u16 hist1;
+	u16 hist3;
+	u16 hist4;
+};
+
+/**
+ * struct ae_stats_info - Exposure statistics information
+ *
+ * AE statistical information consists of
+ * all blocks information and a 1024-bin histogram.
+ *
+ * @ae_stats: array of auto exposure block statistics
+ * @hist: a 1024-bin histogram for the entire image
+ */
+struct ae_stats_info {
+	struct ae_zone_stats ae_stats[AE_STAT_BLK_SIZE];
+	u32 hist[AE_HISTOGRAM_SIZE];
+};
+
+/**
+ * struct af_zone_stats - AF statistics of a block
+ *
+ * AF block stats is aligned with 8 bytes.
+ * The zonal accumulated contrast metrics are stored
+ * in floating point format with 16 bits mantissa and
+ * 5 or 6 bits exponent.
+ * Apart from contrast metrics we accumulate squared image and
+ * quartic image data over the zone.
+ *
+ * @i2_mat: the mantissa of zonal squared image pixel sum
+ * @i4_mat: the mantissa of zonal quartic image pixel sum
+ * @e4_mat: the mantissa of zonal multi-directional quartic edge sum
+ * @i2_exp: the exponent of zonal squared image pixel sum
+ * @i4_exp: the exponent of zonal quartic image pixel sum
+ * @e4_exp: the exponent of zonal multi-directional quartic edge sum
+ */
+struct af_zone_stats {
+	u16 i2_mat;
+	u16 i4_mat;
+	u16 e4_mat;
+	u16 i2_exp: 5;
+	u16 i4_exp: 6;
+	u16 e4_exp: 5;
+};
+
+/**
+ * struct af_stats_info - Auto Focus statistics information
+ *
+ * AF statistical information of each block
+ *
+ * @af_stats: array of auto focus block statistics
+ */
+struct af_stats_info {
+	struct af_zone_stats af_stats[AF_STAT_BLK_SIZE];
+};
+
+/**
+ * struct c3_isp_stats_info - V4L2_META_FMT_C3ISP_STATS
+ *
+ * Contains ISP statistics
+ *
+ * @awb_stats: auto white balance stats
+ * @ae_stats: auto exposure stats
+ * @af_stats: auto focus stats
+ */
+struct c3_isp_stats_info {
+	struct awb_stats_info awb_stats;
+	struct ae_stats_info ae_stats;
+	struct af_stats_info af_stats;
+};
+
+/**
+ * enum c3_isp_param_buffer_version -  C3 ISP parameters block versioning
+ *
+ * @C3_ISP_PARAM_BUFFER_V0: First version of C3 ISP parameters block
+ */
+enum c3_isp_param_buffer_version {
+	C3_ISP_PARAM_BUFFER_V0,
+};
+
+/**
+ * enum c3_isp_param_block_type - Enumeration of C3 ISP parameter blocks
+ *
+ * Each block configures a specific processing block of the C3 ISP.
+ * The block type allows the driver to correctly interpret
+ * the parameters block data.
+ *
+ * @C3_ISP_PARAM_BLOCK_WB_CHANGE: White balance change parameters
+ * @C3_ISP_PARAM_BLOCK_WB_LUMA: White balance Luma-based parameters
+ * @C3_ISP_PARAM_BLOCK_WB_TRIANGLE: White balance triangle parameters
+ * @C3_ISP_PARAM_BLOCK_AWB_STATS: AWB statistics parameters
+ * @C3_ISP_PARAM_BLOCK_AE_STATS: AE statistics parameters
+ * @C3_ISP_PARAM_BLOCK_AF_STATS: AF statistics parameters
+ * @C3_ISP_PARAM_BLOCK_PST_GAMMA: post gamma parameters
+ * @C3_ISP_PARAM_BLOCK_DMSC: demosaic parameters
+ * @C3_ISP_PARAM_BLOCK_CCM: Color correction matrix parameters
+ * @C3_ISP_PARAM_BLOCK_CSC: Color space conversion parameters
+ * @C3_ISP_PARAM_BLOCK_BLC: Black level correction parameters
+ * @C3_ISP_PARAM_BLOCK_SENTINEL: First non-valid block index
+ */
+enum c3_isp_param_block_type {
+	C3_ISP_PARAM_BLOCK_WB_CHANGE,
+	C3_ISP_PARAM_BLOCK_WB_LUMA,
+	C3_ISP_PARAM_BLOCK_WB_TRIANGLE,
+	C3_ISP_PARAM_BLOCK_AWB_STATS,
+	C3_ISP_PARAM_BLOCK_AE_STATS,
+	C3_ISP_PARAM_BLOCK_AF_STATS,
+	C3_ISP_PARAM_BLOCK_PST_GAMMA,
+	C3_ISP_PARAM_BLOCK_DMSC,
+	C3_ISP_PARAM_BLOCK_CCM,
+	C3_ISP_PARAM_BLOCK_CSC,
+	C3_ISP_PARAM_BLOCK_BLC,
+	C3_ISP_PARAM_BLOCK_SENTINEL
+};
+
+/**
+ * struct c3_isp_param_block_header - C3 ISP parameter block header
+ *
+ * This structure represents the common part of all the ISP configuration
+ * blocks. Each parameters block shall embed an instance of this structure type
+ * as its first member, followed by the block-specific configuration data. The
+ * driver inspects this common header to discern the block type and its size and
+ * properly handle the block content by casting it to the correct block-specific
+ * type.
+ *
+ * @type: The parameters block type (enum c3_isp_param_block_type)
+ * @enabled: Block enabled/disabled flag
+ * @size: Size (in bytes) of parameters block
+ */
+
+struct c3_isp_param_block_header {
+	enum c3_isp_param_block_type type;
+	bool enabled;
+	size_t size;
+};
+
+/**
+ * struct wb_change_cfg - White Balance configuration
+ *
+ * @header: The C3 ISP parameters block header
+ * @wb_gain: white balance gain of each color
+ *	wb_gain[0]: Gr gain, range 0~0xfff
+ *	wb_gain[1]: R gain, range 0~0xfff
+ *	wb_gain[2]: B gain, range 0~0xfff
+ *	wb_gain[3]: Gb gain, range 0~0xfff
+ *	wb_gain[4]: Ir gain, range 0~0xfff
+ * @wb_limit: white balance limit of each color
+ *	wb_limit[0]: Gr limit, 16 bits float
+ *	wb_limit[1]: R limit, 16 bits float
+ *	wb_limit[2]: B limit, 16 bits float
+ *	wb_limit[3]: Gb limit, 16 bits float
+ *	wb_limit[4]: Ir limit, 16 bits float
+ * @ae_gain_grbgi: Gain of each color before blending to luma
+ *	ae_gain_grbgi[0]: Gr gain, range 0~255
+ *	ae_gain_grbgi[1]: R gain, range 0~255
+ *	ae_gain_grbgi[2]: B gain, range 0~255
+ *	ae_gain_grbgi[3]: Gb gain, range 0~255
+ *	ae_gain_grbgi[4]: Ir gain, range 0~255
+ * @ae_bl12_grbgi: Offset of each color before ae_gain_grbgi
+ *	ae_bl12_grbgi[0]: Gr offset, range 0~4095
+ *	ae_bl12_grbgi[1]: R offset, range 0~4095
+ *	ae_bl12_grbgi[2]: B offset, range 0~4095
+ *	ae_bl12_grbgi[3]: Gb offset, range 0~4095
+ *	ae_bl12_grbgi[4]: Ir offset, range 0~4095
+ */
+struct wb_change_cfg {
+	struct c3_isp_param_block_header header;
+	u32 wb_gain[5];
+	u32 wb_limit[5];
+	u32 ae_gain_grbgi[5];
+	u32 ae_bl12_grbgi[5];
+};
+
+/**
+ * struct wb_luma_cfg - White Balance Luma-based configuration
+ *
+ * @header: The C3 ISP parameters block header
+ * @awb_stat_blc20: BLC in AWB statistic
+ *	awb_stat_blc20[0]: Gr blc, range 0~0xfffff
+ *	awb_stat_blc20[1]: R blc, range 0~0xfffff
+ *	awb_stat_blc20[2]: B blc, range 0~0xfffff
+ *	awb_stat_blc20[3]: Gb blc, range 0~0xfffff
+ * @awb_stat_gain10: Gain in AWB statistic
+ *	awb_stat_gain10[0]: Gr gain, range 0~1023
+ *	awb_stat_gain10[1]: R gain, range 0~1023
+ *	awb_stat_gain10[2]: B gain, range 0~1023
+ *	awb_stat_gain10[3]: Gb gain, range 0~1023
+ * @awb_stat_satur_low: AWB statistic under-saturation threshold
+ *	value: range 0~65535
+ * @awb_stat_satur_high: AWB statistic over-saturation threshold
+ *	value: range 0~65535
+ */
+struct wb_luma_cfg {
+	struct c3_isp_param_block_header header;
+	u32 awb_stat_blc20[AWB_STAT_BLC20_NUM];
+	u32 awb_stat_gain10[AWB_STAT_GAIN10_NUM];
+	u32 awb_stat_satur_low;
+	u32 awb_stat_satur_high;
+};
+
+/**
+ * struct wb_triangle_cfg - White Balance Triangle
+ *
+ * @header: The C3 ISP parameters block header
+ * @awb_stat_satur_vald: AWB statistic over saturation control
+ *	value: 0: disable, 1: enable
+ * @awb_stat_rg_min: min value of r/g
+ *	value: 0~4095
+ * @awb_stat_rg_max: max value of r/g
+ *	value: 0~4095
+ * @awb_stat_bg_min: min value of b/g
+ *	value: 0~4095
+ * @awb_stat_bg_max: max value of b/g
+ *	value: 0~4095
+ * @awb_stat_rg_low: low value of r/g
+ *	value: 0~4095
+ * @awb_stat_rg_high: high value of r/g
+ *	value: 0~4095
+ * @awb_stat_bg_low: low value of b/g
+ *	value: 0~4095
+ * @awb_stat_bg_high: high value of b/g
+ *	value: 0~4095
+ */
+struct wb_triangle_cfg {
+	struct c3_isp_param_block_header header;
+	u32 awb_stat_satur_vald;
+	u32 awb_stat_rg_min;
+	u32 awb_stat_rg_max;
+	u32 awb_stat_bg_min;
+	u32 awb_stat_bg_max;
+	u32 awb_stat_rg_low;
+	u32 awb_stat_rg_high;
+	u32 awb_stat_bg_low;
+	u32 awb_stat_bg_high;
+};
+
+/**
+ * struct awb_stats_cfg - AWB statistics configuration
+ *
+ * This structure contains AWB statistics control information.
+ *
+ * @header: The C3 ISP parameters block header
+ * @awb_stat_switch: the switch of AWB statistics
+ *	value: 0~7
+ * @awb_stat_blk_weight: Array of weights for AWB statistics blocks
+ *	value: 0~15
+ */
+struct awb_stats_cfg {
+	struct c3_isp_param_block_header header;
+	u8 awb_stat_switch;
+	u32 awb_stat_blk_weight[AWB_BLOCK_WT_NUM];
+};
+
+/**
+ * struct ae_stats_cfg - AE statistics configuration
+ *
+ * This structure contains AE statistics control information.
+ *
+ * @header: The C3 ISP parameters block header
+ * @ae_stat_switch: the switch of AE statistics
+ *	value: 0~3
+ * @ae_stat_blk_weight: Array of weights for AE statistics blocks
+ *	value: 0~15
+ */
+struct ae_stats_cfg {
+	struct c3_isp_param_block_header header;
+	u8 ae_stat_switch;
+	u32 ae_stat_blk_weight[AE_BLOCK_WT_NUM];
+};
+
+/**
+ * struct af_stats_cfg - AF statistics configuration
+ *
+ * This structure contains AF statistics control information.
+ *
+ * @header: The C3 ISP parameters block header
+ * @af_stat_switch: the switch of AF statistics
+ *	value: 0~3
+ */
+struct af_stats_cfg {
+	struct c3_isp_param_block_header header;
+	u8 af_stat_switch;
+};
+
+/**
+ * struct pst_gamma_cfg - Post gamma configuration
+ *
+ * This structure contains post gamma parameters
+ *
+ * @header: The C3 ISP parameters block header
+ * @pst_gamma_lut: LUT for P-Stitch gamma
+ *	value: 0~65535
+ */
+struct pst_gamma_cfg {
+	struct c3_isp_param_block_header header;
+	u32 pst_gamma_lut[GAMMA_LUT_GROUP_NUM][GAMMA_LUT_POINT_NUM];
+};
+
+/**
+ * struct dmsc_cfg - Demosaic configuration
+ *
+ * This structure contains demosaic parameters
+ *
+ * @header: The C3 ISP parameters block header
+ */
+struct dmsc_cfg {
+	struct c3_isp_param_block_header header;
+};
+
+/**
+ * struct ccm_cfg - ISP CCM configuration
+ *
+ * This structure holds the parameters for configuring the CCM,
+ * which is used for color correction.
+ *
+ * @header: The C3 ISP parameters block header
+ * @ccm_4x3matrix: A 3x4 matrix used for color correction
+ *	value: 0~8191
+ */
+struct ccm_cfg {
+	struct c3_isp_param_block_header header;
+	u32 ccm_4x3matrix[3][4];
+};
+
+/**
+ * struct csc_cfg - ISP Color Space Conversion configuration
+ *
+ * This structure contains settings for color space conversion.
+ *
+ * @header: The C3 ISP parameters block header
+ * @cm0_offset_inp: Input offset values for the 0-order color matrix
+ *	value: 0~8191
+ * @cm0_offset_oup: Output offset values for the 0-order color matrix
+ *	value: 0~8191
+ * @cm0_3x3mtrx_rs: matrix right shift for cm0
+ *	value: 0~3
+ * @cm0_3x3matrix: A 3x3 matrix used for the color matrix
+ *	value: 0~8191
+ */
+struct csc_cfg {
+	struct c3_isp_param_block_header header;
+	u32 cm0_offset_inp[3];
+	u32 cm0_offset_oup[3];
+	u32 cm0_3x3mtrx_rs;
+	u32 cm0_3x3matrix[3][3];
+};
+
+/**
+ * struct blc_cfg - ISP Black Level Correction (BLC) configuration
+ *
+ * This structure holds the parameters for BLC in image processing.
+ *
+ * @header: The C3 ISP parameters block header
+ * @fe_bl_ofst: Array of front-end BLC offsets for each color each channels
+ *	fe_bl_ofst[0]: Gr blc offset, range 0~0x1fffff
+ *	fe_bl_ofst[1]: R blc offset, range 0~0x1fffff
+ *	fe_bl_ofst[2]: B blc offset, range 0~0x1fffff
+ *	fe_bl_ofst[3]: Gb blc offset, range 0~0x1fffff
+ *	fe_bl_ofst[4]: Ir blc offset, range 0~0x1fffff
+ * @blc_ofst: Array of LSWB BLC offsets
+ *	blc_ofst[0]: Gr blc offset, 16 bits float
+ *	blc_ofst[1]: R blc offset, 16 bits float
+ *	blc_ofst[2]: B blc offset, 16 bits float
+ *	blc_ofst[3]: Gb blc offset, 16 bits float
+ *	blc_ofst[4]: Ir blc offset, 16 bits float
+ */
+struct blc_cfg {
+	struct c3_isp_param_block_header header;
+	u32 fe_bl_ofst[BLC_OFFSET_NUM];
+	u32 blc_ofst[BLC_OFFSET_NUM];
+};
+
+/**
+ * define C3_ISP_PARAMS_MAX_SIZE - Maximum size of all C3 ISP Parameters
+ *
+ * Though the parameters for the C3 ISP are passed as optional blocks, the
+ * driver still needs to know the absolute maximum size so that it can allocate
+ * a buffer sized appropriately to accommodate userspace attempting to set all
+ * possible parameters in a single frame.
+ */
+#define C3_ISP_PARAMS_MAX_SIZE                 \
+	(sizeof(struct wb_change_cfg) +        \
+	sizeof(struct wb_luma_cfg)   +         \
+	sizeof(struct wb_triangle_cfg) +       \
+	sizeof(struct awb_stats_cfg) +          \
+	sizeof(struct ae_stats_cfg) +           \
+	sizeof(struct af_stats_cfg) +           \
+	sizeof(struct pst_gamma_cfg) +         \
+	sizeof(struct dmsc_cfg) +              \
+	sizeof(struct ccm_cfg) +               \
+	sizeof(struct csc_cfg) +               \
+	sizeof(struct blc_cfg))
+
+/**
+ * struct c3_isp_params_buffer - C3 ISP configuration parameters
+ *
+ * This struct contains the configuration parameters of the C3 ISP
+ * algorithms, serialized by userspace into an opaque data buffer. Each
+ * configuration parameter block is represented by a block-specific structure
+ * which contains a :c:type:`c3_isp_param_block_header` entry as first
+ * member. Userspace populates the @data buffer with configuration parameters
+ * for the blocks that it intends to configure. As a consequence, the data
+ * buffer effective size changes according to the number of ISP blocks that
+ * userspace intends to configure.
+ *
+ * The parameters buffer is versioned by the @version field to allow modifying
+ * and extending its definition. Userspace should populate the @version field to
+ * inform the driver about the version it intends to use. The driver will parse
+ * and handle the @data buffer according to the data layout specific to the
+ * indicated revision and return an error if the desired revision is not
+ * supported.
+ *
+ * For each ISP block that userspace wants to configure, a block-specific
+ * structure is appended to the @data buffer, one after the other without gaps
+ * in between nor overlaps. Userspace shall populate the @total_size field with
+ * the effective size, in bytes, of the @data buffer.
+ *
+ * The expected memory layout of the parameters buffer is::
+ *
+ *	+-------------------- struct c3_isp_params_buffer ------------------+
+ *	| version = C3_ISP_PARAM_BUFFER_V0;                                   |
+ *	| total_size = sizeof(sizeof(struct wb_change_cfg))                   |
+ *	|              sizeof(sizeof(struct wb_luma_cfg));                    |
+ *	| +------------------------- data  ---------------------------------+ |
+ *	| | +------------------ struct wb_change_cfg) --------------------+ | |
+ *	| | | +---------  struct c3_isp_param_block_header header  -----+ | | |
+ *	| | | | type = C3_ISP_PARAM_BLOCK_WB_CHANGE;                    | | | |
+ *	| | | | enabled = true;                                            | | | |
+ *	| | | | size =                                                  | | | |
+ *	| | | |    sizeof(struct c3_isp_param_block_header header);     | | | |
+ *	| | | +---------------------------------------------------------+ | | |
+ *	| | | wb_gain[5] = ...;                                           | | |
+ *	| | | wb_limit[5] = ...;                                          | | |
+ *	| | | ae_gain_grbgi[5] = ...;                                     | | |
+ *	| | | ae_bl12_grbgi[5] = ...;                                     | | |
+ *	| | +------------------ struct wb_luma_cfg -----------------------+ | |
+ *	| | | +---------- struct c3_isp_param_block_header header ------+ | | |
+ *	| | | | type = C3_ISP_PARAM_BLOCK_WB_LUMA;                      | | | |
+ *	| | | | enabled = true;                                            | | | |
+ *	| | | | size = sizeof(struct wb_luma_cfg);                      | | | |
+ *	| | | +---------------------------------------------------------+ | | |
+ *	| | | awb_stat_blc20[4] = ...;                                    | | |
+ *	| | | awb_stat_gain10[4] = ...;                                   | | |
+ *	| | | awb_stat_satur_low = ...;                                   | | |
+ *	| | | awb_stat_satur_high = ...;                                  | | |
+ *	| | +-------------------------------------------------------------+ | |
+ *	| +-----------------------------------------------------------------+ |
+ *	+---------------------------------------------------------------------+
+ *
+ * @version: The C3 ISP parameters buffer version
+ * @total_size: The C3 ISP configuration data effective size,
+ *		excluding this header
+ * @data: The C3 ISP configuration blocks data
+ */
+struct c3_isp_params_buffer {
+	enum c3_isp_param_buffer_version version;
+	size_t total_size;
+	u8 data[C3_ISP_PARAMS_MAX_SIZE];
+};
+
+#endif

-- 
2.46.1



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

* [PATCH v3 8/9] Documentation: media: add documentation file metafmt-c3-isp.rst
  2024-09-18  6:07 [PATCH v3 0/9] Amlogic C3 ISP support Keke Li via B4 Relay
                   ` (6 preceding siblings ...)
  2024-09-18  6:07 ` [PATCH v3 7/9] media: platform: Add c3 ISP driver Keke Li via B4 Relay
@ 2024-09-18  6:07 ` Keke Li via B4 Relay
  2024-11-07 10:38   ` Jacopo Mondi
  2024-09-18  6:07 ` [PATCH v3 9/9] Documentation: media: add documentation file c3-isp.rst Keke Li via B4 Relay
  8 siblings, 1 reply; 37+ messages in thread
From: Keke Li via B4 Relay @ 2024-09-18  6:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-media, devicetree, linux-kernel, kieran.bingham,
	laurent.pinchart, dan.scally, Keke Li

From: Keke Li <keke.li@amlogic.com>

Add the file 'metafmt-c3-isp.rst' that documents
the meta format of c3-isp.

Signed-off-by: Keke Li <keke.li@amlogic.com>
---
 .../userspace-api/media/v4l/meta-formats.rst       |  1 +
 .../userspace-api/media/v4l/metafmt-c3-isp.rst     | 39 ++++++++++++++++++++++
 2 files changed, 40 insertions(+)

diff --git a/Documentation/userspace-api/media/v4l/meta-formats.rst b/Documentation/userspace-api/media/v4l/meta-formats.rst
index c6e56b5888bc..3a420747f6e7 100644
--- a/Documentation/userspace-api/media/v4l/meta-formats.rst
+++ b/Documentation/userspace-api/media/v4l/meta-formats.rst
@@ -12,6 +12,7 @@ These formats are used for the :ref:`metadata` interface only.
 .. toctree::
     :maxdepth: 1
 
+    metafmt-c3-isp
     metafmt-d4xx
     metafmt-generic
     metafmt-intel-ipu3
diff --git a/Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst b/Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst
new file mode 100644
index 000000000000..bbaf3542c1ca
--- /dev/null
+++ b/Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst
@@ -0,0 +1,39 @@
+.. SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+
+.. _v4l2-meta-fmt-c3isp-stats:
+.. _v4l2-meta-fmt-c3isp-params:
+
+***********************************************************************
+V4L2_META_FMT_C3ISP_STATS ('CSTS'), V4L2_META_FMT_C3ISP_PARAMS ('CPRM')
+***********************************************************************
+
+.. c3_isp_stats_info
+
+3A statistics
+=============
+
+The C3 ISP can collect different statistics over an input Bayer frame.
+Those statistics are obtained from the "isp-stats" metadata capture video nodes,
+using the :c:type:`v4l2_meta_format` interface.
+They are formatted as described by the :c:type:`c3_isp_stats_info` structure.
+
+The statistics collected are  Auto-white balance,
+Auto-exposure and Auto-focus information.
+
+.. c3_isp_params_buffer
+
+Pipeline parameters
+===================
+
+The pipeline parameters are passed to the "isp-params" metadata
+output video nodes, using the :c:type:`v4l2_meta_format` interface. They are
+formatted as described by the :c:type:`c3_isp_params_buffer` structure.
+
+The structure c3_isp_params_buffer includes the necessary parameters
+required by the ISP.
+These parameters are written to the ISP hardware.
+
+Amlogic C3 ISP uAPI data types
+===============================
+
+.. kernel-doc:: drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h

-- 
2.46.1



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

* [PATCH v3 9/9] Documentation: media: add documentation file c3-isp.rst
  2024-09-18  6:07 [PATCH v3 0/9] Amlogic C3 ISP support Keke Li via B4 Relay
                   ` (7 preceding siblings ...)
  2024-09-18  6:07 ` [PATCH v3 8/9] Documentation: media: add documentation file metafmt-c3-isp.rst Keke Li via B4 Relay
@ 2024-09-18  6:07 ` Keke Li via B4 Relay
  2024-11-07  9:07   ` Jacopo Mondi
  8 siblings, 1 reply; 37+ messages in thread
From: Keke Li via B4 Relay @ 2024-09-18  6:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-media, devicetree, linux-kernel, kieran.bingham,
	laurent.pinchart, dan.scally, Keke Li

From: Keke Li <keke.li@amlogic.com>

Add the file 'c3-isp.rst' that documents the c3-isp driver.

Signed-off-by: Keke Li <keke.li@amlogic.com>
---
 Documentation/admin-guide/media/c3-isp.dot      | 26 +++++++
 Documentation/admin-guide/media/c3-isp.rst      | 96 +++++++++++++++++++++++++
 Documentation/admin-guide/media/v4l-drivers.rst |  1 +
 MAINTAINERS                                     | 10 +++
 4 files changed, 133 insertions(+)

diff --git a/Documentation/admin-guide/media/c3-isp.dot b/Documentation/admin-guide/media/c3-isp.dot
new file mode 100644
index 000000000000..0cc1b8b96404
--- /dev/null
+++ b/Documentation/admin-guide/media/c3-isp.dot
@@ -0,0 +1,26 @@
+digraph board {
+	rankdir=TB
+	n00000001 [label="{{<port0> 0 | <port1> 1} | isp-core\n/dev/v4l-subdev0 | {<port2> 2 | <port3> 3}}", shape=Mrecord, style=filled, fillcolor=green]
+	n00000001:port3 -> n00000006:port0 [style=bold]
+	n00000001:port3 -> n00000009:port0 [style=bold]
+	n00000001:port3 -> n0000000c:port0 [style=bold]
+	n00000001:port2 -> n00000020 [style=bold]
+	n00000006 [label="{{<port0> 0} | isp-resizer0\n/dev/v4l-subdev1 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
+	n00000006:port1 -> n00000014 [style=bold]
+	n00000009 [label="{{<port0> 0} | isp-resizer1\n/dev/v4l-subdev2 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
+	n00000009:port1 -> n00000018 [style=bold]
+	n0000000c [label="{{<port0> 0} | isp-resizer2\n/dev/v4l-subdev3 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
+	n0000000c:port1 -> n0000001c [style=bold]
+	n0000000f [label="{{<port0> 0} | mipi-adapter\n/dev/v4l-subdev4 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
+	n0000000f:port1 -> n00000001:port0 [style=bold]
+	n00000014 [label="isp-video0\n/dev/video0", shape=box, style=filled, fillcolor=yellow]
+	n00000018 [label="isp-video1\n/dev/video1", shape=box, style=filled, fillcolor=yellow]
+	n0000001c [label="isp-video2\n/dev/video2", shape=box, style=filled, fillcolor=yellow]
+	n00000020 [label="isp-stats\n/dev/video3", shape=box, style=filled, fillcolor=yellow]
+	n00000024 [label="isp-params\n/dev/video4", shape=box, style=filled, fillcolor=yellow]
+	n00000024 -> n00000001:port1 [style=bold]
+	n00000038 [label="{{<port0> 0} | mipi-csi2\n/dev/v4l-subdev5 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
+	n00000038:port1 -> n0000000f:port0 [style=bold]
+	n0000003d [label="{{} | imx290 2-001a\n/dev/v4l-subdev6 | {<port0> 0}}", shape=Mrecord, style=filled, fillcolor=green]
+	n0000003d:port0 -> n00000038:port0 [style=bold]
+}
diff --git a/Documentation/admin-guide/media/c3-isp.rst b/Documentation/admin-guide/media/c3-isp.rst
new file mode 100644
index 000000000000..fab10c962465
--- /dev/null
+++ b/Documentation/admin-guide/media/c3-isp.rst
@@ -0,0 +1,96 @@
+.. SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+
+.. include:: <isonum.txt>
+
+=================================================
+Amlogic C3 Image Signal Processing (C3ISP) driver
+=================================================
+
+Introduction
+============
+
+This file documents the Amlogic C3ISP driver located under
+drivers/media/platform/amlogic/c3-isp.
+
+The current version of the driver supports the C3ISP found on
+Amlogic C308L processor.
+
+The driver implements V4L2, Media controller and V4L2 subdev interfaces.
+Camera sensor using V4L2 subdev interface in the kernel is supported.
+
+The driver has been tested on AW419-C308L-Socket platform.
+
+Anlogic Camera hardware
+=======================
+
+The Camera hardware found on C308L processors and supported by
+the driver consists of:
+
+- 1 MIPI-CSI2 module. It handle the Physical layer of the CSI2 receivers and
+  receive MIPI data.
+  A separate camera sensor can be connected to MIPI-CSi2 module.
+- 1 MIPI-ADAPTER module. Organize MIPI data to meet ISP input requirements and
+  send MIPI data to ISP
+- 1 ISP (Image Signal Processing) module. Contain a pipeline of image processing
+  hardware blocks.
+  The ISP pipeline contains three scalers at the end.
+  The ISP also contains the DMA interface which writes the output data to memory.
+
+Supported functionality
+=======================
+
+The current version of the driver supports:
+
+- Input from camera sensor via MIPI-CSI2;
+
+- Pixel output interface of ISP
+
+  - Scaling support. Configuration of the scaler module
+    for downscalling with ratio up to 8x.
+
+Driver Architecture and Design
+==============================
+
+The driver implements the V4L2 subdev interface. With the goal to model the
+hardware links between the modules and to expose a clean, logical and usable
+interface, the driver is split into V4L2 sub-devices as follows:
+
+- 1 mipi-csi2 sub-device - mipi-csi2 is represented by a single sub-device.
+- 1 mipi-adapter sub-device - mipi-adapter is represented by a single sub-devices.
+- 1 isp-core sub-device - isp-core is represented by a single sub-devices.
+- 3 isp-resizer sub-devices - isp-resizer is represented by a number of sub-devices
+  equal to the number of capture device.
+
+isp-core sub-device is linked to 2 separate video device nodes and
+3 isp-resizer sub-devices nodes.
+
+- 1 capture statistics video device node.
+- 1 output parameters video device node.
+- 3 isp-resizer sub-device nodes.
+
+isp-resizer sub-device is linked to capture video device node.
+
+- isp-resizer0 is linked to isp-cap0
+- isp-resizer1 is linked to isp-cap1
+- isp-resizer2 is linked to isp-cap2
+
+The media controller pipeline graph is as follows (with connected a
+IMX290 camera sensor):
+
+.. _isp_topology_graph:
+
+.. kernel-figure:: c3-isp.dot
+    :alt:   c3-isp.dot
+    :align: center
+
+    Media pipeline topology
+
+Implementation
+==============
+
+Runtime configuration of the hardware via 'isp-params' video device node.
+Acquiring statistics of ISP hardware via 'isp-stats' video device node.
+Acquiring output image of ISP hardware via 'isp-video[0, 2]' video device node.
+
+The output size of the scaler module in the ISP is configured with
+the pixel format of 'isp-video[0, 2]' video device node.
diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst
index b6af448b9fe9..be0a8a860f39 100644
--- a/Documentation/admin-guide/media/v4l-drivers.rst
+++ b/Documentation/admin-guide/media/v4l-drivers.rst
@@ -10,6 +10,7 @@ Video4Linux (V4L) driver-specific documentation
 	:maxdepth: 2
 
 	bttv
+	c3-isp
 	cafe_ccic
 	cx88
 	fimc
diff --git a/MAINTAINERS b/MAINTAINERS
index 31168c05f304..954dd9bdf77e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1209,6 +1209,16 @@ F:	Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
 F:	drivers/perf/amlogic/
 F:	include/soc/amlogic/
 
+AMLOGIC ISP DRIVER
+M:	Keke Li <keke.li@amlogic.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/admin-guide/media/c3-isp.dot
+F:	Documentation/admin-guide/media/c3-isp.rst
+F:	Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml
+F:	Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst
+F:	drivers/media/platform/amlogic/c3-isp/
+
 AMLOGIC MIPI ADAPTER DRIVER
 M:	Keke Li <keke.li@amlogic.com>
 L:	linux-media@vger.kernel.org

-- 
2.46.1



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

* Re: [PATCH v3 5/9] dt-bindings: media: Add amlogic,c3-isp.yaml
  2024-09-18  6:07 ` [PATCH v3 5/9] dt-bindings: media: Add amlogic,c3-isp.yaml Keke Li via B4 Relay
@ 2024-09-21  0:32   ` Rob Herring (Arm)
  0 siblings, 0 replies; 37+ messages in thread
From: Rob Herring (Arm) @ 2024-09-21  0:32 UTC (permalink / raw)
  To: Keke Li
  Cc: linux-kernel, Mauro Carvalho Chehab, linux-media,
	laurent.pinchart, Conor Dooley, devicetree, dan.scally,
	kieran.bingham, Krzysztof Kozlowski


On Wed, 18 Sep 2024 14:07:16 +0800, Keke Li wrote:
> c3-isp is used to process raw image.
> 
> Signed-off-by: Keke Li <keke.li@amlogic.com>
> ---
>  .../devicetree/bindings/media/amlogic,c3-isp.yaml  | 85 ++++++++++++++++++++++
>  1 file changed, 85 insertions(+)
> 

Reviewed-by: Rob Herring (Arm) <robh@kernel.org>


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

* Re: [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver
  2024-09-18  6:07 ` [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver Keke Li via B4 Relay
@ 2024-11-04 17:50   ` Jacopo Mondi
  2024-11-05 10:06     ` Jacopo Mondi
  2024-11-05 11:21     ` Keke Li
  2024-11-05  8:21   ` Jacopo Mondi
  1 sibling, 2 replies; 37+ messages in thread
From: Jacopo Mondi @ 2024-11-04 17:50 UTC (permalink / raw)
  To: keke.li
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally,
	tomi.valkeinen@ideasonboard.com

Hi Keke
   sorry for the late feedback, hope you're still interested in
upstreaming this driver

On Wed, Sep 18, 2024 at 02:07:13PM +0800, Keke Li via B4 Relay wrote:
> From: Keke Li <keke.li@amlogic.com>
>
> This driver is used to receive mipi data from image sensor.
>
> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> Signed-off-by: Keke Li <keke.li@amlogic.com>
> ---
>  MAINTAINERS                                        |   7 +
>  drivers/media/platform/amlogic/Kconfig             |   1 +
>  drivers/media/platform/amlogic/Makefile            |   2 +
>  .../media/platform/amlogic/c3-mipi-csi2/Kconfig    |  16 +
>  .../media/platform/amlogic/c3-mipi-csi2/Makefile   |   3 +
>  .../platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c   | 910 +++++++++++++++++++++
>  6 files changed, 939 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2cdd7cacec86..9e75874a6e69 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1209,6 +1209,13 @@ F:	Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
>  F:	drivers/perf/amlogic/
>  F:	include/soc/amlogic/
>
> +AMLOGIC MIPI CSI2 DRIVER
> +M:	Keke Li <keke.li@amlogic.com>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml
> +F:	drivers/media/platform/amlogic/c3-mipi-csi2/
> +
>  AMPHENOL CHIPCAP 2 HUMIDITY-TEMPERATURE IIO DRIVER
>  M:	Javier Carrasco <javier.carrasco.cruz@gmail.com>
>  L:	linux-hwmon@vger.kernel.org
> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
> index 5014957404e9..b7c2de14848b 100644
> --- a/drivers/media/platform/amlogic/Kconfig
> +++ b/drivers/media/platform/amlogic/Kconfig
> @@ -2,4 +2,5 @@
>
>  comment "Amlogic media platform drivers"
>
> +source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>  source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
> index d3cdb8fa4ddb..4f571ce5d13e 100644
> --- a/drivers/media/platform/amlogic/Makefile
> +++ b/drivers/media/platform/amlogic/Makefile
> @@ -1,2 +1,4 @@
>  # SPDX-License-Identifier: GPL-2.0-only
> +
> +obj-y += c3-mipi-csi2/
>  obj-y += meson-ge2d/
> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
> new file mode 100644
> index 000000000000..0d7b2e203273
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
> @@ -0,0 +1,16 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config VIDEO_C3_MIPI_CSI2
> +	tristate "Amlogic C3 MIPI CSI-2 receiver"
> +	depends on ARCH_MESON || COMPILE_TEST
> +	depends on VIDEO_DEV
> +	depends on OF
> +	select MEDIA_CONTROLLER
> +	select V4L2_FWNODE
> +	select VIDEO_V4L2_SUBDEV_API
> +	help
> +	  Video4Linux2 driver for Amlogic C3 MIPI CSI-2 receiver.
> +	  C3 MIPI CSI-2 receiver is used to receive MIPI data from
> +	  image sensor.
> +
> +	  To compile this driver as a module choose m here.
> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
> new file mode 100644
> index 000000000000..cc08fc722bfd
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +obj-$(CONFIG_VIDEO_C3_MIPI_CSI2) += c3-mipi-csi2.o
> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
> new file mode 100644
> index 000000000000..6ac60d5b26a8
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
> @@ -0,0 +1,910 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/v4l2-subdev.h>
> +
> +/* C3 CSI-2 submodule definition */
> +enum {
> +	SUBMD_APHY,
> +	SUBMD_DPHY,
> +	SUBMD_HOST,
> +};
> +
> +#define CSI2_SUBMD_MASK             GENMASK(17, 16)
> +#define CSI2_SUBMD_SHIFT            16
> +#define CSI2_SUBMD(x)               (((x) & (CSI2_SUBMD_MASK)) >> (CSI2_SUBMD_SHIFT))
> +#define CSI2_REG_ADDR_MASK          GENMASK(15, 0)
> +#define CSI2_REG_ADDR(x)            ((x) & (CSI2_REG_ADDR_MASK))
> +#define CSI2_REG_A(x)               ((SUBMD_APHY << CSI2_SUBMD_SHIFT) | (x))
> +#define CSI2_REG_D(x)               ((SUBMD_DPHY << CSI2_SUBMD_SHIFT) | (x))
> +#define CSI2_REG_H(x)               ((SUBMD_HOST << CSI2_SUBMD_SHIFT) | (x))
> +
> +#define MIPI_CSI2_CLOCK_NUM_MAX     3
> +#define MIPI_CSI2_SUBDEV_NAME       "mipi-csi2"

Isn't the name too generic ? Should it report at least "c3-mipi-csi2"
?
> +
> +/* C3 CSI-2 APHY register */
> +#define MIPI_CSI_2M_PHY2_CNTL1      CSI2_REG_A(0x44)
> +#define MIPI_APHY_NORMAL_CNTL1      0x3f425C00

All other hex addresses use small capitals for letters

> +
> +#define MIPI_CSI_2M_PHY2_CNTL2      CSI2_REG_A(0x48)
> +#define MIPI_APHY_4LANES_CNTL2      0x033a0000
> +#define MIPI_APHY_NORMAL_CNTL2      0x333a0000
> +
> +#define MIPI_CSI_2M_PHY2_CNTL3      CSI2_REG_A(0x4c)
> +#define MIPI_APHY_2LANES_CNTL3      0x03800000
> +
> +/* C3 CSI-2 DPHY register */
> +#define MIPI_PHY_CTRL	            CSI2_REG_D(0x00)
> +#define MIPI_DPHY_LANES_ENABLE      0x0
> +
> +#define MIPI_PHY_CLK_LANE_CTRL	    CSI2_REG_D(0x04)
> +#define MIPI_DPHY_CLK_CONTINUE_MODE 0x3d8
> +
> +#define MIPI_PHY_DATA_LANE_CTRL     CSI2_REG_D(0x08)
> +#define MIPI_DPHY_LANE_CTRL_DISABLE 0x0
> +
> +#define MIPI_PHY_DATA_LANE_CTRL1    CSI2_REG_D(0x0c)
> +#define MIPI_DPHY_INSERT_ERRESC     BIT(0)
> +#define MIPI_DPHY_HS_SYNC_CHECK     BIT(1)
> +#define MIPI_DPHY_FIVE_HS_PIPE      GENMASK(6, 2)
> +#define MIPI_DPHY_FIVE_HS_PIPE_SHIFT           2
> +#define MIPI_DPHY_DATA_PIPE_SELECT  GENMASK(9, 7)
> +#define MIPI_DPHY_DATA_PIPE_SELECT_SHIFT       7
> +
> +#define MIPI_PHY_TCLK_MISS	    CSI2_REG_D(0x10)
> +#define MIPI_DPHY_CLK_MISS          0x9
> +
> +#define MIPI_PHY_TCLK_SETTLE	    CSI2_REG_D(0x14)
> +#define MIPI_DPHY_CLK_SETTLE        0x1F
> +
> +#define MIPI_PHY_THS_EXIT	    CSI2_REG_D(0x18)
> +#define MIPI_DPHY_HS_EXIT           0x8
> +
> +#define MIPI_PHY_THS_SKIP	    CSI2_REG_D(0x1c)
> +#define MIPI_DPHY_HS_SKIP           0xa
> +
> +#define MIPI_PHY_THS_SETTLE	    CSI2_REG_D(0x20)
> +#define MIPI_PHY_TINIT	            CSI2_REG_D(0x24)
> +#define MIPI_DPHY_INIT_CYCLES       0x4e20
> +
> +#define MIPI_PHY_TULPS_C	    CSI2_REG_D(0x28)
> +#define MIPI_DPHY_ULPS_CHECK_CYCLES 0x1000
> +
> +#define MIPI_PHY_TULPS_S	    CSI2_REG_D(0x2c)
> +#define MIPI_DPHY_ULPS_START_CYCLES 0x100
> +
> +#define MIPI_PHY_TMBIAS             CSI2_REG_D(0x30)
> +#define MIPI_DPHY_MBIAS_CYCLES      0x100
> +
> +#define MIPI_PHY_TLP_EN_W           CSI2_REG_D(0x34)
> +#define MIPI_DPHY_ULPS_STOP_CYCLES  0xC
> +
> +#define MIPI_PHY_TLPOK	            CSI2_REG_D(0x38)
> +#define MIPI_DPHY_POWER_UP_CYCLES   0x100
> +
> +#define MIPI_PHY_TWD_INIT	    CSI2_REG_D(0x3c)
> +#define MIPI_DPHY_INIT_WATCH_DOG    0x400000
> +
> +#define MIPI_PHY_TWD_HS             CSI2_REG_D(0x40)
> +#define MIPI_DPHY_HS_WATCH_DOG      0x400000
> +
> +#define MIPI_PHY_MUX_CTRL0	    CSI2_REG_D(0x284)
> +#define MIPI_DPHY_LANE3_SELECT      GENMASK(3, 0)
> +#define MIPI_DPHY_LANE2_SELECT      GENMASK(7, 4)
> +#define MIPI_DPHY_LANE2_SELECT_SHIFT           4
> +#define MIPI_DPHY_LANE1_SELECT      GENMASK(11, 8)
> +#define MIPI_DPHY_LANE1_SELECT_SHIFT            8
> +#define MIPI_DPHY_LANE0_SELECT      GENMASK(14, 12)
> +
> +#define MIPI_PHY_MUX_CTRL1	    CSI2_REG_D(0x288)
> +#define MIPI_DPHY_LANE3_CTRL_SIGNAL GENMASK(3, 0)
> +#define MIPI_DPHY_LANE2_CTRL_SIGNAL GENMASK(7, 4)
> +#define MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT      4
> +#define MIPI_DPHY_LANE1_CTRL_SIGNAL GENMASK(11, 8)
> +#define MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT       8
> +#define MIPI_DPHY_LANE0_CTRL_SIGNAL GENMASK(14, 12)
> +#define MIPI_DPHY_CLK_SELECT        BIT(17)
> +
> +/* C3 CSI-2 HOST register */
> +#define CSI2_HOST_N_LANES           CSI2_REG_H(0x04)
> +#define CSI2_HOST_CSI2_RESETN       CSI2_REG_H(0x10)
> +#define CSI2_HOST_RESETN_DEFAULT    0x0
> +#define CSI2_HOST_RESETN_RST_VALUE  BIT(0)
> +
> +#define CSI2_HOST_MASK1             CSI2_REG_H(0x28)
> +#define CSI2_HOST_ERROR_MASK1       GENMASK(28, 0)
> +
> +#define MIPI_CSI2_MAX_WIDTH         2888
> +#define MIPI_CSI2_MIN_WIDTH         160
> +#define MIPI_CSI2_MAX_HEIGHT        2240
> +#define MIPI_CSI2_MIN_HEIGHT        120
> +#define MIPI_CSI2_DEFAULT_WIDTH     1920
> +#define MIPI_CSI2_DEFAULT_HEIGHT    1080
> +#define MIPI_CSI2_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
> +
> +/* C3 CSI-2 pad list */
> +enum {
> +	MIPI_CSI2_PAD_SINK,
> +	MIPI_CSI2_PAD_SRC,
> +	MIPI_CSI2_PAD_MAX
> +};
> +
> +/**

You don't need to kernel-doc in-driver types and functions.
Documentation is always good, but this won't be parsed by kernel-doc
(afaiu) so you should drop one * from /**

> + * struct csi_info - MIPI CSI2 information
> + *
> + * @clocks: array of MIPI CSI2 clock names
> + * @clock_rates: array of MIPI CSI2 clock rate
> + * @clock_num: actual clock number
> + */
> +struct csi_info {
> +	char *clocks[MIPI_CSI2_CLOCK_NUM_MAX];
> +	u32 clock_rates[MIPI_CSI2_CLOCK_NUM_MAX];
> +	u32 clock_num;
> +};
> +
> +/**
> + * struct csi_device - MIPI CSI2 platform device
> + *
> + * @dev: pointer to the struct device
> + * @aphy: MIPI CSI2 aphy register address
> + * @dphy: MIPI CSI2 dphy register address
> + * @host: MIPI CSI2 host register address
> + * @clks: array of MIPI CSI2 clocks
> + * @sd: MIPI CSI2 sub-device
> + * @pads: MIPI CSI2 sub-device pads
> + * @notifier: notifier to register on the v4l2-async API
> + * @src_sd: source sub-device
> + * @bus: MIPI CSI2 bus information
> + * @src_sd_pad: source sub-device pad
> + * @lock: protect MIPI CSI2 device
> + * @info: version-specific MIPI CSI2 information
> + */
> +struct csi_device {
> +	struct device *dev;
> +	void __iomem *aphy;
> +	void __iomem *dphy;
> +	void __iomem *host;
> +	struct clk_bulk_data clks[MIPI_CSI2_CLOCK_NUM_MAX];
> +
> +	struct v4l2_subdev sd;
> +	struct media_pad pads[MIPI_CSI2_PAD_MAX];
> +	struct v4l2_async_notifier notifier;
> +	struct v4l2_subdev *src_sd;
> +	struct v4l2_mbus_config_mipi_csi2 bus;
> +
> +	u16 src_sd_pad;
> +	struct mutex lock; /* Protect csi device */

All the operations which receive a subdev_state are guaranteed to be locked
so you can avoid manually locking in enable/disable streams
(and drop #include cleanup.h if you don't use guards in any other
place)

> +	const struct csi_info *info;
> +};
> +
> +static const u32 c3_mipi_csi_formats[] = {
> +	MEDIA_BUS_FMT_SBGGR10_1X10,
> +	MEDIA_BUS_FMT_SGBRG10_1X10,
> +	MEDIA_BUS_FMT_SGRBG10_1X10,
> +	MEDIA_BUS_FMT_SRGGB10_1X10,
> +	MEDIA_BUS_FMT_SBGGR12_1X12,
> +	MEDIA_BUS_FMT_SGBRG12_1X12,
> +	MEDIA_BUS_FMT_SGRBG12_1X12,
> +	MEDIA_BUS_FMT_SRGGB12_1X12,
> +};
> +
> +/* Hardware configuration */
> +
> +static void c3_mipi_csi_write(struct csi_device *csi, u32 reg, u32 val)
> +{
> +	void __iomem *addr;
> +
> +	switch (CSI2_SUBMD(reg)) {
> +	case SUBMD_APHY:
> +		addr = csi->aphy + CSI2_REG_ADDR(reg);
> +		break;
> +	case SUBMD_DPHY:
> +		addr = csi->dphy + CSI2_REG_ADDR(reg);
> +		break;
> +	case SUBMD_HOST:
> +		addr = csi->host + CSI2_REG_ADDR(reg);
> +		break;
> +	default:
> +		dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
> +		return;
> +	}
> +
> +	writel(val, addr);
> +}
> +
> +static void c3_mipi_csi_update_bits(struct csi_device *csi, u32 reg,
> +				    u32 mask, u32 val)
> +{
> +	void __iomem *addr;
> +	u32 orig, tmp;
> +
> +	switch (CSI2_SUBMD(reg)) {
> +	case SUBMD_APHY:
> +		addr = csi->aphy + CSI2_REG_ADDR(reg);
> +		break;
> +	case SUBMD_DPHY:
> +		addr = csi->dphy + CSI2_REG_ADDR(reg);
> +		break;
> +	case SUBMD_HOST:
> +		addr = csi->host + CSI2_REG_ADDR(reg);
> +		break;
> +	default:
> +		dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
> +		return;
> +	}

This is repeated in two functions and could be grouped to a common
place. Up to you

> +
> +	orig = readl(addr);
> +	tmp = orig & ~mask;
> +	tmp |= val & mask;
> +
> +	if (tmp != orig)
> +		writel(tmp, addr);
> +}
> +
> +static void c3_mipi_csi_cfg_aphy(struct csi_device *csi, u32 lanes)
> +{
> +	c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL1, MIPI_APHY_NORMAL_CNTL1);
> +
> +	if (lanes == 4)
> +		c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_4LANES_CNTL2);
> +	else
> +		c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_NORMAL_CNTL2);
> +
> +	if (lanes == 2)
> +		c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL3, MIPI_APHY_2LANES_CNTL3);

The driver seems to only accept 2 or 4 lanes. What is
MIPI_APHY_NORMAL_CNTL2 for ?

> +}
> +
> +static void c3_mipi_csi_2lanes_setting(struct csi_device *csi)
> +{
> +	/* Disable lane 2 and lane 3 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0xf);
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
> +				0xf << MIPI_DPHY_LANE2_SELECT_SHIFT);
> +	/* Select analog data lane 1 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
> +				0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
> +	/* Select analog data lane 0 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
> +
> +	/* Disable lane 2 and lane 3 control signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0xf);
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
> +				0xf << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
> +	/* Select lane 1 signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
> +				0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
> +	/* Select lane 0 signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
> +	/* Select input 0 as clock */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
> +				MIPI_DPHY_CLK_SELECT);
> +}
> +
> +static void c3_mipi_csi_4lanes_setting(struct csi_device *csi)
> +{
> +	/* Select analog data lane 3 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0x3);
> +	/* Select analog data lane 2 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
> +				0x2 << MIPI_DPHY_LANE2_SELECT_SHIFT);
> +	/* Select analog data lane 1 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
> +				0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
> +	/* Select analog data lane 0 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
> +
> +	/* Select lane 3 signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0x3);
> +	/* Select lane 2 signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
> +				0x2 << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
> +	/* Select lane 1 signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
> +				0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
> +	/* Select lane 0 signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
> +	/* Select input 0 as clock */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
> +				MIPI_DPHY_CLK_SELECT);
> +}
> +
> +static void c3_mipi_csi_cfg_dphy(struct csi_device *csi, u32 lanes, s64 rate)
> +{
> +	u32 val;
> +	u32 settle;
> +
> +	/* Calculate the high speed settle */
> +	val = DIV_ROUND_UP(1000000000, rate);
> +	settle = (16 * val + 230) / 10;
> +
> +	c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL, MIPI_DPHY_CLK_CONTINUE_MODE);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TCLK_MISS, MIPI_DPHY_CLK_MISS);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TCLK_SETTLE, MIPI_DPHY_CLK_SETTLE);
> +	c3_mipi_csi_write(csi, MIPI_PHY_THS_EXIT, MIPI_DPHY_HS_EXIT);
> +	c3_mipi_csi_write(csi, MIPI_PHY_THS_SKIP, MIPI_DPHY_HS_SKIP);
> +	c3_mipi_csi_write(csi, MIPI_PHY_THS_SETTLE, settle);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TINIT, MIPI_DPHY_INIT_CYCLES);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TMBIAS, MIPI_DPHY_MBIAS_CYCLES);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TULPS_C, MIPI_DPHY_ULPS_CHECK_CYCLES);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TULPS_S, MIPI_DPHY_ULPS_START_CYCLES);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TLP_EN_W, MIPI_DPHY_ULPS_STOP_CYCLES);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TLPOK, MIPI_DPHY_POWER_UP_CYCLES);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TWD_INIT, MIPI_DPHY_INIT_WATCH_DOG);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TWD_HS, MIPI_DPHY_HS_WATCH_DOG);
> +	c3_mipi_csi_write(csi, MIPI_PHY_DATA_LANE_CTRL, MIPI_DPHY_LANE_CTRL_DISABLE);
> +
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_INSERT_ERRESC,
> +				MIPI_DPHY_INSERT_ERRESC);
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_HS_SYNC_CHECK,
> +				MIPI_DPHY_HS_SYNC_CHECK);
> +	/*
> +	 * Set 5 pipe lines to the same high speed.
> +	 * Each bit for one pipe line.
> +	 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_FIVE_HS_PIPE,
> +				0x1f << MIPI_DPHY_FIVE_HS_PIPE_SHIFT);
> +
> +	/* Output data with pipe line data. */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_DATA_PIPE_SELECT,
> +				0x3 << MIPI_DPHY_DATA_PIPE_SELECT_SHIFT);

Would it be possible to provide a definition for these 0x1f and 0x3
values ?

> +	if (lanes == 2)
> +		c3_mipi_csi_2lanes_setting(csi);
> +	else
> +		c3_mipi_csi_4lanes_setting(csi);
> +
> +	/* Enable digital data and clock lanes */
> +	c3_mipi_csi_write(csi, MIPI_PHY_CTRL, MIPI_DPHY_LANES_ENABLE);
> +}
> +
> +static void c3_mipi_csi_cfg_host(struct csi_device *csi, u32 lanes)
> +{
> +	/* Reset CSI-2 controller output */
> +	c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_DEFAULT);
> +	c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_RST_VALUE);
> +
> +	/* Set data lane number */
> +	c3_mipi_csi_write(csi, CSI2_HOST_N_LANES, lanes - 1);
> +
> +	/* Enable error mask */
> +	c3_mipi_csi_write(csi, CSI2_HOST_MASK1, CSI2_HOST_ERROR_MASK1);
> +}
> +
> +static int c3_mipi_csi_start_stream(struct csi_device *csi)
> +{
> +	s64 link_freq;
> +	s64 lane_rate;
> +
> +	link_freq = v4l2_get_link_freq(csi->src_sd->ctrl_handler, 0, 0);
> +	if (link_freq < 0) {
> +		dev_err(csi->dev, "Unable to obtain link frequency: %lld\n", link_freq);
> +		return link_freq;
> +	}
> +
> +	lane_rate = link_freq * 2;
> +	if (lane_rate > 1500000000)

I would dev_err here too

> +		return -EINVAL;
> +
> +	c3_mipi_csi_cfg_aphy(csi, csi->bus.num_data_lanes);
> +	c3_mipi_csi_cfg_dphy(csi, csi->bus.num_data_lanes, lane_rate);
> +	c3_mipi_csi_cfg_host(csi, csi->bus.num_data_lanes);
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_enable_streams(struct v4l2_subdev *sd,
> +				      struct v4l2_subdev_state *state,
> +				      u32 pad, u64 streams_mask)
> +{
> +	struct csi_device *csi = v4l2_get_subdevdata(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	guard(mutex)(&csi->lock);
> +
> +	pm_runtime_resume_and_get(csi->dev);
> +
> +	c3_mipi_csi_start_stream(csi);
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       MIPI_CSI2_PAD_SINK,
> +						       &streams_mask);
> +	ret = v4l2_subdev_enable_streams(csi->src_sd,
> +					 csi->src_sd_pad,
> +					 sink_streams);
> +	if (ret) {
> +		pm_runtime_put(csi->dev);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_disable_streams(struct v4l2_subdev *sd,
> +				       struct v4l2_subdev_state *state,
> +				       u32 pad, u64 streams_mask)
> +{
> +	struct csi_device *csi = v4l2_get_subdevdata(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	guard(mutex)(&csi->lock);
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       MIPI_CSI2_PAD_SINK,
> +						       &streams_mask);
> +	ret = v4l2_subdev_disable_streams(csi->src_sd,
> +					  csi->src_sd_pad,
> +					  sink_streams);
> +	if (ret)
> +		dev_err(csi->dev, "Failed to disable %s\n", csi->src_sd->name);
> +
> +	pm_runtime_put(csi->dev);
> +
> +	return ret;
> +}
> +
> +static int c3_mipi_csi_cfg_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   struct v4l2_subdev_krouting *routing)
> +{
> +	static const struct v4l2_mbus_framefmt format = {
> +		.width = MIPI_CSI2_DEFAULT_WIDTH,
> +		.height = MIPI_CSI2_DEFAULT_HEIGHT,
> +		.code = MIPI_CSI2_DEFAULT_FMT,
> +		.field = V4L2_FIELD_NONE,
> +		.colorspace = V4L2_COLORSPACE_RAW,
> +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,

I presume for Raw Bayer data the quantization range is full ?

> +		.xfer_func = V4L2_XFER_FUNC_NONE,
> +	};
> +	int ret;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;

You should validate that the provided routing table matches what the
driver supports, so only [0/0]->[1/0]

Now that I've said so, if the routing table is not modifiable I wonder
if you should support set_routing() at all, or it could be left out
until you don't add support for more streams to the driver.

After all this driver implements support for routing but doesn't set
the V4L2_SUBDEV_FL_STREAMS flag, so the operation is disallowed from
userspace for now.

> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_init_routing(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes;
> +	struct v4l2_subdev_krouting routing;
> +
> +	routes.sink_pad = MIPI_CSI2_PAD_SINK;
> +	routes.sink_stream = 0;
> +	routes.source_pad = MIPI_CSI2_PAD_SRC;
> +	routes.source_stream = 0;
> +	routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> +
> +	routing.num_routes = 1;
> +	routing.routes = &routes;
> +
> +	return c3_mipi_csi_cfg_routing(sd, state, &routing);
> +}
> +
> +static int c3_mipi_csi_set_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   enum v4l2_subdev_format_whence which,
> +				   struct v4l2_subdev_krouting *routing)
> +{
> +	bool is_streaming = v4l2_subdev_is_streaming(sd);
> +
> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> +		return -EBUSY;
> +
> +	return c3_mipi_csi_cfg_routing(sd, state, routing);
> +}
> +
> +static int c3_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
> +				      struct v4l2_subdev_state *state,
> +				      struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	switch (code->pad) {
> +	case MIPI_CSI2_PAD_SINK:
> +		if (code->index >= ARRAY_SIZE(c3_mipi_csi_formats))
> +			return -EINVAL;
> +
> +		code->code = c3_mipi_csi_formats[code->index];
> +		break;
> +	case MIPI_CSI2_PAD_SRC:
> +		struct v4l2_mbus_framefmt *fmt;
> +
> +		if (code->index > 0)
> +			return -EINVAL;
> +
> +		fmt = v4l2_subdev_state_get_format(state, code->pad);
> +		code->code = fmt->code;
> +		break;

I'm not sure if the V4L2 API specify that the formats on a pad should
be enumerated in full, regardless of the configuration, or like you're
doing here reflect the subdev configuration. I like what you have here
more, so unless someone screams I think it's fine.

> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_set_fmt(struct v4l2_subdev *sd,
> +			       struct v4l2_subdev_state *state,
> +			       struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +	unsigned int i;
> +
> +	if (format->pad != MIPI_CSI2_PAD_SINK)
> +		return v4l2_subdev_get_fmt(sd, state, format);
> +
> +	fmt = v4l2_subdev_state_get_format(state, format->pad);

Could you clarify what other streams you plan to support ? As you
support routing I presume you are preparing to capture
multiple streams of data like image + embedded data, or to support
sensors which sends data on different virtual channels ?

How do you see this driver evolve ? Will it be augmented with an
additional source pad directed to a video device where to capture
embedded data from ?

I'm wondering because if PAD_SINK become multiplexed, you won't be
allowed to set a format there. It only works now because you have a
single stream.

Speaking of which, as you prepare to support multiple streams, I would
specify the stream number when calling v4l2_subdev_state_get_format().

	fmt = v4l2_subdev_state_get_format(state, format->pad, 0);

> +
> +	for (i = 0; i < ARRAY_SIZE(c3_mipi_csi_formats); i++)
> +		if (format->format.code == c3_mipi_csi_formats[i])
> +			break;

nit: please use {} for the for loop

> +
> +	if (i == ARRAY_SIZE(c3_mipi_csi_formats))
> +		fmt->code = c3_mipi_csi_formats[0];
> +	else
> +		fmt->code = c3_mipi_csi_formats[i];

You could set this in the for loop, before breaking.

> +
> +	fmt->width = clamp_t(u32, format->format.width,
> +			     MIPI_CSI2_MIN_WIDTH, MIPI_CSI2_MAX_WIDTH);
> +	fmt->height = clamp_t(u32, format->format.height,
> +			      MIPI_CSI2_MIN_HEIGHT, MIPI_CSI2_MAX_HEIGHT);
> +
You should set the colorspace related information too, as you
initialize them, similar to what you do in init_state()

> +	format->format = *fmt;
> +
> +	/* Synchronize the format to source pad */
> +	fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
> +	*fmt = format->format;
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_init_state(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SINK);
> +	src_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
> +
> +	sink_fmt->width = MIPI_CSI2_DEFAULT_WIDTH;
> +	sink_fmt->height = MIPI_CSI2_DEFAULT_HEIGHT;
> +	sink_fmt->field = V4L2_FIELD_NONE;
> +	sink_fmt->code = MIPI_CSI2_DEFAULT_FMT;
> +	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> +	sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
> +	sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
> +	sink_fmt->quantization =
> +		V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,

If you could initialize them like you do above, with specific values
instead of using _DEFAULT() I think it's better.

> +					      sink_fmt->ycbcr_enc);
> +	*src_fmt = *sink_fmt;
> +
> +	return c3_mipi_csi_init_routing(sd, state);
> +}
> +
> +static const struct v4l2_subdev_pad_ops c3_mipi_csi_pad_ops = {
> +	.enum_mbus_code = c3_mipi_csi_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = c3_mipi_csi_set_fmt,
> +	.set_routing = c3_mipi_csi_set_routing,
> +	.enable_streams = c3_mipi_csi_enable_streams,
> +	.disable_streams = c3_mipi_csi_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops c3_mipi_csi_subdev_ops = {
> +	.pad = &c3_mipi_csi_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops c3_mipi_csi_internal_ops = {
> +	.init_state = c3_mipi_csi_init_state,
> +};
> +
> +/* Media entity operations */
> +static const struct media_entity_operations c3_mipi_csi_entity_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +/* PM runtime */
> +
> +static int __maybe_unused c3_mipi_csi_runtime_suspend(struct device *dev)
> +{
> +	struct csi_device *csi = dev_get_drvdata(dev);
> +
> +	clk_bulk_disable_unprepare(csi->info->clock_num, csi->clks);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused c3_mipi_csi_runtime_resume(struct device *dev)
> +{
> +	struct csi_device *csi = dev_get_drvdata(dev);
> +
> +	return clk_bulk_prepare_enable(csi->info->clock_num, csi->clks);
> +}
> +
> +static const struct dev_pm_ops c3_mipi_csi_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> +				pm_runtime_force_resume)
> +	SET_RUNTIME_PM_OPS(c3_mipi_csi_runtime_suspend,
> +			   c3_mipi_csi_runtime_resume, NULL)

You could use SYSTEM_SLEEP_PM_OPS and RUNTIME_PM_OPS and set

		.pm = pm_ptr(&c3_mipi_csi_pm_ops),

to avoid __maybe_unused in the functions, up to you

> +};
> +
> +/* Probe/remove & platform driver */
> +
> +static int c3_mipi_csi_subdev_init(struct csi_device *csi)
> +{
> +	struct v4l2_subdev *sd = &csi->sd;
> +	int ret;
> +
> +	v4l2_subdev_init(sd, &c3_mipi_csi_subdev_ops);
> +	sd->owner = THIS_MODULE;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	sd->internal_ops = &c3_mipi_csi_internal_ops;
> +	snprintf(sd->name, sizeof(sd->name), "%s", MIPI_CSI2_SUBDEV_NAME);
> +
> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	sd->entity.ops = &c3_mipi_csi_entity_ops;
> +
> +	sd->dev = csi->dev;
> +	v4l2_set_subdevdata(sd, csi);
> +
> +	csi->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +	csi->pads[MIPI_CSI2_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sd->entity, MIPI_CSI2_PAD_MAX, csi->pads);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret) {
> +		media_entity_cleanup(&sd->entity);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void c3_mipi_csi_subdev_deinit(struct csi_device *csi)
> +{
> +	v4l2_subdev_cleanup(&csi->sd);
> +	media_entity_cleanup(&csi->sd.entity);
> +}
> +
> +/* Subdev notifier register */
> +static int c3_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier,
> +				    struct v4l2_subdev *sd,
> +				    struct v4l2_async_connection *asc)
> +{
> +	struct csi_device *csi = v4l2_get_subdevdata(notifier->sd);
> +	struct media_pad *sink = &csi->sd.entity.pads[MIPI_CSI2_PAD_SINK];
> +	int ret;
> +
> +	ret = media_entity_get_fwnode_pad(&sd->entity,
> +					  sd->fwnode, MEDIA_PAD_FL_SOURCE);
> +	if (ret < 0) {
> +		dev_err(csi->dev, "Failed to find pad for %s\n", sd->name);
> +		return ret;
> +	}
> +
> +	csi->src_sd = sd;
> +	csi->src_sd_pad = ret;
> +
> +	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
> +					       MEDIA_LNK_FL_IMMUTABLE);
> +}
> +
> +static const struct v4l2_async_notifier_operations c3_mipi_csi_notify_ops = {
> +	.bound = c3_mipi_csi_notify_bound,
> +};
> +
> +static int c3_mipi_csi_async_register(struct csi_device *csi)
> +{
> +	struct v4l2_fwnode_endpoint vep = {
> +		.bus_type = V4L2_MBUS_CSI2_DPHY,
> +	};
> +	struct v4l2_async_connection *asc;
> +	struct fwnode_handle *ep;
> +	int ret;
> +
> +	v4l2_async_subdev_nf_init(&csi->notifier, &csi->sd);
> +
> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0,
> +					     FWNODE_GRAPH_ENDPOINT_NEXT);
> +	if (!ep)
> +		return -ENOTCONN;
> +
> +	ret = v4l2_fwnode_endpoint_parse(ep, &vep);
> +	if (ret)
> +		goto err_put_handle;
> +
> +	csi->bus = vep.bus.mipi_csi2;
> +	if (csi->bus.num_data_lanes != 2 && csi->bus.num_data_lanes != 4)
> +		goto err_put_handle;

I would dev_err() here

Thanks
   j

> +
> +	asc = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep,
> +					      struct v4l2_async_connection);
> +	if (IS_ERR(asc)) {
> +		ret = PTR_ERR(asc);
> +		goto err_put_handle;
> +	}
> +
> +	csi->notifier.ops = &c3_mipi_csi_notify_ops;
> +	ret = v4l2_async_nf_register(&csi->notifier);
> +	if (ret)
> +		goto err_cleanup_nf;
> +
> +	ret = v4l2_async_register_subdev(&csi->sd);
> +	if (ret)
> +		goto err_unregister_nf;
> +
> +	fwnode_handle_put(ep);
> +
> +	return 0;
> +
> +err_unregister_nf:
> +	v4l2_async_nf_unregister(&csi->notifier);
> +err_cleanup_nf:
> +	v4l2_async_nf_cleanup(&csi->notifier);
> +err_put_handle:
> +	fwnode_handle_put(ep);
> +	return ret;
> +}
> +
> +static void c3_mipi_csi_async_unregister(struct csi_device *csi)
> +{
> +	v4l2_async_unregister_subdev(&csi->sd);
> +	v4l2_async_nf_unregister(&csi->notifier);
> +	v4l2_async_nf_cleanup(&csi->notifier);
> +}
> +
> +static int c3_mipi_csi_ioremap_resource(struct csi_device *csi)
> +{
> +	struct device *dev = csi->dev;
> +	struct platform_device *pdev = to_platform_device(dev);
> +
> +	csi->aphy = devm_platform_ioremap_resource_byname(pdev, "aphy");
> +	if (IS_ERR(csi->aphy))
> +		return PTR_ERR(csi->aphy);
> +
> +	csi->dphy = devm_platform_ioremap_resource_byname(pdev, "dphy");
> +	if (IS_ERR(csi->dphy))
> +		return PTR_ERR(csi->dphy);
> +
> +	csi->host = devm_platform_ioremap_resource_byname(pdev, "host");
> +	if (IS_ERR(csi->host))
> +		return PTR_ERR(csi->host);
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_configure_clocks(struct csi_device *csi)
> +{
> +	const struct csi_info *info = csi->info;
> +	int ret;
> +	u32 i;
> +
> +	for (i = 0; i < info->clock_num; i++)
> +		csi->clks[i].id = info->clocks[i];
> +
> +	ret = devm_clk_bulk_get(csi->dev, info->clock_num, csi->clks);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < info->clock_num; i++) {
> +		if (!info->clock_rates[i])
> +			continue;
> +		ret = clk_set_rate(csi->clks[i].clk, info->clock_rates[i]);
> +		if (ret) {
> +			dev_err(csi->dev, "Failed to set %s rate %u\n", info->clocks[i],
> +				info->clock_rates[i]);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct csi_device *csi;
> +	int ret;
> +
> +	csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
> +	if (!csi)
> +		return -ENOMEM;
> +
> +	csi->info = of_device_get_match_data(dev);
> +	csi->dev = dev;
> +
> +	ret = c3_mipi_csi_ioremap_resource(csi);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
> +
> +	ret = c3_mipi_csi_configure_clocks(csi);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to configure clocks\n");
> +
> +	platform_set_drvdata(pdev, csi);
> +
> +	mutex_init(&csi->lock);
> +	pm_runtime_enable(dev);
> +
> +	ret = c3_mipi_csi_subdev_init(csi);
> +	if (ret)
> +		goto err_disable_runtime_pm;
> +
> +	ret = c3_mipi_csi_async_register(csi);
> +	if (ret)
> +		goto err_deinit_subdev;
> +
> +	return 0;
> +
> +err_deinit_subdev:
> +	c3_mipi_csi_subdev_deinit(csi);
> +err_disable_runtime_pm:
> +	pm_runtime_disable(dev);
> +	mutex_destroy(&csi->lock);
> +	return ret;
> +};
> +
> +static void c3_mipi_csi_remove(struct platform_device *pdev)
> +{
> +	struct csi_device *csi = platform_get_drvdata(pdev);
> +
> +	c3_mipi_csi_async_unregister(csi);
> +	c3_mipi_csi_subdev_deinit(csi);
> +
> +	pm_runtime_disable(&pdev->dev);
> +	mutex_destroy(&csi->lock);
> +};
> +
> +static const struct csi_info c3_mipi_csi_info = {
> +	.clocks = {"vapb", "phy0"},
> +	.clock_rates = {0, 200000000},
> +	.clock_num = 2
> +};
> +
> +static const struct of_device_id c3_mipi_csi_of_match[] = {
> +	{ .compatible = "amlogic,c3-mipi-csi2",
> +	  .data = &c3_mipi_csi_info,
> +	},
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, c3_mipi_csi_of_match);
> +
> +static struct platform_driver c3_mipi_csi_driver = {
> +	.probe = c3_mipi_csi_probe,
> +	.remove = c3_mipi_csi_remove,
> +	.driver = {
> +		.name = "c3-mipi-csi2",
> +		.of_match_table = c3_mipi_csi_of_match,
> +		.pm = &c3_mipi_csi_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(c3_mipi_csi_driver);
> +
> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
> +MODULE_DESCRIPTION("Amlogic C3 MIPI CSI-2 receiver");
> +MODULE_LICENSE("GPL");
>
> --
> 2.46.1
>
>
>

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

* Re: [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver
  2024-09-18  6:07 ` [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver Keke Li via B4 Relay
  2024-11-04 17:50   ` Jacopo Mondi
@ 2024-11-05  8:21   ` Jacopo Mondi
  2024-11-05 11:36     ` Keke Li
  1 sibling, 1 reply; 37+ messages in thread
From: Jacopo Mondi @ 2024-11-05  8:21 UTC (permalink / raw)
  To: keke.li
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi Keke, one more thing

On Wed, Sep 18, 2024 at 02:07:13PM +0800, Keke Li via B4 Relay wrote:
> From: Keke Li <keke.li@amlogic.com>
>
> This driver is used to receive mipi data from image sensor.
>
> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> Signed-off-by: Keke Li <keke.li@amlogic.com>
> ---
>  MAINTAINERS                                        |   7 +
>  drivers/media/platform/amlogic/Kconfig             |   1 +
>  drivers/media/platform/amlogic/Makefile            |   2 +
>  .../media/platform/amlogic/c3-mipi-csi2/Kconfig    |  16 +
>  .../media/platform/amlogic/c3-mipi-csi2/Makefile   |   3 +
>  .../platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c   | 910 +++++++++++++++++++++
>  6 files changed, 939 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2cdd7cacec86..9e75874a6e69 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1209,6 +1209,13 @@ F:	Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
>  F:	drivers/perf/amlogic/
>  F:	include/soc/amlogic/
>
> +AMLOGIC MIPI CSI2 DRIVER
> +M:	Keke Li <keke.li@amlogic.com>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml
> +F:	drivers/media/platform/amlogic/c3-mipi-csi2/
> +
>  AMPHENOL CHIPCAP 2 HUMIDITY-TEMPERATURE IIO DRIVER
>  M:	Javier Carrasco <javier.carrasco.cruz@gmail.com>
>  L:	linux-hwmon@vger.kernel.org
> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
> index 5014957404e9..b7c2de14848b 100644
> --- a/drivers/media/platform/amlogic/Kconfig
> +++ b/drivers/media/platform/amlogic/Kconfig
> @@ -2,4 +2,5 @@
>
>  comment "Amlogic media platform drivers"
>
> +source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>  source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
> index d3cdb8fa4ddb..4f571ce5d13e 100644
> --- a/drivers/media/platform/amlogic/Makefile
> +++ b/drivers/media/platform/amlogic/Makefile
> @@ -1,2 +1,4 @@
>  # SPDX-License-Identifier: GPL-2.0-only
> +
> +obj-y += c3-mipi-csi2/
>  obj-y += meson-ge2d/
> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
> new file mode 100644
> index 000000000000..0d7b2e203273
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
> @@ -0,0 +1,16 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config VIDEO_C3_MIPI_CSI2
> +	tristate "Amlogic C3 MIPI CSI-2 receiver"
> +	depends on ARCH_MESON || COMPILE_TEST
> +	depends on VIDEO_DEV
> +	depends on OF
> +	select MEDIA_CONTROLLER
> +	select V4L2_FWNODE
> +	select VIDEO_V4L2_SUBDEV_API
> +	help
> +	  Video4Linux2 driver for Amlogic C3 MIPI CSI-2 receiver.
> +	  C3 MIPI CSI-2 receiver is used to receive MIPI data from
> +	  image sensor.
> +
> +	  To compile this driver as a module choose m here.
> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
> new file mode 100644
> index 000000000000..cc08fc722bfd
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +obj-$(CONFIG_VIDEO_C3_MIPI_CSI2) += c3-mipi-csi2.o
> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
> new file mode 100644
> index 000000000000..6ac60d5b26a8
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
> @@ -0,0 +1,910 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/v4l2-subdev.h>
> +
> +/* C3 CSI-2 submodule definition */
> +enum {
> +	SUBMD_APHY,
> +	SUBMD_DPHY,
> +	SUBMD_HOST,
> +};
> +
> +#define CSI2_SUBMD_MASK             GENMASK(17, 16)
> +#define CSI2_SUBMD_SHIFT            16
> +#define CSI2_SUBMD(x)               (((x) & (CSI2_SUBMD_MASK)) >> (CSI2_SUBMD_SHIFT))
> +#define CSI2_REG_ADDR_MASK          GENMASK(15, 0)
> +#define CSI2_REG_ADDR(x)            ((x) & (CSI2_REG_ADDR_MASK))
> +#define CSI2_REG_A(x)               ((SUBMD_APHY << CSI2_SUBMD_SHIFT) | (x))
> +#define CSI2_REG_D(x)               ((SUBMD_DPHY << CSI2_SUBMD_SHIFT) | (x))
> +#define CSI2_REG_H(x)               ((SUBMD_HOST << CSI2_SUBMD_SHIFT) | (x))
> +
> +#define MIPI_CSI2_CLOCK_NUM_MAX     3
> +#define MIPI_CSI2_SUBDEV_NAME       "mipi-csi2"
> +
> +/* C3 CSI-2 APHY register */
> +#define MIPI_CSI_2M_PHY2_CNTL1      CSI2_REG_A(0x44)
> +#define MIPI_APHY_NORMAL_CNTL1      0x3f425C00
> +
> +#define MIPI_CSI_2M_PHY2_CNTL2      CSI2_REG_A(0x48)
> +#define MIPI_APHY_4LANES_CNTL2      0x033a0000
> +#define MIPI_APHY_NORMAL_CNTL2      0x333a0000
> +
> +#define MIPI_CSI_2M_PHY2_CNTL3      CSI2_REG_A(0x4c)
> +#define MIPI_APHY_2LANES_CNTL3      0x03800000
> +
> +/* C3 CSI-2 DPHY register */
> +#define MIPI_PHY_CTRL	            CSI2_REG_D(0x00)
> +#define MIPI_DPHY_LANES_ENABLE      0x0
> +
> +#define MIPI_PHY_CLK_LANE_CTRL	    CSI2_REG_D(0x04)
> +#define MIPI_DPHY_CLK_CONTINUE_MODE 0x3d8

I was checking the registers settings, and I've noticed the values
used to configure the interface group together settings from different
register bitfields.

I think to allow the driver to be easily consumable and extensible,
each bit field should be described by its own macro.

Instead of defining a magic value like

#define MIPI_DPHY_CLK_CONTINUE_MODE 0x3d8

The single register bitfield should be described

#define MIPI_PHY_CLK_LANE_CTRL                  CSI2_REG_D(0x04)
#define MIPI_PHY_CLK_LANE_CTRL_HS_RX_EN         BIT(9)
#define MIPI_PHY_CLK_LANE_CTRL_END_EN           BIT(8)
#define MIPI_PHY_CLK_LANE_CTRL_LPEN_DIS         BIT(7)
#define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_EN     BIT(6)
#define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS     (0 << 3)
#define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_2   (1 << 3)
#define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_4   (2 << 3)
#define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_8   (3 << 3)
#define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_16  (4 << 3)
#define MIPI_PHY_CLK_LANE_CTRL_FORCE_ULPS_EXIT  BIT(1)
#define MIPI_PHY_CLK_LANE_CTRL_FORCE_ULPS_ENTER BIT(0)

and you configure it with

        c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL,
                          MIPI_PHY_CLK_LANE_CTRL_HS_RX_EN |
                          MIPI_PHY_CLK_LANE_CTRL_END_EN |
                          MIPI_PHY_CLK_LANE_CTRL_LPEN_DIS |
                          MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_EN |
                          MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_2);

Otherwise iy you do

        c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL, MIPI_DPHY_CLK_CONTINUE_MODE);

if MIPI_DPHY_CLK_CONTINUE_MODE has to be made configurable for
whatever reason, it's hard to untangle.

The above suggestion only applies to register where it makes sense to
describe the single fields of course.

> +
> +#define MIPI_PHY_DATA_LANE_CTRL     CSI2_REG_D(0x08)
> +#define MIPI_DPHY_LANE_CTRL_DISABLE 0x0
> +
> +#define MIPI_PHY_DATA_LANE_CTRL1    CSI2_REG_D(0x0c)
> +#define MIPI_DPHY_INSERT_ERRESC     BIT(0)
> +#define MIPI_DPHY_HS_SYNC_CHECK     BIT(1)
> +#define MIPI_DPHY_FIVE_HS_PIPE      GENMASK(6, 2)
> +#define MIPI_DPHY_FIVE_HS_PIPE_SHIFT           2
> +#define MIPI_DPHY_DATA_PIPE_SELECT  GENMASK(9, 7)
> +#define MIPI_DPHY_DATA_PIPE_SELECT_SHIFT       7

In example, this is done right!

> +
> +#define MIPI_PHY_TCLK_MISS	    CSI2_REG_D(0x10)
> +#define MIPI_DPHY_CLK_MISS          0x9

and here you're just programming a counter, so it's of course fine to
have the raw number.

> +
> +#define MIPI_PHY_TCLK_SETTLE	    CSI2_REG_D(0x14)
> +#define MIPI_DPHY_CLK_SETTLE        0x1F

nit: while at it, use small caps for hex as you're using them in most places

Thanks
  j

> +
> +#define MIPI_PHY_THS_EXIT	    CSI2_REG_D(0x18)
> +#define MIPI_DPHY_HS_EXIT           0x8
> +
> +#define MIPI_PHY_THS_SKIP	    CSI2_REG_D(0x1c)
> +#define MIPI_DPHY_HS_SKIP           0xa
> +
> +#define MIPI_PHY_THS_SETTLE	    CSI2_REG_D(0x20)
> +#define MIPI_PHY_TINIT	            CSI2_REG_D(0x24)
> +#define MIPI_DPHY_INIT_CYCLES       0x4e20
> +
> +#define MIPI_PHY_TULPS_C	    CSI2_REG_D(0x28)
> +#define MIPI_DPHY_ULPS_CHECK_CYCLES 0x1000
> +
> +#define MIPI_PHY_TULPS_S	    CSI2_REG_D(0x2c)
> +#define MIPI_DPHY_ULPS_START_CYCLES 0x100
> +
> +#define MIPI_PHY_TMBIAS             CSI2_REG_D(0x30)
> +#define MIPI_DPHY_MBIAS_CYCLES      0x100
> +
> +#define MIPI_PHY_TLP_EN_W           CSI2_REG_D(0x34)
> +#define MIPI_DPHY_ULPS_STOP_CYCLES  0xC
> +
> +#define MIPI_PHY_TLPOK	            CSI2_REG_D(0x38)
> +#define MIPI_DPHY_POWER_UP_CYCLES   0x100
> +
> +#define MIPI_PHY_TWD_INIT	    CSI2_REG_D(0x3c)
> +#define MIPI_DPHY_INIT_WATCH_DOG    0x400000
> +
> +#define MIPI_PHY_TWD_HS             CSI2_REG_D(0x40)
> +#define MIPI_DPHY_HS_WATCH_DOG      0x400000
> +
> +#define MIPI_PHY_MUX_CTRL0	    CSI2_REG_D(0x284)
> +#define MIPI_DPHY_LANE3_SELECT      GENMASK(3, 0)
> +#define MIPI_DPHY_LANE2_SELECT      GENMASK(7, 4)
> +#define MIPI_DPHY_LANE2_SELECT_SHIFT           4
> +#define MIPI_DPHY_LANE1_SELECT      GENMASK(11, 8)
> +#define MIPI_DPHY_LANE1_SELECT_SHIFT            8
> +#define MIPI_DPHY_LANE0_SELECT      GENMASK(14, 12)
> +
> +#define MIPI_PHY_MUX_CTRL1	    CSI2_REG_D(0x288)
> +#define MIPI_DPHY_LANE3_CTRL_SIGNAL GENMASK(3, 0)
> +#define MIPI_DPHY_LANE2_CTRL_SIGNAL GENMASK(7, 4)
> +#define MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT      4
> +#define MIPI_DPHY_LANE1_CTRL_SIGNAL GENMASK(11, 8)
> +#define MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT       8
> +#define MIPI_DPHY_LANE0_CTRL_SIGNAL GENMASK(14, 12)
> +#define MIPI_DPHY_CLK_SELECT        BIT(17)
> +
> +/* C3 CSI-2 HOST register */
> +#define CSI2_HOST_N_LANES           CSI2_REG_H(0x04)
> +#define CSI2_HOST_CSI2_RESETN       CSI2_REG_H(0x10)
> +#define CSI2_HOST_RESETN_DEFAULT    0x0
> +#define CSI2_HOST_RESETN_RST_VALUE  BIT(0)
> +
> +#define CSI2_HOST_MASK1             CSI2_REG_H(0x28)
> +#define CSI2_HOST_ERROR_MASK1       GENMASK(28, 0)
> +
> +#define MIPI_CSI2_MAX_WIDTH         2888
> +#define MIPI_CSI2_MIN_WIDTH         160
> +#define MIPI_CSI2_MAX_HEIGHT        2240
> +#define MIPI_CSI2_MIN_HEIGHT        120
> +#define MIPI_CSI2_DEFAULT_WIDTH     1920
> +#define MIPI_CSI2_DEFAULT_HEIGHT    1080
> +#define MIPI_CSI2_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
> +
> +/* C3 CSI-2 pad list */
> +enum {
> +	MIPI_CSI2_PAD_SINK,
> +	MIPI_CSI2_PAD_SRC,
> +	MIPI_CSI2_PAD_MAX
> +};
> +
> +/**
> + * struct csi_info - MIPI CSI2 information
> + *
> + * @clocks: array of MIPI CSI2 clock names
> + * @clock_rates: array of MIPI CSI2 clock rate
> + * @clock_num: actual clock number
> + */
> +struct csi_info {
> +	char *clocks[MIPI_CSI2_CLOCK_NUM_MAX];
> +	u32 clock_rates[MIPI_CSI2_CLOCK_NUM_MAX];
> +	u32 clock_num;
> +};
> +
> +/**
> + * struct csi_device - MIPI CSI2 platform device
> + *
> + * @dev: pointer to the struct device
> + * @aphy: MIPI CSI2 aphy register address
> + * @dphy: MIPI CSI2 dphy register address
> + * @host: MIPI CSI2 host register address
> + * @clks: array of MIPI CSI2 clocks
> + * @sd: MIPI CSI2 sub-device
> + * @pads: MIPI CSI2 sub-device pads
> + * @notifier: notifier to register on the v4l2-async API
> + * @src_sd: source sub-device
> + * @bus: MIPI CSI2 bus information
> + * @src_sd_pad: source sub-device pad
> + * @lock: protect MIPI CSI2 device
> + * @info: version-specific MIPI CSI2 information
> + */
> +struct csi_device {
> +	struct device *dev;
> +	void __iomem *aphy;
> +	void __iomem *dphy;
> +	void __iomem *host;
> +	struct clk_bulk_data clks[MIPI_CSI2_CLOCK_NUM_MAX];
> +
> +	struct v4l2_subdev sd;
> +	struct media_pad pads[MIPI_CSI2_PAD_MAX];
> +	struct v4l2_async_notifier notifier;
> +	struct v4l2_subdev *src_sd;
> +	struct v4l2_mbus_config_mipi_csi2 bus;
> +
> +	u16 src_sd_pad;
> +	struct mutex lock; /* Protect csi device */
> +	const struct csi_info *info;
> +};
> +
> +static const u32 c3_mipi_csi_formats[] = {
> +	MEDIA_BUS_FMT_SBGGR10_1X10,
> +	MEDIA_BUS_FMT_SGBRG10_1X10,
> +	MEDIA_BUS_FMT_SGRBG10_1X10,
> +	MEDIA_BUS_FMT_SRGGB10_1X10,
> +	MEDIA_BUS_FMT_SBGGR12_1X12,
> +	MEDIA_BUS_FMT_SGBRG12_1X12,
> +	MEDIA_BUS_FMT_SGRBG12_1X12,
> +	MEDIA_BUS_FMT_SRGGB12_1X12,
> +};
> +
> +/* Hardware configuration */
> +
> +static void c3_mipi_csi_write(struct csi_device *csi, u32 reg, u32 val)
> +{
> +	void __iomem *addr;
> +
> +	switch (CSI2_SUBMD(reg)) {
> +	case SUBMD_APHY:
> +		addr = csi->aphy + CSI2_REG_ADDR(reg);
> +		break;
> +	case SUBMD_DPHY:
> +		addr = csi->dphy + CSI2_REG_ADDR(reg);
> +		break;
> +	case SUBMD_HOST:
> +		addr = csi->host + CSI2_REG_ADDR(reg);
> +		break;
> +	default:
> +		dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
> +		return;
> +	}
> +
> +	writel(val, addr);
> +}
> +
> +static void c3_mipi_csi_update_bits(struct csi_device *csi, u32 reg,
> +				    u32 mask, u32 val)
> +{
> +	void __iomem *addr;
> +	u32 orig, tmp;
> +
> +	switch (CSI2_SUBMD(reg)) {
> +	case SUBMD_APHY:
> +		addr = csi->aphy + CSI2_REG_ADDR(reg);
> +		break;
> +	case SUBMD_DPHY:
> +		addr = csi->dphy + CSI2_REG_ADDR(reg);
> +		break;
> +	case SUBMD_HOST:
> +		addr = csi->host + CSI2_REG_ADDR(reg);
> +		break;
> +	default:
> +		dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
> +		return;
> +	}
> +
> +	orig = readl(addr);
> +	tmp = orig & ~mask;
> +	tmp |= val & mask;
> +
> +	if (tmp != orig)
> +		writel(tmp, addr);
> +}
> +
> +static void c3_mipi_csi_cfg_aphy(struct csi_device *csi, u32 lanes)
> +{
> +	c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL1, MIPI_APHY_NORMAL_CNTL1);
> +
> +	if (lanes == 4)
> +		c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_4LANES_CNTL2);
> +	else
> +		c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_NORMAL_CNTL2);
> +
> +	if (lanes == 2)
> +		c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL3, MIPI_APHY_2LANES_CNTL3);
> +}
> +
> +static void c3_mipi_csi_2lanes_setting(struct csi_device *csi)
> +{
> +	/* Disable lane 2 and lane 3 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0xf);
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
> +				0xf << MIPI_DPHY_LANE2_SELECT_SHIFT);
> +	/* Select analog data lane 1 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
> +				0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
> +	/* Select analog data lane 0 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
> +
> +	/* Disable lane 2 and lane 3 control signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0xf);
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
> +				0xf << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
> +	/* Select lane 1 signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
> +				0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
> +	/* Select lane 0 signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
> +	/* Select input 0 as clock */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
> +				MIPI_DPHY_CLK_SELECT);
> +}
> +
> +static void c3_mipi_csi_4lanes_setting(struct csi_device *csi)
> +{
> +	/* Select analog data lane 3 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0x3);
> +	/* Select analog data lane 2 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
> +				0x2 << MIPI_DPHY_LANE2_SELECT_SHIFT);
> +	/* Select analog data lane 1 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
> +				0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
> +	/* Select analog data lane 0 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
> +
> +	/* Select lane 3 signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0x3);
> +	/* Select lane 2 signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
> +				0x2 << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
> +	/* Select lane 1 signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
> +				0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
> +	/* Select lane 0 signal */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
> +	/* Select input 0 as clock */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
> +				MIPI_DPHY_CLK_SELECT);
> +}
> +
> +static void c3_mipi_csi_cfg_dphy(struct csi_device *csi, u32 lanes, s64 rate)
> +{
> +	u32 val;
> +	u32 settle;
> +
> +	/* Calculate the high speed settle */
> +	val = DIV_ROUND_UP(1000000000, rate);
> +	settle = (16 * val + 230) / 10;
> +
> +	c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL, MIPI_DPHY_CLK_CONTINUE_MODE);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TCLK_MISS, MIPI_DPHY_CLK_MISS);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TCLK_SETTLE, MIPI_DPHY_CLK_SETTLE);
> +	c3_mipi_csi_write(csi, MIPI_PHY_THS_EXIT, MIPI_DPHY_HS_EXIT);
> +	c3_mipi_csi_write(csi, MIPI_PHY_THS_SKIP, MIPI_DPHY_HS_SKIP);
> +	c3_mipi_csi_write(csi, MIPI_PHY_THS_SETTLE, settle);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TINIT, MIPI_DPHY_INIT_CYCLES);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TMBIAS, MIPI_DPHY_MBIAS_CYCLES);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TULPS_C, MIPI_DPHY_ULPS_CHECK_CYCLES);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TULPS_S, MIPI_DPHY_ULPS_START_CYCLES);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TLP_EN_W, MIPI_DPHY_ULPS_STOP_CYCLES);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TLPOK, MIPI_DPHY_POWER_UP_CYCLES);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TWD_INIT, MIPI_DPHY_INIT_WATCH_DOG);
> +	c3_mipi_csi_write(csi, MIPI_PHY_TWD_HS, MIPI_DPHY_HS_WATCH_DOG);
> +	c3_mipi_csi_write(csi, MIPI_PHY_DATA_LANE_CTRL, MIPI_DPHY_LANE_CTRL_DISABLE);
> +
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_INSERT_ERRESC,
> +				MIPI_DPHY_INSERT_ERRESC);
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_HS_SYNC_CHECK,
> +				MIPI_DPHY_HS_SYNC_CHECK);
> +	/*
> +	 * Set 5 pipe lines to the same high speed.
> +	 * Each bit for one pipe line.
> +	 */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_FIVE_HS_PIPE,
> +				0x1f << MIPI_DPHY_FIVE_HS_PIPE_SHIFT);
> +
> +	/* Output data with pipe line data. */
> +	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_DATA_PIPE_SELECT,
> +				0x3 << MIPI_DPHY_DATA_PIPE_SELECT_SHIFT);
> +	if (lanes == 2)
> +		c3_mipi_csi_2lanes_setting(csi);
> +	else
> +		c3_mipi_csi_4lanes_setting(csi);
> +
> +	/* Enable digital data and clock lanes */
> +	c3_mipi_csi_write(csi, MIPI_PHY_CTRL, MIPI_DPHY_LANES_ENABLE);
> +}
> +
> +static void c3_mipi_csi_cfg_host(struct csi_device *csi, u32 lanes)
> +{
> +	/* Reset CSI-2 controller output */
> +	c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_DEFAULT);
> +	c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_RST_VALUE);
> +
> +	/* Set data lane number */
> +	c3_mipi_csi_write(csi, CSI2_HOST_N_LANES, lanes - 1);
> +
> +	/* Enable error mask */
> +	c3_mipi_csi_write(csi, CSI2_HOST_MASK1, CSI2_HOST_ERROR_MASK1);
> +}
> +
> +static int c3_mipi_csi_start_stream(struct csi_device *csi)
> +{
> +	s64 link_freq;
> +	s64 lane_rate;
> +
> +	link_freq = v4l2_get_link_freq(csi->src_sd->ctrl_handler, 0, 0);
> +	if (link_freq < 0) {
> +		dev_err(csi->dev, "Unable to obtain link frequency: %lld\n", link_freq);
> +		return link_freq;
> +	}
> +
> +	lane_rate = link_freq * 2;
> +	if (lane_rate > 1500000000)
> +		return -EINVAL;
> +
> +	c3_mipi_csi_cfg_aphy(csi, csi->bus.num_data_lanes);
> +	c3_mipi_csi_cfg_dphy(csi, csi->bus.num_data_lanes, lane_rate);
> +	c3_mipi_csi_cfg_host(csi, csi->bus.num_data_lanes);
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_enable_streams(struct v4l2_subdev *sd,
> +				      struct v4l2_subdev_state *state,
> +				      u32 pad, u64 streams_mask)
> +{
> +	struct csi_device *csi = v4l2_get_subdevdata(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	guard(mutex)(&csi->lock);
> +
> +	pm_runtime_resume_and_get(csi->dev);
> +
> +	c3_mipi_csi_start_stream(csi);
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       MIPI_CSI2_PAD_SINK,
> +						       &streams_mask);
> +	ret = v4l2_subdev_enable_streams(csi->src_sd,
> +					 csi->src_sd_pad,
> +					 sink_streams);
> +	if (ret) {
> +		pm_runtime_put(csi->dev);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_disable_streams(struct v4l2_subdev *sd,
> +				       struct v4l2_subdev_state *state,
> +				       u32 pad, u64 streams_mask)
> +{
> +	struct csi_device *csi = v4l2_get_subdevdata(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	guard(mutex)(&csi->lock);
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       MIPI_CSI2_PAD_SINK,
> +						       &streams_mask);
> +	ret = v4l2_subdev_disable_streams(csi->src_sd,
> +					  csi->src_sd_pad,
> +					  sink_streams);
> +	if (ret)
> +		dev_err(csi->dev, "Failed to disable %s\n", csi->src_sd->name);
> +
> +	pm_runtime_put(csi->dev);
> +
> +	return ret;
> +}
> +
> +static int c3_mipi_csi_cfg_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   struct v4l2_subdev_krouting *routing)
> +{
> +	static const struct v4l2_mbus_framefmt format = {
> +		.width = MIPI_CSI2_DEFAULT_WIDTH,
> +		.height = MIPI_CSI2_DEFAULT_HEIGHT,
> +		.code = MIPI_CSI2_DEFAULT_FMT,
> +		.field = V4L2_FIELD_NONE,
> +		.colorspace = V4L2_COLORSPACE_RAW,
> +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
> +		.xfer_func = V4L2_XFER_FUNC_NONE,
> +	};
> +	int ret;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_init_routing(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes;
> +	struct v4l2_subdev_krouting routing;
> +
> +	routes.sink_pad = MIPI_CSI2_PAD_SINK;
> +	routes.sink_stream = 0;
> +	routes.source_pad = MIPI_CSI2_PAD_SRC;
> +	routes.source_stream = 0;
> +	routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> +
> +	routing.num_routes = 1;
> +	routing.routes = &routes;
> +
> +	return c3_mipi_csi_cfg_routing(sd, state, &routing);
> +}
> +
> +static int c3_mipi_csi_set_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   enum v4l2_subdev_format_whence which,
> +				   struct v4l2_subdev_krouting *routing)
> +{
> +	bool is_streaming = v4l2_subdev_is_streaming(sd);
> +
> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> +		return -EBUSY;
> +
> +	return c3_mipi_csi_cfg_routing(sd, state, routing);
> +}
> +
> +static int c3_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
> +				      struct v4l2_subdev_state *state,
> +				      struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	switch (code->pad) {
> +	case MIPI_CSI2_PAD_SINK:
> +		if (code->index >= ARRAY_SIZE(c3_mipi_csi_formats))
> +			return -EINVAL;
> +
> +		code->code = c3_mipi_csi_formats[code->index];
> +		break;
> +	case MIPI_CSI2_PAD_SRC:
> +		struct v4l2_mbus_framefmt *fmt;
> +
> +		if (code->index > 0)
> +			return -EINVAL;
> +
> +		fmt = v4l2_subdev_state_get_format(state, code->pad);
> +		code->code = fmt->code;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_set_fmt(struct v4l2_subdev *sd,
> +			       struct v4l2_subdev_state *state,
> +			       struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +	unsigned int i;
> +
> +	if (format->pad != MIPI_CSI2_PAD_SINK)
> +		return v4l2_subdev_get_fmt(sd, state, format);
> +
> +	fmt = v4l2_subdev_state_get_format(state, format->pad);
> +
> +	for (i = 0; i < ARRAY_SIZE(c3_mipi_csi_formats); i++)
> +		if (format->format.code == c3_mipi_csi_formats[i])
> +			break;
> +
> +	if (i == ARRAY_SIZE(c3_mipi_csi_formats))
> +		fmt->code = c3_mipi_csi_formats[0];
> +	else
> +		fmt->code = c3_mipi_csi_formats[i];
> +
> +	fmt->width = clamp_t(u32, format->format.width,
> +			     MIPI_CSI2_MIN_WIDTH, MIPI_CSI2_MAX_WIDTH);
> +	fmt->height = clamp_t(u32, format->format.height,
> +			      MIPI_CSI2_MIN_HEIGHT, MIPI_CSI2_MAX_HEIGHT);
> +
> +	format->format = *fmt;
> +
> +	/* Synchronize the format to source pad */
> +	fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
> +	*fmt = format->format;
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_init_state(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SINK);
> +	src_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
> +
> +	sink_fmt->width = MIPI_CSI2_DEFAULT_WIDTH;
> +	sink_fmt->height = MIPI_CSI2_DEFAULT_HEIGHT;
> +	sink_fmt->field = V4L2_FIELD_NONE;
> +	sink_fmt->code = MIPI_CSI2_DEFAULT_FMT;
> +	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> +	sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
> +	sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
> +	sink_fmt->quantization =
> +		V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
> +					      sink_fmt->ycbcr_enc);
> +	*src_fmt = *sink_fmt;
> +
> +	return c3_mipi_csi_init_routing(sd, state);
> +}
> +
> +static const struct v4l2_subdev_pad_ops c3_mipi_csi_pad_ops = {
> +	.enum_mbus_code = c3_mipi_csi_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = c3_mipi_csi_set_fmt,
> +	.set_routing = c3_mipi_csi_set_routing,
> +	.enable_streams = c3_mipi_csi_enable_streams,
> +	.disable_streams = c3_mipi_csi_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops c3_mipi_csi_subdev_ops = {
> +	.pad = &c3_mipi_csi_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops c3_mipi_csi_internal_ops = {
> +	.init_state = c3_mipi_csi_init_state,
> +};
> +
> +/* Media entity operations */
> +static const struct media_entity_operations c3_mipi_csi_entity_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +/* PM runtime */
> +
> +static int __maybe_unused c3_mipi_csi_runtime_suspend(struct device *dev)
> +{
> +	struct csi_device *csi = dev_get_drvdata(dev);
> +
> +	clk_bulk_disable_unprepare(csi->info->clock_num, csi->clks);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused c3_mipi_csi_runtime_resume(struct device *dev)
> +{
> +	struct csi_device *csi = dev_get_drvdata(dev);
> +
> +	return clk_bulk_prepare_enable(csi->info->clock_num, csi->clks);
> +}
> +
> +static const struct dev_pm_ops c3_mipi_csi_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> +				pm_runtime_force_resume)
> +	SET_RUNTIME_PM_OPS(c3_mipi_csi_runtime_suspend,
> +			   c3_mipi_csi_runtime_resume, NULL)
> +};
> +
> +/* Probe/remove & platform driver */
> +
> +static int c3_mipi_csi_subdev_init(struct csi_device *csi)
> +{
> +	struct v4l2_subdev *sd = &csi->sd;
> +	int ret;
> +
> +	v4l2_subdev_init(sd, &c3_mipi_csi_subdev_ops);
> +	sd->owner = THIS_MODULE;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	sd->internal_ops = &c3_mipi_csi_internal_ops;
> +	snprintf(sd->name, sizeof(sd->name), "%s", MIPI_CSI2_SUBDEV_NAME);
> +
> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	sd->entity.ops = &c3_mipi_csi_entity_ops;
> +
> +	sd->dev = csi->dev;
> +	v4l2_set_subdevdata(sd, csi);
> +
> +	csi->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +	csi->pads[MIPI_CSI2_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sd->entity, MIPI_CSI2_PAD_MAX, csi->pads);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret) {
> +		media_entity_cleanup(&sd->entity);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void c3_mipi_csi_subdev_deinit(struct csi_device *csi)
> +{
> +	v4l2_subdev_cleanup(&csi->sd);
> +	media_entity_cleanup(&csi->sd.entity);
> +}
> +
> +/* Subdev notifier register */
> +static int c3_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier,
> +				    struct v4l2_subdev *sd,
> +				    struct v4l2_async_connection *asc)
> +{
> +	struct csi_device *csi = v4l2_get_subdevdata(notifier->sd);
> +	struct media_pad *sink = &csi->sd.entity.pads[MIPI_CSI2_PAD_SINK];
> +	int ret;
> +
> +	ret = media_entity_get_fwnode_pad(&sd->entity,
> +					  sd->fwnode, MEDIA_PAD_FL_SOURCE);
> +	if (ret < 0) {
> +		dev_err(csi->dev, "Failed to find pad for %s\n", sd->name);
> +		return ret;
> +	}
> +
> +	csi->src_sd = sd;
> +	csi->src_sd_pad = ret;
> +
> +	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
> +					       MEDIA_LNK_FL_IMMUTABLE);
> +}
> +
> +static const struct v4l2_async_notifier_operations c3_mipi_csi_notify_ops = {
> +	.bound = c3_mipi_csi_notify_bound,
> +};
> +
> +static int c3_mipi_csi_async_register(struct csi_device *csi)
> +{
> +	struct v4l2_fwnode_endpoint vep = {
> +		.bus_type = V4L2_MBUS_CSI2_DPHY,
> +	};
> +	struct v4l2_async_connection *asc;
> +	struct fwnode_handle *ep;
> +	int ret;
> +
> +	v4l2_async_subdev_nf_init(&csi->notifier, &csi->sd);
> +
> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0,
> +					     FWNODE_GRAPH_ENDPOINT_NEXT);
> +	if (!ep)
> +		return -ENOTCONN;
> +
> +	ret = v4l2_fwnode_endpoint_parse(ep, &vep);
> +	if (ret)
> +		goto err_put_handle;
> +
> +	csi->bus = vep.bus.mipi_csi2;
> +	if (csi->bus.num_data_lanes != 2 && csi->bus.num_data_lanes != 4)
> +		goto err_put_handle;
> +
> +	asc = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep,
> +					      struct v4l2_async_connection);
> +	if (IS_ERR(asc)) {
> +		ret = PTR_ERR(asc);
> +		goto err_put_handle;
> +	}
> +
> +	csi->notifier.ops = &c3_mipi_csi_notify_ops;
> +	ret = v4l2_async_nf_register(&csi->notifier);
> +	if (ret)
> +		goto err_cleanup_nf;
> +
> +	ret = v4l2_async_register_subdev(&csi->sd);
> +	if (ret)
> +		goto err_unregister_nf;
> +
> +	fwnode_handle_put(ep);
> +
> +	return 0;
> +
> +err_unregister_nf:
> +	v4l2_async_nf_unregister(&csi->notifier);
> +err_cleanup_nf:
> +	v4l2_async_nf_cleanup(&csi->notifier);
> +err_put_handle:
> +	fwnode_handle_put(ep);
> +	return ret;
> +}
> +
> +static void c3_mipi_csi_async_unregister(struct csi_device *csi)
> +{
> +	v4l2_async_unregister_subdev(&csi->sd);
> +	v4l2_async_nf_unregister(&csi->notifier);
> +	v4l2_async_nf_cleanup(&csi->notifier);
> +}
> +
> +static int c3_mipi_csi_ioremap_resource(struct csi_device *csi)
> +{
> +	struct device *dev = csi->dev;
> +	struct platform_device *pdev = to_platform_device(dev);
> +
> +	csi->aphy = devm_platform_ioremap_resource_byname(pdev, "aphy");
> +	if (IS_ERR(csi->aphy))
> +		return PTR_ERR(csi->aphy);
> +
> +	csi->dphy = devm_platform_ioremap_resource_byname(pdev, "dphy");
> +	if (IS_ERR(csi->dphy))
> +		return PTR_ERR(csi->dphy);
> +
> +	csi->host = devm_platform_ioremap_resource_byname(pdev, "host");
> +	if (IS_ERR(csi->host))
> +		return PTR_ERR(csi->host);
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_configure_clocks(struct csi_device *csi)
> +{
> +	const struct csi_info *info = csi->info;
> +	int ret;
> +	u32 i;
> +
> +	for (i = 0; i < info->clock_num; i++)
> +		csi->clks[i].id = info->clocks[i];
> +
> +	ret = devm_clk_bulk_get(csi->dev, info->clock_num, csi->clks);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < info->clock_num; i++) {
> +		if (!info->clock_rates[i])
> +			continue;
> +		ret = clk_set_rate(csi->clks[i].clk, info->clock_rates[i]);
> +		if (ret) {
> +			dev_err(csi->dev, "Failed to set %s rate %u\n", info->clocks[i],
> +				info->clock_rates[i]);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_csi_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct csi_device *csi;
> +	int ret;
> +
> +	csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
> +	if (!csi)
> +		return -ENOMEM;
> +
> +	csi->info = of_device_get_match_data(dev);
> +	csi->dev = dev;
> +
> +	ret = c3_mipi_csi_ioremap_resource(csi);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
> +
> +	ret = c3_mipi_csi_configure_clocks(csi);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to configure clocks\n");
> +
> +	platform_set_drvdata(pdev, csi);
> +
> +	mutex_init(&csi->lock);
> +	pm_runtime_enable(dev);
> +
> +	ret = c3_mipi_csi_subdev_init(csi);
> +	if (ret)
> +		goto err_disable_runtime_pm;
> +
> +	ret = c3_mipi_csi_async_register(csi);
> +	if (ret)
> +		goto err_deinit_subdev;
> +
> +	return 0;
> +
> +err_deinit_subdev:
> +	c3_mipi_csi_subdev_deinit(csi);
> +err_disable_runtime_pm:
> +	pm_runtime_disable(dev);
> +	mutex_destroy(&csi->lock);
> +	return ret;
> +};
> +
> +static void c3_mipi_csi_remove(struct platform_device *pdev)
> +{
> +	struct csi_device *csi = platform_get_drvdata(pdev);
> +
> +	c3_mipi_csi_async_unregister(csi);
> +	c3_mipi_csi_subdev_deinit(csi);
> +
> +	pm_runtime_disable(&pdev->dev);
> +	mutex_destroy(&csi->lock);
> +};
> +
> +static const struct csi_info c3_mipi_csi_info = {
> +	.clocks = {"vapb", "phy0"},
> +	.clock_rates = {0, 200000000},
> +	.clock_num = 2
> +};
> +
> +static const struct of_device_id c3_mipi_csi_of_match[] = {
> +	{ .compatible = "amlogic,c3-mipi-csi2",
> +	  .data = &c3_mipi_csi_info,
> +	},
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, c3_mipi_csi_of_match);
> +
> +static struct platform_driver c3_mipi_csi_driver = {
> +	.probe = c3_mipi_csi_probe,
> +	.remove = c3_mipi_csi_remove,
> +	.driver = {
> +		.name = "c3-mipi-csi2",
> +		.of_match_table = c3_mipi_csi_of_match,
> +		.pm = &c3_mipi_csi_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(c3_mipi_csi_driver);
> +
> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
> +MODULE_DESCRIPTION("Amlogic C3 MIPI CSI-2 receiver");
> +MODULE_LICENSE("GPL");
>
> --
> 2.46.1
>
>
>

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

* Re: [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver
  2024-11-04 17:50   ` Jacopo Mondi
@ 2024-11-05 10:06     ` Jacopo Mondi
  2024-11-05 11:40       ` Keke Li
  2024-11-05 11:21     ` Keke Li
  1 sibling, 1 reply; 37+ messages in thread
From: Jacopo Mondi @ 2024-11-05 10:06 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: keke.li, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally,
	tomi.valkeinen@ideasonboard.com

Hi again Keke

On Mon, Nov 04, 2024 at 06:50:11PM +0100, Jacopo Mondi wrote:
> Hi Keke
>    sorry for the late feedback, hope you're still interested in
> upstreaming this driver
>
> On Wed, Sep 18, 2024 at 02:07:13PM +0800, Keke Li via B4 Relay wrote:
> > From: Keke Li <keke.li@amlogic.com>
> >
> > This driver is used to receive mipi data from image sensor.
> >
> > Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> > Signed-off-by: Keke Li <keke.li@amlogic.com>
> > ---
> >  MAINTAINERS                                        |   7 +
> >  drivers/media/platform/amlogic/Kconfig             |   1 +
> >  drivers/media/platform/amlogic/Makefile            |   2 +
> >  .../media/platform/amlogic/c3-mipi-csi2/Kconfig    |  16 +
> >  .../media/platform/amlogic/c3-mipi-csi2/Makefile   |   3 +
> >  .../platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c   | 910 +++++++++++++++++++++
> >  6 files changed, 939 insertions(+)
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 2cdd7cacec86..9e75874a6e69 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -1209,6 +1209,13 @@ F:	Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
> >  F:	drivers/perf/amlogic/
> >  F:	include/soc/amlogic/
> >
> > +AMLOGIC MIPI CSI2 DRIVER
> > +M:	Keke Li <keke.li@amlogic.com>
> > +L:	linux-media@vger.kernel.org
> > +S:	Maintained
> > +F:	Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml
> > +F:	drivers/media/platform/amlogic/c3-mipi-csi2/
> > +
> >  AMPHENOL CHIPCAP 2 HUMIDITY-TEMPERATURE IIO DRIVER
> >  M:	Javier Carrasco <javier.carrasco.cruz@gmail.com>
> >  L:	linux-hwmon@vger.kernel.org
> > diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
> > index 5014957404e9..b7c2de14848b 100644
> > --- a/drivers/media/platform/amlogic/Kconfig
> > +++ b/drivers/media/platform/amlogic/Kconfig
> > @@ -2,4 +2,5 @@
> >
> >  comment "Amlogic media platform drivers"
> >
> > +source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
> >  source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
> > diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
> > index d3cdb8fa4ddb..4f571ce5d13e 100644
> > --- a/drivers/media/platform/amlogic/Makefile
> > +++ b/drivers/media/platform/amlogic/Makefile
> > @@ -1,2 +1,4 @@
> >  # SPDX-License-Identifier: GPL-2.0-only
> > +
> > +obj-y += c3-mipi-csi2/
> >  obj-y += meson-ge2d/
> > diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
> > new file mode 100644
> > index 000000000000..0d7b2e203273
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
> > @@ -0,0 +1,16 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +
> > +config VIDEO_C3_MIPI_CSI2
> > +	tristate "Amlogic C3 MIPI CSI-2 receiver"
> > +	depends on ARCH_MESON || COMPILE_TEST
> > +	depends on VIDEO_DEV
> > +	depends on OF
> > +	select MEDIA_CONTROLLER
> > +	select V4L2_FWNODE
> > +	select VIDEO_V4L2_SUBDEV_API
> > +	help
> > +	  Video4Linux2 driver for Amlogic C3 MIPI CSI-2 receiver.
> > +	  C3 MIPI CSI-2 receiver is used to receive MIPI data from
> > +	  image sensor.
> > +
> > +	  To compile this driver as a module choose m here.
> > diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
> > new file mode 100644
> > index 000000000000..cc08fc722bfd
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
> > @@ -0,0 +1,3 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +
> > +obj-$(CONFIG_VIDEO_C3_MIPI_CSI2) += c3-mipi-csi2.o
> > diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
> > new file mode 100644
> > index 000000000000..6ac60d5b26a8
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
> > @@ -0,0 +1,910 @@
> > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > +/*
> > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > + */
> > +
> > +#include <linux/cleanup.h>
> > +#include <linux/clk.h>
> > +#include <linux/device.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +
> > +#include <media/v4l2-async.h>
> > +#include <media/v4l2-common.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-mc.h>
> > +#include <media/v4l2-subdev.h>
> > +
> > +/* C3 CSI-2 submodule definition */
> > +enum {
> > +	SUBMD_APHY,
> > +	SUBMD_DPHY,
> > +	SUBMD_HOST,
> > +};
> > +
> > +#define CSI2_SUBMD_MASK             GENMASK(17, 16)
> > +#define CSI2_SUBMD_SHIFT            16
> > +#define CSI2_SUBMD(x)               (((x) & (CSI2_SUBMD_MASK)) >> (CSI2_SUBMD_SHIFT))
> > +#define CSI2_REG_ADDR_MASK          GENMASK(15, 0)
> > +#define CSI2_REG_ADDR(x)            ((x) & (CSI2_REG_ADDR_MASK))
> > +#define CSI2_REG_A(x)               ((SUBMD_APHY << CSI2_SUBMD_SHIFT) | (x))
> > +#define CSI2_REG_D(x)               ((SUBMD_DPHY << CSI2_SUBMD_SHIFT) | (x))
> > +#define CSI2_REG_H(x)               ((SUBMD_HOST << CSI2_SUBMD_SHIFT) | (x))
> > +
> > +#define MIPI_CSI2_CLOCK_NUM_MAX     3
> > +#define MIPI_CSI2_SUBDEV_NAME       "mipi-csi2"
>
> Isn't the name too generic ? Should it report at least "c3-mipi-csi2"
> ?
> > +
> > +/* C3 CSI-2 APHY register */
> > +#define MIPI_CSI_2M_PHY2_CNTL1      CSI2_REG_A(0x44)
> > +#define MIPI_APHY_NORMAL_CNTL1      0x3f425C00
>
> All other hex addresses use small capitals for letters
>
> > +
> > +#define MIPI_CSI_2M_PHY2_CNTL2      CSI2_REG_A(0x48)
> > +#define MIPI_APHY_4LANES_CNTL2      0x033a0000
> > +#define MIPI_APHY_NORMAL_CNTL2      0x333a0000
> > +
> > +#define MIPI_CSI_2M_PHY2_CNTL3      CSI2_REG_A(0x4c)
> > +#define MIPI_APHY_2LANES_CNTL3      0x03800000
> > +
> > +/* C3 CSI-2 DPHY register */
> > +#define MIPI_PHY_CTRL	            CSI2_REG_D(0x00)
> > +#define MIPI_DPHY_LANES_ENABLE      0x0
> > +
> > +#define MIPI_PHY_CLK_LANE_CTRL	    CSI2_REG_D(0x04)
> > +#define MIPI_DPHY_CLK_CONTINUE_MODE 0x3d8
> > +
> > +#define MIPI_PHY_DATA_LANE_CTRL     CSI2_REG_D(0x08)
> > +#define MIPI_DPHY_LANE_CTRL_DISABLE 0x0
> > +
> > +#define MIPI_PHY_DATA_LANE_CTRL1    CSI2_REG_D(0x0c)
> > +#define MIPI_DPHY_INSERT_ERRESC     BIT(0)
> > +#define MIPI_DPHY_HS_SYNC_CHECK     BIT(1)
> > +#define MIPI_DPHY_FIVE_HS_PIPE      GENMASK(6, 2)
> > +#define MIPI_DPHY_FIVE_HS_PIPE_SHIFT           2
> > +#define MIPI_DPHY_DATA_PIPE_SELECT  GENMASK(9, 7)
> > +#define MIPI_DPHY_DATA_PIPE_SELECT_SHIFT       7
> > +
> > +#define MIPI_PHY_TCLK_MISS	    CSI2_REG_D(0x10)
> > +#define MIPI_DPHY_CLK_MISS          0x9
> > +
> > +#define MIPI_PHY_TCLK_SETTLE	    CSI2_REG_D(0x14)
> > +#define MIPI_DPHY_CLK_SETTLE        0x1F
> > +
> > +#define MIPI_PHY_THS_EXIT	    CSI2_REG_D(0x18)
> > +#define MIPI_DPHY_HS_EXIT           0x8
> > +
> > +#define MIPI_PHY_THS_SKIP	    CSI2_REG_D(0x1c)
> > +#define MIPI_DPHY_HS_SKIP           0xa
> > +
> > +#define MIPI_PHY_THS_SETTLE	    CSI2_REG_D(0x20)
> > +#define MIPI_PHY_TINIT	            CSI2_REG_D(0x24)
> > +#define MIPI_DPHY_INIT_CYCLES       0x4e20
> > +
> > +#define MIPI_PHY_TULPS_C	    CSI2_REG_D(0x28)
> > +#define MIPI_DPHY_ULPS_CHECK_CYCLES 0x1000
> > +
> > +#define MIPI_PHY_TULPS_S	    CSI2_REG_D(0x2c)
> > +#define MIPI_DPHY_ULPS_START_CYCLES 0x100
> > +
> > +#define MIPI_PHY_TMBIAS             CSI2_REG_D(0x30)
> > +#define MIPI_DPHY_MBIAS_CYCLES      0x100
> > +
> > +#define MIPI_PHY_TLP_EN_W           CSI2_REG_D(0x34)
> > +#define MIPI_DPHY_ULPS_STOP_CYCLES  0xC
> > +
> > +#define MIPI_PHY_TLPOK	            CSI2_REG_D(0x38)
> > +#define MIPI_DPHY_POWER_UP_CYCLES   0x100
> > +
> > +#define MIPI_PHY_TWD_INIT	    CSI2_REG_D(0x3c)
> > +#define MIPI_DPHY_INIT_WATCH_DOG    0x400000
> > +
> > +#define MIPI_PHY_TWD_HS             CSI2_REG_D(0x40)
> > +#define MIPI_DPHY_HS_WATCH_DOG      0x400000
> > +
> > +#define MIPI_PHY_MUX_CTRL0	    CSI2_REG_D(0x284)
> > +#define MIPI_DPHY_LANE3_SELECT      GENMASK(3, 0)
> > +#define MIPI_DPHY_LANE2_SELECT      GENMASK(7, 4)
> > +#define MIPI_DPHY_LANE2_SELECT_SHIFT           4
> > +#define MIPI_DPHY_LANE1_SELECT      GENMASK(11, 8)
> > +#define MIPI_DPHY_LANE1_SELECT_SHIFT            8
> > +#define MIPI_DPHY_LANE0_SELECT      GENMASK(14, 12)
> > +
> > +#define MIPI_PHY_MUX_CTRL1	    CSI2_REG_D(0x288)
> > +#define MIPI_DPHY_LANE3_CTRL_SIGNAL GENMASK(3, 0)
> > +#define MIPI_DPHY_LANE2_CTRL_SIGNAL GENMASK(7, 4)
> > +#define MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT      4
> > +#define MIPI_DPHY_LANE1_CTRL_SIGNAL GENMASK(11, 8)
> > +#define MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT       8
> > +#define MIPI_DPHY_LANE0_CTRL_SIGNAL GENMASK(14, 12)
> > +#define MIPI_DPHY_CLK_SELECT        BIT(17)
> > +
> > +/* C3 CSI-2 HOST register */
> > +#define CSI2_HOST_N_LANES           CSI2_REG_H(0x04)
> > +#define CSI2_HOST_CSI2_RESETN       CSI2_REG_H(0x10)
> > +#define CSI2_HOST_RESETN_DEFAULT    0x0
> > +#define CSI2_HOST_RESETN_RST_VALUE  BIT(0)
> > +
> > +#define CSI2_HOST_MASK1             CSI2_REG_H(0x28)
> > +#define CSI2_HOST_ERROR_MASK1       GENMASK(28, 0)
> > +
> > +#define MIPI_CSI2_MAX_WIDTH         2888
> > +#define MIPI_CSI2_MIN_WIDTH         160
> > +#define MIPI_CSI2_MAX_HEIGHT        2240
> > +#define MIPI_CSI2_MIN_HEIGHT        120
> > +#define MIPI_CSI2_DEFAULT_WIDTH     1920
> > +#define MIPI_CSI2_DEFAULT_HEIGHT    1080
> > +#define MIPI_CSI2_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
> > +
> > +/* C3 CSI-2 pad list */
> > +enum {
> > +	MIPI_CSI2_PAD_SINK,
> > +	MIPI_CSI2_PAD_SRC,
> > +	MIPI_CSI2_PAD_MAX
> > +};
> > +
> > +/**
>
> You don't need to kernel-doc in-driver types and functions.
> Documentation is always good, but this won't be parsed by kernel-doc
> (afaiu) so you should drop one * from /**
>
> > + * struct csi_info - MIPI CSI2 information
> > + *
> > + * @clocks: array of MIPI CSI2 clock names
> > + * @clock_rates: array of MIPI CSI2 clock rate
> > + * @clock_num: actual clock number
> > + */
> > +struct csi_info {
> > +	char *clocks[MIPI_CSI2_CLOCK_NUM_MAX];
> > +	u32 clock_rates[MIPI_CSI2_CLOCK_NUM_MAX];
> > +	u32 clock_num;
> > +};
> > +
> > +/**
> > + * struct csi_device - MIPI CSI2 platform device
> > + *
> > + * @dev: pointer to the struct device
> > + * @aphy: MIPI CSI2 aphy register address
> > + * @dphy: MIPI CSI2 dphy register address
> > + * @host: MIPI CSI2 host register address
> > + * @clks: array of MIPI CSI2 clocks
> > + * @sd: MIPI CSI2 sub-device
> > + * @pads: MIPI CSI2 sub-device pads
> > + * @notifier: notifier to register on the v4l2-async API
> > + * @src_sd: source sub-device
> > + * @bus: MIPI CSI2 bus information
> > + * @src_sd_pad: source sub-device pad
> > + * @lock: protect MIPI CSI2 device
> > + * @info: version-specific MIPI CSI2 information
> > + */
> > +struct csi_device {
> > +	struct device *dev;
> > +	void __iomem *aphy;
> > +	void __iomem *dphy;
> > +	void __iomem *host;
> > +	struct clk_bulk_data clks[MIPI_CSI2_CLOCK_NUM_MAX];
> > +
> > +	struct v4l2_subdev sd;
> > +	struct media_pad pads[MIPI_CSI2_PAD_MAX];
> > +	struct v4l2_async_notifier notifier;
> > +	struct v4l2_subdev *src_sd;
> > +	struct v4l2_mbus_config_mipi_csi2 bus;
> > +
> > +	u16 src_sd_pad;
> > +	struct mutex lock; /* Protect csi device */
>
> All the operations which receive a subdev_state are guaranteed to be locked
> so you can avoid manually locking in enable/disable streams
> (and drop #include cleanup.h if you don't use guards in any other
> place)
>
> > +	const struct csi_info *info;
> > +};
> > +
> > +static const u32 c3_mipi_csi_formats[] = {
> > +	MEDIA_BUS_FMT_SBGGR10_1X10,
> > +	MEDIA_BUS_FMT_SGBRG10_1X10,
> > +	MEDIA_BUS_FMT_SGRBG10_1X10,
> > +	MEDIA_BUS_FMT_SRGGB10_1X10,
> > +	MEDIA_BUS_FMT_SBGGR12_1X12,
> > +	MEDIA_BUS_FMT_SGBRG12_1X12,
> > +	MEDIA_BUS_FMT_SGRBG12_1X12,
> > +	MEDIA_BUS_FMT_SRGGB12_1X12,
> > +};
> > +
> > +/* Hardware configuration */
> > +
> > +static void c3_mipi_csi_write(struct csi_device *csi, u32 reg, u32 val)
> > +{
> > +	void __iomem *addr;
> > +
> > +	switch (CSI2_SUBMD(reg)) {
> > +	case SUBMD_APHY:
> > +		addr = csi->aphy + CSI2_REG_ADDR(reg);
> > +		break;
> > +	case SUBMD_DPHY:
> > +		addr = csi->dphy + CSI2_REG_ADDR(reg);
> > +		break;
> > +	case SUBMD_HOST:
> > +		addr = csi->host + CSI2_REG_ADDR(reg);
> > +		break;
> > +	default:
> > +		dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
> > +		return;
> > +	}
> > +
> > +	writel(val, addr);
> > +}
> > +
> > +static void c3_mipi_csi_update_bits(struct csi_device *csi, u32 reg,
> > +				    u32 mask, u32 val)
> > +{
> > +	void __iomem *addr;
> > +	u32 orig, tmp;
> > +
> > +	switch (CSI2_SUBMD(reg)) {
> > +	case SUBMD_APHY:
> > +		addr = csi->aphy + CSI2_REG_ADDR(reg);
> > +		break;
> > +	case SUBMD_DPHY:
> > +		addr = csi->dphy + CSI2_REG_ADDR(reg);
> > +		break;
> > +	case SUBMD_HOST:
> > +		addr = csi->host + CSI2_REG_ADDR(reg);
> > +		break;
> > +	default:
> > +		dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
> > +		return;
> > +	}
>
> This is repeated in two functions and could be grouped to a common
> place. Up to you
>
> > +
> > +	orig = readl(addr);
> > +	tmp = orig & ~mask;
> > +	tmp |= val & mask;
> > +
> > +	if (tmp != orig)
> > +		writel(tmp, addr);
> > +}
> > +
> > +static void c3_mipi_csi_cfg_aphy(struct csi_device *csi, u32 lanes)
> > +{
> > +	c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL1, MIPI_APHY_NORMAL_CNTL1);
> > +
> > +	if (lanes == 4)
> > +		c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_4LANES_CNTL2);
> > +	else
> > +		c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_NORMAL_CNTL2);
> > +
> > +	if (lanes == 2)
> > +		c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL3, MIPI_APHY_2LANES_CNTL3);
>
> The driver seems to only accept 2 or 4 lanes. What is
> MIPI_APHY_NORMAL_CNTL2 for ?
>
> > +}
> > +
> > +static void c3_mipi_csi_2lanes_setting(struct csi_device *csi)
> > +{
> > +	/* Disable lane 2 and lane 3 */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0xf);
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
> > +				0xf << MIPI_DPHY_LANE2_SELECT_SHIFT);
> > +	/* Select analog data lane 1 */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
> > +				0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
> > +	/* Select analog data lane 0 */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
> > +
> > +	/* Disable lane 2 and lane 3 control signal */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0xf);
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
> > +				0xf << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
> > +	/* Select lane 1 signal */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
> > +				0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
> > +	/* Select lane 0 signal */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
> > +	/* Select input 0 as clock */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
> > +				MIPI_DPHY_CLK_SELECT);
> > +}
> > +
> > +static void c3_mipi_csi_4lanes_setting(struct csi_device *csi)
> > +{
> > +	/* Select analog data lane 3 */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0x3);
> > +	/* Select analog data lane 2 */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
> > +				0x2 << MIPI_DPHY_LANE2_SELECT_SHIFT);
> > +	/* Select analog data lane 1 */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
> > +				0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
> > +	/* Select analog data lane 0 */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
> > +
> > +	/* Select lane 3 signal */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0x3);
> > +	/* Select lane 2 signal */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
> > +				0x2 << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
> > +	/* Select lane 1 signal */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
> > +				0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
> > +	/* Select lane 0 signal */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
> > +	/* Select input 0 as clock */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
> > +				MIPI_DPHY_CLK_SELECT);
> > +}
> > +
> > +static void c3_mipi_csi_cfg_dphy(struct csi_device *csi, u32 lanes, s64 rate)
> > +{
> > +	u32 val;
> > +	u32 settle;
> > +
> > +	/* Calculate the high speed settle */
> > +	val = DIV_ROUND_UP(1000000000, rate);
> > +	settle = (16 * val + 230) / 10;
> > +
> > +	c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL, MIPI_DPHY_CLK_CONTINUE_MODE);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_TCLK_MISS, MIPI_DPHY_CLK_MISS);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_TCLK_SETTLE, MIPI_DPHY_CLK_SETTLE);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_THS_EXIT, MIPI_DPHY_HS_EXIT);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_THS_SKIP, MIPI_DPHY_HS_SKIP);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_THS_SETTLE, settle);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_TINIT, MIPI_DPHY_INIT_CYCLES);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_TMBIAS, MIPI_DPHY_MBIAS_CYCLES);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_TULPS_C, MIPI_DPHY_ULPS_CHECK_CYCLES);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_TULPS_S, MIPI_DPHY_ULPS_START_CYCLES);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_TLP_EN_W, MIPI_DPHY_ULPS_STOP_CYCLES);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_TLPOK, MIPI_DPHY_POWER_UP_CYCLES);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_TWD_INIT, MIPI_DPHY_INIT_WATCH_DOG);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_TWD_HS, MIPI_DPHY_HS_WATCH_DOG);
> > +	c3_mipi_csi_write(csi, MIPI_PHY_DATA_LANE_CTRL, MIPI_DPHY_LANE_CTRL_DISABLE);
> > +
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_INSERT_ERRESC,
> > +				MIPI_DPHY_INSERT_ERRESC);
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_HS_SYNC_CHECK,
> > +				MIPI_DPHY_HS_SYNC_CHECK);
> > +	/*
> > +	 * Set 5 pipe lines to the same high speed.
> > +	 * Each bit for one pipe line.
> > +	 */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_FIVE_HS_PIPE,
> > +				0x1f << MIPI_DPHY_FIVE_HS_PIPE_SHIFT);
> > +
> > +	/* Output data with pipe line data. */
> > +	c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_DATA_PIPE_SELECT,
> > +				0x3 << MIPI_DPHY_DATA_PIPE_SELECT_SHIFT);
>
> Would it be possible to provide a definition for these 0x1f and 0x3
> values ?
>
> > +	if (lanes == 2)
> > +		c3_mipi_csi_2lanes_setting(csi);
> > +	else
> > +		c3_mipi_csi_4lanes_setting(csi);
> > +
> > +	/* Enable digital data and clock lanes */
> > +	c3_mipi_csi_write(csi, MIPI_PHY_CTRL, MIPI_DPHY_LANES_ENABLE);
> > +}
> > +
> > +static void c3_mipi_csi_cfg_host(struct csi_device *csi, u32 lanes)
> > +{
> > +	/* Reset CSI-2 controller output */
> > +	c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_DEFAULT);
> > +	c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_RST_VALUE);
> > +
> > +	/* Set data lane number */
> > +	c3_mipi_csi_write(csi, CSI2_HOST_N_LANES, lanes - 1);
> > +
> > +	/* Enable error mask */
> > +	c3_mipi_csi_write(csi, CSI2_HOST_MASK1, CSI2_HOST_ERROR_MASK1);
> > +}
> > +
> > +static int c3_mipi_csi_start_stream(struct csi_device *csi)
> > +{
> > +	s64 link_freq;
> > +	s64 lane_rate;
> > +
> > +	link_freq = v4l2_get_link_freq(csi->src_sd->ctrl_handler, 0, 0);
> > +	if (link_freq < 0) {
> > +		dev_err(csi->dev, "Unable to obtain link frequency: %lld\n", link_freq);
> > +		return link_freq;
> > +	}
> > +
> > +	lane_rate = link_freq * 2;
> > +	if (lane_rate > 1500000000)
>
> I would dev_err here too
>
> > +		return -EINVAL;
> > +
> > +	c3_mipi_csi_cfg_aphy(csi, csi->bus.num_data_lanes);
> > +	c3_mipi_csi_cfg_dphy(csi, csi->bus.num_data_lanes, lane_rate);
> > +	c3_mipi_csi_cfg_host(csi, csi->bus.num_data_lanes);
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_mipi_csi_enable_streams(struct v4l2_subdev *sd,
> > +				      struct v4l2_subdev_state *state,
> > +				      u32 pad, u64 streams_mask)
> > +{
> > +	struct csi_device *csi = v4l2_get_subdevdata(sd);
> > +	u64 sink_streams;
> > +	int ret;
> > +
> > +	guard(mutex)(&csi->lock);
> > +
> > +	pm_runtime_resume_and_get(csi->dev);
> > +
> > +	c3_mipi_csi_start_stream(csi);
> > +
> > +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > +						       MIPI_CSI2_PAD_SINK,
> > +						       &streams_mask);
> > +	ret = v4l2_subdev_enable_streams(csi->src_sd,
> > +					 csi->src_sd_pad,
> > +					 sink_streams);
> > +	if (ret) {
> > +		pm_runtime_put(csi->dev);
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_mipi_csi_disable_streams(struct v4l2_subdev *sd,
> > +				       struct v4l2_subdev_state *state,
> > +				       u32 pad, u64 streams_mask)
> > +{
> > +	struct csi_device *csi = v4l2_get_subdevdata(sd);
> > +	u64 sink_streams;
> > +	int ret;
> > +
> > +	guard(mutex)(&csi->lock);
> > +
> > +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > +						       MIPI_CSI2_PAD_SINK,
> > +						       &streams_mask);
> > +	ret = v4l2_subdev_disable_streams(csi->src_sd,
> > +					  csi->src_sd_pad,
> > +					  sink_streams);
> > +	if (ret)
> > +		dev_err(csi->dev, "Failed to disable %s\n", csi->src_sd->name);
> > +
> > +	pm_runtime_put(csi->dev);
> > +
> > +	return ret;
> > +}
> > +
> > +static int c3_mipi_csi_cfg_routing(struct v4l2_subdev *sd,
> > +				   struct v4l2_subdev_state *state,
> > +				   struct v4l2_subdev_krouting *routing)
> > +{
> > +	static const struct v4l2_mbus_framefmt format = {
> > +		.width = MIPI_CSI2_DEFAULT_WIDTH,
> > +		.height = MIPI_CSI2_DEFAULT_HEIGHT,
> > +		.code = MIPI_CSI2_DEFAULT_FMT,
> > +		.field = V4L2_FIELD_NONE,
> > +		.colorspace = V4L2_COLORSPACE_RAW,
> > +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> > +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
>
> I presume for Raw Bayer data the quantization range is full ?
>
> > +		.xfer_func = V4L2_XFER_FUNC_NONE,
> > +	};
> > +	int ret;
> > +
> > +	ret = v4l2_subdev_routing_validate(sd, routing,
> > +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> > +	if (ret)
> > +		return ret;
>
> You should validate that the provided routing table matches what the
> driver supports, so only [0/0]->[1/0]
>
> Now that I've said so, if the routing table is not modifiable I wonder
> if you should support set_routing() at all, or it could be left out
> until you don't add support for more streams to the driver.
>
> After all this driver implements support for routing but doesn't set
> the V4L2_SUBDEV_FL_STREAMS flag, so the operation is disallowed from
> userspace for now.
>
> > +
> > +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_mipi_csi_init_routing(struct v4l2_subdev *sd,
> > +				    struct v4l2_subdev_state *state)
> > +{
> > +	struct v4l2_subdev_route routes;
> > +	struct v4l2_subdev_krouting routing;
> > +
> > +	routes.sink_pad = MIPI_CSI2_PAD_SINK;
> > +	routes.sink_stream = 0;
> > +	routes.source_pad = MIPI_CSI2_PAD_SRC;
> > +	routes.source_stream = 0;
> > +	routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> > +
> > +	routing.num_routes = 1;
> > +	routing.routes = &routes;
> > +
> > +	return c3_mipi_csi_cfg_routing(sd, state, &routing);
> > +}
> > +
> > +static int c3_mipi_csi_set_routing(struct v4l2_subdev *sd,
> > +				   struct v4l2_subdev_state *state,
> > +				   enum v4l2_subdev_format_whence which,
> > +				   struct v4l2_subdev_krouting *routing)
> > +{
> > +	bool is_streaming = v4l2_subdev_is_streaming(sd);
> > +
> > +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> > +		return -EBUSY;
> > +
> > +	return c3_mipi_csi_cfg_routing(sd, state, routing);
> > +}
> > +
> > +static int c3_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
> > +				      struct v4l2_subdev_state *state,
> > +				      struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > +	switch (code->pad) {
> > +	case MIPI_CSI2_PAD_SINK:
> > +		if (code->index >= ARRAY_SIZE(c3_mipi_csi_formats))
> > +			return -EINVAL;
> > +
> > +		code->code = c3_mipi_csi_formats[code->index];
> > +		break;
> > +	case MIPI_CSI2_PAD_SRC:
> > +		struct v4l2_mbus_framefmt *fmt;
> > +
> > +		if (code->index > 0)
> > +			return -EINVAL;
> > +
> > +		fmt = v4l2_subdev_state_get_format(state, code->pad);
> > +		code->code = fmt->code;
> > +		break;
>
> I'm not sure if the V4L2 API specify that the formats on a pad should
> be enumerated in full, regardless of the configuration, or like you're
> doing here reflect the subdev configuration. I like what you have here
> more, so unless someone screams I think it's fine.
>
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_mipi_csi_set_fmt(struct v4l2_subdev *sd,
> > +			       struct v4l2_subdev_state *state,
> > +			       struct v4l2_subdev_format *format)
> > +{
> > +	struct v4l2_mbus_framefmt *fmt;
> > +	unsigned int i;
> > +
> > +	if (format->pad != MIPI_CSI2_PAD_SINK)
> > +		return v4l2_subdev_get_fmt(sd, state, format);
> > +
> > +	fmt = v4l2_subdev_state_get_format(state, format->pad);
>
> Could you clarify what other streams you plan to support ? As you
> support routing I presume you are preparing to capture
> multiple streams of data like image + embedded data, or to support
> sensors which sends data on different virtual channels ?
>
> How do you see this driver evolve ? Will it be augmented with an
> additional source pad directed to a video device where to capture
> embedded data from ?
>
> I'm wondering because if PAD_SINK become multiplexed, you won't be
> allowed to set a format there. It only works now because you have a
> single stream.

Pardon me, I've been made to notice the stream API allows userspace to
set a format on a [pad/stream] combination, so the above statement is
not correct: you will still be allowed to set a format a multiplexed
sink pad.

Knowing however how you plan to expand this to support multiple
streams is still something I would be interested in :)

Thanks
  j

>
> Speaking of which, as you prepare to support multiple streams, I would
> specify the stream number when calling v4l2_subdev_state_get_format().
>
> 	fmt = v4l2_subdev_state_get_format(state, format->pad, 0);
>
> > +
> > +	for (i = 0; i < ARRAY_SIZE(c3_mipi_csi_formats); i++)
> > +		if (format->format.code == c3_mipi_csi_formats[i])
> > +			break;
>
> nit: please use {} for the for loop
>
> > +
> > +	if (i == ARRAY_SIZE(c3_mipi_csi_formats))
> > +		fmt->code = c3_mipi_csi_formats[0];
> > +	else
> > +		fmt->code = c3_mipi_csi_formats[i];
>
> You could set this in the for loop, before breaking.
>
> > +
> > +	fmt->width = clamp_t(u32, format->format.width,
> > +			     MIPI_CSI2_MIN_WIDTH, MIPI_CSI2_MAX_WIDTH);
> > +	fmt->height = clamp_t(u32, format->format.height,
> > +			      MIPI_CSI2_MIN_HEIGHT, MIPI_CSI2_MAX_HEIGHT);
> > +
> You should set the colorspace related information too, as you
> initialize them, similar to what you do in init_state()
>
> > +	format->format = *fmt;
> > +
> > +	/* Synchronize the format to source pad */
> > +	fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
> > +	*fmt = format->format;
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_mipi_csi_init_state(struct v4l2_subdev *sd,
> > +				  struct v4l2_subdev_state *state)
> > +{
> > +	struct v4l2_mbus_framefmt *sink_fmt;
> > +	struct v4l2_mbus_framefmt *src_fmt;
> > +
> > +	sink_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SINK);
> > +	src_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
> > +
> > +	sink_fmt->width = MIPI_CSI2_DEFAULT_WIDTH;
> > +	sink_fmt->height = MIPI_CSI2_DEFAULT_HEIGHT;
> > +	sink_fmt->field = V4L2_FIELD_NONE;
> > +	sink_fmt->code = MIPI_CSI2_DEFAULT_FMT;
> > +	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> > +	sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
> > +	sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
> > +	sink_fmt->quantization =
> > +		V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
>
> If you could initialize them like you do above, with specific values
> instead of using _DEFAULT() I think it's better.
>
> > +					      sink_fmt->ycbcr_enc);
> > +	*src_fmt = *sink_fmt;
> > +
> > +	return c3_mipi_csi_init_routing(sd, state);
> > +}
> > +
> > +static const struct v4l2_subdev_pad_ops c3_mipi_csi_pad_ops = {
> > +	.enum_mbus_code = c3_mipi_csi_enum_mbus_code,
> > +	.get_fmt = v4l2_subdev_get_fmt,
> > +	.set_fmt = c3_mipi_csi_set_fmt,
> > +	.set_routing = c3_mipi_csi_set_routing,
> > +	.enable_streams = c3_mipi_csi_enable_streams,
> > +	.disable_streams = c3_mipi_csi_disable_streams,
> > +};
> > +
> > +static const struct v4l2_subdev_ops c3_mipi_csi_subdev_ops = {
> > +	.pad = &c3_mipi_csi_pad_ops,
> > +};
> > +
> > +static const struct v4l2_subdev_internal_ops c3_mipi_csi_internal_ops = {
> > +	.init_state = c3_mipi_csi_init_state,
> > +};
> > +
> > +/* Media entity operations */
> > +static const struct media_entity_operations c3_mipi_csi_entity_ops = {
> > +	.link_validate = v4l2_subdev_link_validate,
> > +};
> > +
> > +/* PM runtime */
> > +
> > +static int __maybe_unused c3_mipi_csi_runtime_suspend(struct device *dev)
> > +{
> > +	struct csi_device *csi = dev_get_drvdata(dev);
> > +
> > +	clk_bulk_disable_unprepare(csi->info->clock_num, csi->clks);
> > +
> > +	return 0;
> > +}
> > +
> > +static int __maybe_unused c3_mipi_csi_runtime_resume(struct device *dev)
> > +{
> > +	struct csi_device *csi = dev_get_drvdata(dev);
> > +
> > +	return clk_bulk_prepare_enable(csi->info->clock_num, csi->clks);
> > +}
> > +
> > +static const struct dev_pm_ops c3_mipi_csi_pm_ops = {
> > +	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> > +				pm_runtime_force_resume)
> > +	SET_RUNTIME_PM_OPS(c3_mipi_csi_runtime_suspend,
> > +			   c3_mipi_csi_runtime_resume, NULL)
>
> You could use SYSTEM_SLEEP_PM_OPS and RUNTIME_PM_OPS and set
>
> 		.pm = pm_ptr(&c3_mipi_csi_pm_ops),
>
> to avoid __maybe_unused in the functions, up to you
>
> > +};
> > +
> > +/* Probe/remove & platform driver */
> > +
> > +static int c3_mipi_csi_subdev_init(struct csi_device *csi)
> > +{
> > +	struct v4l2_subdev *sd = &csi->sd;
> > +	int ret;
> > +
> > +	v4l2_subdev_init(sd, &c3_mipi_csi_subdev_ops);
> > +	sd->owner = THIS_MODULE;
> > +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > +	sd->internal_ops = &c3_mipi_csi_internal_ops;
> > +	snprintf(sd->name, sizeof(sd->name), "%s", MIPI_CSI2_SUBDEV_NAME);
> > +
> > +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> > +	sd->entity.ops = &c3_mipi_csi_entity_ops;
> > +
> > +	sd->dev = csi->dev;
> > +	v4l2_set_subdevdata(sd, csi);
> > +
> > +	csi->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> > +	csi->pads[MIPI_CSI2_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
> > +	ret = media_entity_pads_init(&sd->entity, MIPI_CSI2_PAD_MAX, csi->pads);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = v4l2_subdev_init_finalize(sd);
> > +	if (ret) {
> > +		media_entity_cleanup(&sd->entity);
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static void c3_mipi_csi_subdev_deinit(struct csi_device *csi)
> > +{
> > +	v4l2_subdev_cleanup(&csi->sd);
> > +	media_entity_cleanup(&csi->sd.entity);
> > +}
> > +
> > +/* Subdev notifier register */
> > +static int c3_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier,
> > +				    struct v4l2_subdev *sd,
> > +				    struct v4l2_async_connection *asc)
> > +{
> > +	struct csi_device *csi = v4l2_get_subdevdata(notifier->sd);
> > +	struct media_pad *sink = &csi->sd.entity.pads[MIPI_CSI2_PAD_SINK];
> > +	int ret;
> > +
> > +	ret = media_entity_get_fwnode_pad(&sd->entity,
> > +					  sd->fwnode, MEDIA_PAD_FL_SOURCE);
> > +	if (ret < 0) {
> > +		dev_err(csi->dev, "Failed to find pad for %s\n", sd->name);
> > +		return ret;
> > +	}
> > +
> > +	csi->src_sd = sd;
> > +	csi->src_sd_pad = ret;
> > +
> > +	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
> > +					       MEDIA_LNK_FL_IMMUTABLE);
> > +}
> > +
> > +static const struct v4l2_async_notifier_operations c3_mipi_csi_notify_ops = {
> > +	.bound = c3_mipi_csi_notify_bound,
> > +};
> > +
> > +static int c3_mipi_csi_async_register(struct csi_device *csi)
> > +{
> > +	struct v4l2_fwnode_endpoint vep = {
> > +		.bus_type = V4L2_MBUS_CSI2_DPHY,
> > +	};
> > +	struct v4l2_async_connection *asc;
> > +	struct fwnode_handle *ep;
> > +	int ret;
> > +
> > +	v4l2_async_subdev_nf_init(&csi->notifier, &csi->sd);
> > +
> > +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0,
> > +					     FWNODE_GRAPH_ENDPOINT_NEXT);
> > +	if (!ep)
> > +		return -ENOTCONN;
> > +
> > +	ret = v4l2_fwnode_endpoint_parse(ep, &vep);
> > +	if (ret)
> > +		goto err_put_handle;
> > +
> > +	csi->bus = vep.bus.mipi_csi2;
> > +	if (csi->bus.num_data_lanes != 2 && csi->bus.num_data_lanes != 4)
> > +		goto err_put_handle;
>
> I would dev_err() here
>
> Thanks
>    j
>
> > +
> > +	asc = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep,
> > +					      struct v4l2_async_connection);
> > +	if (IS_ERR(asc)) {
> > +		ret = PTR_ERR(asc);
> > +		goto err_put_handle;
> > +	}
> > +
> > +	csi->notifier.ops = &c3_mipi_csi_notify_ops;
> > +	ret = v4l2_async_nf_register(&csi->notifier);
> > +	if (ret)
> > +		goto err_cleanup_nf;
> > +
> > +	ret = v4l2_async_register_subdev(&csi->sd);
> > +	if (ret)
> > +		goto err_unregister_nf;
> > +
> > +	fwnode_handle_put(ep);
> > +
> > +	return 0;
> > +
> > +err_unregister_nf:
> > +	v4l2_async_nf_unregister(&csi->notifier);
> > +err_cleanup_nf:
> > +	v4l2_async_nf_cleanup(&csi->notifier);
> > +err_put_handle:
> > +	fwnode_handle_put(ep);
> > +	return ret;
> > +}
> > +
> > +static void c3_mipi_csi_async_unregister(struct csi_device *csi)
> > +{
> > +	v4l2_async_unregister_subdev(&csi->sd);
> > +	v4l2_async_nf_unregister(&csi->notifier);
> > +	v4l2_async_nf_cleanup(&csi->notifier);
> > +}
> > +
> > +static int c3_mipi_csi_ioremap_resource(struct csi_device *csi)
> > +{
> > +	struct device *dev = csi->dev;
> > +	struct platform_device *pdev = to_platform_device(dev);
> > +
> > +	csi->aphy = devm_platform_ioremap_resource_byname(pdev, "aphy");
> > +	if (IS_ERR(csi->aphy))
> > +		return PTR_ERR(csi->aphy);
> > +
> > +	csi->dphy = devm_platform_ioremap_resource_byname(pdev, "dphy");
> > +	if (IS_ERR(csi->dphy))
> > +		return PTR_ERR(csi->dphy);
> > +
> > +	csi->host = devm_platform_ioremap_resource_byname(pdev, "host");
> > +	if (IS_ERR(csi->host))
> > +		return PTR_ERR(csi->host);
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_mipi_csi_configure_clocks(struct csi_device *csi)
> > +{
> > +	const struct csi_info *info = csi->info;
> > +	int ret;
> > +	u32 i;
> > +
> > +	for (i = 0; i < info->clock_num; i++)
> > +		csi->clks[i].id = info->clocks[i];
> > +
> > +	ret = devm_clk_bulk_get(csi->dev, info->clock_num, csi->clks);
> > +	if (ret)
> > +		return ret;
> > +
> > +	for (i = 0; i < info->clock_num; i++) {
> > +		if (!info->clock_rates[i])
> > +			continue;
> > +		ret = clk_set_rate(csi->clks[i].clk, info->clock_rates[i]);
> > +		if (ret) {
> > +			dev_err(csi->dev, "Failed to set %s rate %u\n", info->clocks[i],
> > +				info->clock_rates[i]);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_mipi_csi_probe(struct platform_device *pdev)
> > +{
> > +	struct device *dev = &pdev->dev;
> > +	struct csi_device *csi;
> > +	int ret;
> > +
> > +	csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
> > +	if (!csi)
> > +		return -ENOMEM;
> > +
> > +	csi->info = of_device_get_match_data(dev);
> > +	csi->dev = dev;
> > +
> > +	ret = c3_mipi_csi_ioremap_resource(csi);
> > +	if (ret)
> > +		return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
> > +
> > +	ret = c3_mipi_csi_configure_clocks(csi);
> > +	if (ret)
> > +		return dev_err_probe(dev, ret, "Failed to configure clocks\n");
> > +
> > +	platform_set_drvdata(pdev, csi);
> > +
> > +	mutex_init(&csi->lock);
> > +	pm_runtime_enable(dev);
> > +
> > +	ret = c3_mipi_csi_subdev_init(csi);
> > +	if (ret)
> > +		goto err_disable_runtime_pm;
> > +
> > +	ret = c3_mipi_csi_async_register(csi);
> > +	if (ret)
> > +		goto err_deinit_subdev;
> > +
> > +	return 0;
> > +
> > +err_deinit_subdev:
> > +	c3_mipi_csi_subdev_deinit(csi);
> > +err_disable_runtime_pm:
> > +	pm_runtime_disable(dev);
> > +	mutex_destroy(&csi->lock);
> > +	return ret;
> > +};
> > +
> > +static void c3_mipi_csi_remove(struct platform_device *pdev)
> > +{
> > +	struct csi_device *csi = platform_get_drvdata(pdev);
> > +
> > +	c3_mipi_csi_async_unregister(csi);
> > +	c3_mipi_csi_subdev_deinit(csi);
> > +
> > +	pm_runtime_disable(&pdev->dev);
> > +	mutex_destroy(&csi->lock);
> > +};
> > +
> > +static const struct csi_info c3_mipi_csi_info = {
> > +	.clocks = {"vapb", "phy0"},
> > +	.clock_rates = {0, 200000000},
> > +	.clock_num = 2
> > +};
> > +
> > +static const struct of_device_id c3_mipi_csi_of_match[] = {
> > +	{ .compatible = "amlogic,c3-mipi-csi2",
> > +	  .data = &c3_mipi_csi_info,
> > +	},
> > +	{ },
> > +};
> > +MODULE_DEVICE_TABLE(of, c3_mipi_csi_of_match);
> > +
> > +static struct platform_driver c3_mipi_csi_driver = {
> > +	.probe = c3_mipi_csi_probe,
> > +	.remove = c3_mipi_csi_remove,
> > +	.driver = {
> > +		.name = "c3-mipi-csi2",
> > +		.of_match_table = c3_mipi_csi_of_match,
> > +		.pm = &c3_mipi_csi_pm_ops,
> > +	},
> > +};
> > +
> > +module_platform_driver(c3_mipi_csi_driver);
> > +
> > +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
> > +MODULE_DESCRIPTION("Amlogic C3 MIPI CSI-2 receiver");
> > +MODULE_LICENSE("GPL");
> >
> > --
> > 2.46.1
> >
> >
> >

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

* Re: [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver
  2024-11-04 17:50   ` Jacopo Mondi
  2024-11-05 10:06     ` Jacopo Mondi
@ 2024-11-05 11:21     ` Keke Li
  2024-11-05 11:39       ` Jacopo Mondi
  1 sibling, 1 reply; 37+ messages in thread
From: Keke Li @ 2024-11-05 11:21 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally,
	tomi.valkeinen@ideasonboard.com

Hi Jacopo

Thanks very much for your reply.

On 2024/11/5 01:50, Jacopo Mondi wrote:
> [You don't often get email from jacopo.mondi@ideasonboard.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>
> [ EXTERNAL EMAIL ]
>
> Hi Keke
>     sorry for the late feedback, hope you're still interested in
> upstreaming this driver
>
> On Wed, Sep 18, 2024 at 02:07:13PM +0800, Keke Li via B4 Relay wrote:
>> From: Keke Li <keke.li@amlogic.com>
>>
>> This driver is used to receive mipi data from image sensor.
>>
>> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
>> Signed-off-by: Keke Li <keke.li@amlogic.com>
>> ---
>>   MAINTAINERS                                        |   7 +
>>   drivers/media/platform/amlogic/Kconfig             |   1 +
>>   drivers/media/platform/amlogic/Makefile            |   2 +
>>   .../media/platform/amlogic/c3-mipi-csi2/Kconfig    |  16 +
>>   .../media/platform/amlogic/c3-mipi-csi2/Makefile   |   3 +
>>   .../platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c   | 910 +++++++++++++++++++++
>>   6 files changed, 939 insertions(+)
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 2cdd7cacec86..9e75874a6e69 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -1209,6 +1209,13 @@ F:     Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
>>   F:   drivers/perf/amlogic/
>>   F:   include/soc/amlogic/
>>
>> +AMLOGIC MIPI CSI2 DRIVER
>> +M:   Keke Li <keke.li@amlogic.com>
>> +L:   linux-media@vger.kernel.org
>> +S:   Maintained
>> +F:   Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml
>> +F:   drivers/media/platform/amlogic/c3-mipi-csi2/
>> +
>>   AMPHENOL CHIPCAP 2 HUMIDITY-TEMPERATURE IIO DRIVER
>>   M:   Javier Carrasco <javier.carrasco.cruz@gmail.com>
>>   L:   linux-hwmon@vger.kernel.org
>> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
>> index 5014957404e9..b7c2de14848b 100644
>> --- a/drivers/media/platform/amlogic/Kconfig
>> +++ b/drivers/media/platform/amlogic/Kconfig
>> @@ -2,4 +2,5 @@
>>
>>   comment "Amlogic media platform drivers"
>>
>> +source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>>   source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
>> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
>> index d3cdb8fa4ddb..4f571ce5d13e 100644
>> --- a/drivers/media/platform/amlogic/Makefile
>> +++ b/drivers/media/platform/amlogic/Makefile
>> @@ -1,2 +1,4 @@
>>   # SPDX-License-Identifier: GPL-2.0-only
>> +
>> +obj-y += c3-mipi-csi2/
>>   obj-y += meson-ge2d/
>> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
>> new file mode 100644
>> index 000000000000..0d7b2e203273
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
>> @@ -0,0 +1,16 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +config VIDEO_C3_MIPI_CSI2
>> +     tristate "Amlogic C3 MIPI CSI-2 receiver"
>> +     depends on ARCH_MESON || COMPILE_TEST
>> +     depends on VIDEO_DEV
>> +     depends on OF
>> +     select MEDIA_CONTROLLER
>> +     select V4L2_FWNODE
>> +     select VIDEO_V4L2_SUBDEV_API
>> +     help
>> +       Video4Linux2 driver for Amlogic C3 MIPI CSI-2 receiver.
>> +       C3 MIPI CSI-2 receiver is used to receive MIPI data from
>> +       image sensor.
>> +
>> +       To compile this driver as a module choose m here.
>> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
>> new file mode 100644
>> index 000000000000..cc08fc722bfd
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
>> @@ -0,0 +1,3 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +obj-$(CONFIG_VIDEO_C3_MIPI_CSI2) += c3-mipi-csi2.o
>> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
>> new file mode 100644
>> index 000000000000..6ac60d5b26a8
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
>> @@ -0,0 +1,910 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/cleanup.h>
>> +#include <linux/clk.h>
>> +#include <linux/device.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-async.h>
>> +#include <media/v4l2-common.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-fwnode.h>
>> +#include <media/v4l2-mc.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +/* C3 CSI-2 submodule definition */
>> +enum {
>> +     SUBMD_APHY,
>> +     SUBMD_DPHY,
>> +     SUBMD_HOST,
>> +};
>> +
>> +#define CSI2_SUBMD_MASK             GENMASK(17, 16)
>> +#define CSI2_SUBMD_SHIFT            16
>> +#define CSI2_SUBMD(x)               (((x) & (CSI2_SUBMD_MASK)) >> (CSI2_SUBMD_SHIFT))
>> +#define CSI2_REG_ADDR_MASK          GENMASK(15, 0)
>> +#define CSI2_REG_ADDR(x)            ((x) & (CSI2_REG_ADDR_MASK))
>> +#define CSI2_REG_A(x)               ((SUBMD_APHY << CSI2_SUBMD_SHIFT) | (x))
>> +#define CSI2_REG_D(x)               ((SUBMD_DPHY << CSI2_SUBMD_SHIFT) | (x))
>> +#define CSI2_REG_H(x)               ((SUBMD_HOST << CSI2_SUBMD_SHIFT) | (x))
>> +
>> +#define MIPI_CSI2_CLOCK_NUM_MAX     3
>> +#define MIPI_CSI2_SUBDEV_NAME       "mipi-csi2"
> Isn't the name too generic ? Should it report at least "c3-mipi-csi2"
> ?
Will modify the name with  "C3_MIPI_CSI2".
>> +
>> +/* C3 CSI-2 APHY register */
>> +#define MIPI_CSI_2M_PHY2_CNTL1      CSI2_REG_A(0x44)
>> +#define MIPI_APHY_NORMAL_CNTL1      0x3f425C00
> All other hex addresses use small capitals for letters
OK, will use small capitals for letters.
>> +
>> +#define MIPI_CSI_2M_PHY2_CNTL2      CSI2_REG_A(0x48)
>> +#define MIPI_APHY_4LANES_CNTL2      0x033a0000
>> +#define MIPI_APHY_NORMAL_CNTL2      0x333a0000
>> +
>> +#define MIPI_CSI_2M_PHY2_CNTL3      CSI2_REG_A(0x4c)
>> +#define MIPI_APHY_2LANES_CNTL3      0x03800000
>> +
>> +/* C3 CSI-2 DPHY register */
>> +#define MIPI_PHY_CTRL                    CSI2_REG_D(0x00)
>> +#define MIPI_DPHY_LANES_ENABLE      0x0
>> +
>> +#define MIPI_PHY_CLK_LANE_CTRL           CSI2_REG_D(0x04)
>> +#define MIPI_DPHY_CLK_CONTINUE_MODE 0x3d8
>> +
>> +#define MIPI_PHY_DATA_LANE_CTRL     CSI2_REG_D(0x08)
>> +#define MIPI_DPHY_LANE_CTRL_DISABLE 0x0
>> +
>> +#define MIPI_PHY_DATA_LANE_CTRL1    CSI2_REG_D(0x0c)
>> +#define MIPI_DPHY_INSERT_ERRESC     BIT(0)
>> +#define MIPI_DPHY_HS_SYNC_CHECK     BIT(1)
>> +#define MIPI_DPHY_FIVE_HS_PIPE      GENMASK(6, 2)
>> +#define MIPI_DPHY_FIVE_HS_PIPE_SHIFT           2
>> +#define MIPI_DPHY_DATA_PIPE_SELECT  GENMASK(9, 7)
>> +#define MIPI_DPHY_DATA_PIPE_SELECT_SHIFT       7
>> +
>> +#define MIPI_PHY_TCLK_MISS       CSI2_REG_D(0x10)
>> +#define MIPI_DPHY_CLK_MISS          0x9
>> +
>> +#define MIPI_PHY_TCLK_SETTLE     CSI2_REG_D(0x14)
>> +#define MIPI_DPHY_CLK_SETTLE        0x1F
>> +
>> +#define MIPI_PHY_THS_EXIT        CSI2_REG_D(0x18)
>> +#define MIPI_DPHY_HS_EXIT           0x8
>> +
>> +#define MIPI_PHY_THS_SKIP        CSI2_REG_D(0x1c)
>> +#define MIPI_DPHY_HS_SKIP           0xa
>> +
>> +#define MIPI_PHY_THS_SETTLE      CSI2_REG_D(0x20)
>> +#define MIPI_PHY_TINIT                   CSI2_REG_D(0x24)
>> +#define MIPI_DPHY_INIT_CYCLES       0x4e20
>> +
>> +#define MIPI_PHY_TULPS_C         CSI2_REG_D(0x28)
>> +#define MIPI_DPHY_ULPS_CHECK_CYCLES 0x1000
>> +
>> +#define MIPI_PHY_TULPS_S         CSI2_REG_D(0x2c)
>> +#define MIPI_DPHY_ULPS_START_CYCLES 0x100
>> +
>> +#define MIPI_PHY_TMBIAS             CSI2_REG_D(0x30)
>> +#define MIPI_DPHY_MBIAS_CYCLES      0x100
>> +
>> +#define MIPI_PHY_TLP_EN_W           CSI2_REG_D(0x34)
>> +#define MIPI_DPHY_ULPS_STOP_CYCLES  0xC
>> +
>> +#define MIPI_PHY_TLPOK                   CSI2_REG_D(0x38)
>> +#define MIPI_DPHY_POWER_UP_CYCLES   0x100
>> +
>> +#define MIPI_PHY_TWD_INIT        CSI2_REG_D(0x3c)
>> +#define MIPI_DPHY_INIT_WATCH_DOG    0x400000
>> +
>> +#define MIPI_PHY_TWD_HS             CSI2_REG_D(0x40)
>> +#define MIPI_DPHY_HS_WATCH_DOG      0x400000
>> +
>> +#define MIPI_PHY_MUX_CTRL0       CSI2_REG_D(0x284)
>> +#define MIPI_DPHY_LANE3_SELECT      GENMASK(3, 0)
>> +#define MIPI_DPHY_LANE2_SELECT      GENMASK(7, 4)
>> +#define MIPI_DPHY_LANE2_SELECT_SHIFT           4
>> +#define MIPI_DPHY_LANE1_SELECT      GENMASK(11, 8)
>> +#define MIPI_DPHY_LANE1_SELECT_SHIFT            8
>> +#define MIPI_DPHY_LANE0_SELECT      GENMASK(14, 12)
>> +
>> +#define MIPI_PHY_MUX_CTRL1       CSI2_REG_D(0x288)
>> +#define MIPI_DPHY_LANE3_CTRL_SIGNAL GENMASK(3, 0)
>> +#define MIPI_DPHY_LANE2_CTRL_SIGNAL GENMASK(7, 4)
>> +#define MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT      4
>> +#define MIPI_DPHY_LANE1_CTRL_SIGNAL GENMASK(11, 8)
>> +#define MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT       8
>> +#define MIPI_DPHY_LANE0_CTRL_SIGNAL GENMASK(14, 12)
>> +#define MIPI_DPHY_CLK_SELECT        BIT(17)
>> +
>> +/* C3 CSI-2 HOST register */
>> +#define CSI2_HOST_N_LANES           CSI2_REG_H(0x04)
>> +#define CSI2_HOST_CSI2_RESETN       CSI2_REG_H(0x10)
>> +#define CSI2_HOST_RESETN_DEFAULT    0x0
>> +#define CSI2_HOST_RESETN_RST_VALUE  BIT(0)
>> +
>> +#define CSI2_HOST_MASK1             CSI2_REG_H(0x28)
>> +#define CSI2_HOST_ERROR_MASK1       GENMASK(28, 0)
>> +
>> +#define MIPI_CSI2_MAX_WIDTH         2888
>> +#define MIPI_CSI2_MIN_WIDTH         160
>> +#define MIPI_CSI2_MAX_HEIGHT        2240
>> +#define MIPI_CSI2_MIN_HEIGHT        120
>> +#define MIPI_CSI2_DEFAULT_WIDTH     1920
>> +#define MIPI_CSI2_DEFAULT_HEIGHT    1080
>> +#define MIPI_CSI2_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
>> +
>> +/* C3 CSI-2 pad list */
>> +enum {
>> +     MIPI_CSI2_PAD_SINK,
>> +     MIPI_CSI2_PAD_SRC,
>> +     MIPI_CSI2_PAD_MAX
>> +};
>> +
>> +/**
> You don't need to kernel-doc in-driver types and functions.
> Documentation is always good, but this won't be parsed by kernel-doc
> (afaiu) so you should drop one * from /**
>
Will drop one * from /**
>> + * struct csi_info - MIPI CSI2 information
>> + *
>> + * @clocks: array of MIPI CSI2 clock names
>> + * @clock_rates: array of MIPI CSI2 clock rate
>> + * @clock_num: actual clock number
>> + */
>> +struct csi_info {
>> +     char *clocks[MIPI_CSI2_CLOCK_NUM_MAX];
>> +     u32 clock_rates[MIPI_CSI2_CLOCK_NUM_MAX];
>> +     u32 clock_num;
>> +};
>> +
>> +/**
>> + * struct csi_device - MIPI CSI2 platform device
>> + *
>> + * @dev: pointer to the struct device
>> + * @aphy: MIPI CSI2 aphy register address
>> + * @dphy: MIPI CSI2 dphy register address
>> + * @host: MIPI CSI2 host register address
>> + * @clks: array of MIPI CSI2 clocks
>> + * @sd: MIPI CSI2 sub-device
>> + * @pads: MIPI CSI2 sub-device pads
>> + * @notifier: notifier to register on the v4l2-async API
>> + * @src_sd: source sub-device
>> + * @bus: MIPI CSI2 bus information
>> + * @src_sd_pad: source sub-device pad
>> + * @lock: protect MIPI CSI2 device
>> + * @info: version-specific MIPI CSI2 information
>> + */
>> +struct csi_device {
>> +     struct device *dev;
>> +     void __iomem *aphy;
>> +     void __iomem *dphy;
>> +     void __iomem *host;
>> +     struct clk_bulk_data clks[MIPI_CSI2_CLOCK_NUM_MAX];
>> +
>> +     struct v4l2_subdev sd;
>> +     struct media_pad pads[MIPI_CSI2_PAD_MAX];
>> +     struct v4l2_async_notifier notifier;
>> +     struct v4l2_subdev *src_sd;
>> +     struct v4l2_mbus_config_mipi_csi2 bus;
>> +
>> +     u16 src_sd_pad;
>> +     struct mutex lock; /* Protect csi device */
> All the operations which receive a subdev_state are guaranteed to be locked
> so you can avoid manually locking in enable/disable streams
> (and drop #include cleanup.h if you don't use guards in any other
> place)
>
OK, will remove this lock and drop "#include cleanup.h"
>> +     const struct csi_info *info;
>> +};
>> +
>> +static const u32 c3_mipi_csi_formats[] = {
>> +     MEDIA_BUS_FMT_SBGGR10_1X10,
>> +     MEDIA_BUS_FMT_SGBRG10_1X10,
>> +     MEDIA_BUS_FMT_SGRBG10_1X10,
>> +     MEDIA_BUS_FMT_SRGGB10_1X10,
>> +     MEDIA_BUS_FMT_SBGGR12_1X12,
>> +     MEDIA_BUS_FMT_SGBRG12_1X12,
>> +     MEDIA_BUS_FMT_SGRBG12_1X12,
>> +     MEDIA_BUS_FMT_SRGGB12_1X12,
>> +};
>> +
>> +/* Hardware configuration */
>> +
>> +static void c3_mipi_csi_write(struct csi_device *csi, u32 reg, u32 val)
>> +{
>> +     void __iomem *addr;
>> +
>> +     switch (CSI2_SUBMD(reg)) {
>> +     case SUBMD_APHY:
>> +             addr = csi->aphy + CSI2_REG_ADDR(reg);
>> +             break;
>> +     case SUBMD_DPHY:
>> +             addr = csi->dphy + CSI2_REG_ADDR(reg);
>> +             break;
>> +     case SUBMD_HOST:
>> +             addr = csi->host + CSI2_REG_ADDR(reg);
>> +             break;
>> +     default:
>> +             dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
>> +             return;
>> +     }
>> +
>> +     writel(val, addr);
>> +}
>> +
>> +static void c3_mipi_csi_update_bits(struct csi_device *csi, u32 reg,
>> +                                 u32 mask, u32 val)
>> +{
>> +     void __iomem *addr;
>> +     u32 orig, tmp;
>> +
>> +     switch (CSI2_SUBMD(reg)) {
>> +     case SUBMD_APHY:
>> +             addr = csi->aphy + CSI2_REG_ADDR(reg);
>> +             break;
>> +     case SUBMD_DPHY:
>> +             addr = csi->dphy + CSI2_REG_ADDR(reg);
>> +             break;
>> +     case SUBMD_HOST:
>> +             addr = csi->host + CSI2_REG_ADDR(reg);
>> +             break;
>> +     default:
>> +             dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
>> +             return;
>> +     }
> This is repeated in two functions and could be grouped to a common
> place. Up to you
Will use a common function to replace this part.
>> +
>> +     orig = readl(addr);
>> +     tmp = orig & ~mask;
>> +     tmp |= val & mask;
>> +
>> +     if (tmp != orig)
>> +             writel(tmp, addr);
>> +}
>> +
>> +static void c3_mipi_csi_cfg_aphy(struct csi_device *csi, u32 lanes)
>> +{
>> +     c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL1, MIPI_APHY_NORMAL_CNTL1);
>> +
>> +     if (lanes == 4)
>> +             c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_4LANES_CNTL2);
>> +     else
>> +             c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_NORMAL_CNTL2);
>> +
>> +     if (lanes == 2)
>> +             c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL3, MIPI_APHY_2LANES_CNTL3);
> The driver seems to only accept 2 or 4 lanes. What is
> MIPI_APHY_NORMAL_CNTL2 for ?

Will modify MIPI_APHY_NORMAL_CNTL2 to MIPI_APHY_2LANES_CNTL2.

MIPI_APHY_2LANES_CNTL2 can indicate this 2 lanes setting.

>> +}
>> +
>> +static void c3_mipi_csi_2lanes_setting(struct csi_device *csi)
>> +{
>> +     /* Disable lane 2 and lane 3 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0xf);
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
>> +                             0xf << MIPI_DPHY_LANE2_SELECT_SHIFT);
>> +     /* Select analog data lane 1 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
>> +                             0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
>> +     /* Select analog data lane 0 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
>> +
>> +     /* Disable lane 2 and lane 3 control signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0xf);
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
>> +                             0xf << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
>> +     /* Select lane 1 signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
>> +                             0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
>> +     /* Select lane 0 signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
>> +     /* Select input 0 as clock */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
>> +                             MIPI_DPHY_CLK_SELECT);
>> +}
>> +
>> +static void c3_mipi_csi_4lanes_setting(struct csi_device *csi)
>> +{
>> +     /* Select analog data lane 3 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0x3);
>> +     /* Select analog data lane 2 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
>> +                             0x2 << MIPI_DPHY_LANE2_SELECT_SHIFT);
>> +     /* Select analog data lane 1 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
>> +                             0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
>> +     /* Select analog data lane 0 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
>> +
>> +     /* Select lane 3 signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0x3);
>> +     /* Select lane 2 signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
>> +                             0x2 << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
>> +     /* Select lane 1 signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
>> +                             0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
>> +     /* Select lane 0 signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
>> +     /* Select input 0 as clock */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
>> +                             MIPI_DPHY_CLK_SELECT);
>> +}
>> +
>> +static void c3_mipi_csi_cfg_dphy(struct csi_device *csi, u32 lanes, s64 rate)
>> +{
>> +     u32 val;
>> +     u32 settle;
>> +
>> +     /* Calculate the high speed settle */
>> +     val = DIV_ROUND_UP(1000000000, rate);
>> +     settle = (16 * val + 230) / 10;
>> +
>> +     c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL, MIPI_DPHY_CLK_CONTINUE_MODE);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TCLK_MISS, MIPI_DPHY_CLK_MISS);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TCLK_SETTLE, MIPI_DPHY_CLK_SETTLE);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_THS_EXIT, MIPI_DPHY_HS_EXIT);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_THS_SKIP, MIPI_DPHY_HS_SKIP);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_THS_SETTLE, settle);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TINIT, MIPI_DPHY_INIT_CYCLES);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TMBIAS, MIPI_DPHY_MBIAS_CYCLES);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TULPS_C, MIPI_DPHY_ULPS_CHECK_CYCLES);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TULPS_S, MIPI_DPHY_ULPS_START_CYCLES);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TLP_EN_W, MIPI_DPHY_ULPS_STOP_CYCLES);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TLPOK, MIPI_DPHY_POWER_UP_CYCLES);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TWD_INIT, MIPI_DPHY_INIT_WATCH_DOG);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TWD_HS, MIPI_DPHY_HS_WATCH_DOG);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_DATA_LANE_CTRL, MIPI_DPHY_LANE_CTRL_DISABLE);
>> +
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_INSERT_ERRESC,
>> +                             MIPI_DPHY_INSERT_ERRESC);
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_HS_SYNC_CHECK,
>> +                             MIPI_DPHY_HS_SYNC_CHECK);
>> +     /*
>> +      * Set 5 pipe lines to the same high speed.
>> +      * Each bit for one pipe line.
>> +      */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_FIVE_HS_PIPE,
>> +                             0x1f << MIPI_DPHY_FIVE_HS_PIPE_SHIFT);
>> +
>> +     /* Output data with pipe line data. */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_DATA_PIPE_SELECT,
>> +                             0x3 << MIPI_DPHY_DATA_PIPE_SELECT_SHIFT);
> Would it be possible to provide a definition for these 0x1f and 0x3
> values ?
OK, will provide two macros to represent  0x1f and 0x3.
>> +     if (lanes == 2)
>> +             c3_mipi_csi_2lanes_setting(csi);
>> +     else
>> +             c3_mipi_csi_4lanes_setting(csi);
>> +
>> +     /* Enable digital data and clock lanes */
>> +     c3_mipi_csi_write(csi, MIPI_PHY_CTRL, MIPI_DPHY_LANES_ENABLE);
>> +}
>> +
>> +static void c3_mipi_csi_cfg_host(struct csi_device *csi, u32 lanes)
>> +{
>> +     /* Reset CSI-2 controller output */
>> +     c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_DEFAULT);
>> +     c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_RST_VALUE);
>> +
>> +     /* Set data lane number */
>> +     c3_mipi_csi_write(csi, CSI2_HOST_N_LANES, lanes - 1);
>> +
>> +     /* Enable error mask */
>> +     c3_mipi_csi_write(csi, CSI2_HOST_MASK1, CSI2_HOST_ERROR_MASK1);
>> +}
>> +
>> +static int c3_mipi_csi_start_stream(struct csi_device *csi)
>> +{
>> +     s64 link_freq;
>> +     s64 lane_rate;
>> +
>> +     link_freq = v4l2_get_link_freq(csi->src_sd->ctrl_handler, 0, 0);
>> +     if (link_freq < 0) {
>> +             dev_err(csi->dev, "Unable to obtain link frequency: %lld\n", link_freq);
>> +             return link_freq;
>> +     }
>> +
>> +     lane_rate = link_freq * 2;
>> +     if (lane_rate > 1500000000)
> I would dev_err here too
Will add dev_err.
>> +             return -EINVAL;
>> +
>> +     c3_mipi_csi_cfg_aphy(csi, csi->bus.num_data_lanes);
>> +     c3_mipi_csi_cfg_dphy(csi, csi->bus.num_data_lanes, lane_rate);
>> +     c3_mipi_csi_cfg_host(csi, csi->bus.num_data_lanes);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_enable_streams(struct v4l2_subdev *sd,
>> +                                   struct v4l2_subdev_state *state,
>> +                                   u32 pad, u64 streams_mask)
>> +{
>> +     struct csi_device *csi = v4l2_get_subdevdata(sd);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     guard(mutex)(&csi->lock);
>> +
>> +     pm_runtime_resume_and_get(csi->dev);
>> +
>> +     c3_mipi_csi_start_stream(csi);
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +                                                    MIPI_CSI2_PAD_SINK,
>> +                                                    &streams_mask);
>> +     ret = v4l2_subdev_enable_streams(csi->src_sd,
>> +                                      csi->src_sd_pad,
>> +                                      sink_streams);
>> +     if (ret) {
>> +             pm_runtime_put(csi->dev);
>> +             return ret;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_disable_streams(struct v4l2_subdev *sd,
>> +                                    struct v4l2_subdev_state *state,
>> +                                    u32 pad, u64 streams_mask)
>> +{
>> +     struct csi_device *csi = v4l2_get_subdevdata(sd);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     guard(mutex)(&csi->lock);
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +                                                    MIPI_CSI2_PAD_SINK,
>> +                                                    &streams_mask);
>> +     ret = v4l2_subdev_disable_streams(csi->src_sd,
>> +                                       csi->src_sd_pad,
>> +                                       sink_streams);
>> +     if (ret)
>> +             dev_err(csi->dev, "Failed to disable %s\n", csi->src_sd->name);
>> +
>> +     pm_runtime_put(csi->dev);
>> +
>> +     return ret;
>> +}
>> +
>> +static int c3_mipi_csi_cfg_routing(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state,
>> +                                struct v4l2_subdev_krouting *routing)
>> +{
>> +     static const struct v4l2_mbus_framefmt format = {
>> +             .width = MIPI_CSI2_DEFAULT_WIDTH,
>> +             .height = MIPI_CSI2_DEFAULT_HEIGHT,
>> +             .code = MIPI_CSI2_DEFAULT_FMT,
>> +             .field = V4L2_FIELD_NONE,
>> +             .colorspace = V4L2_COLORSPACE_RAW,
>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
> I presume for Raw Bayer data the quantization range is full ?
OK, will modify V4L2_QUANTIZATION_LIM_RANGE to 
V4L2_QUANTIZATION_FULL_RANGE.
>> +             .xfer_func = V4L2_XFER_FUNC_NONE,
>> +     };
>> +     int ret;
>> +
>> +     ret = v4l2_subdev_routing_validate(sd, routing,
>> +                                        V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>> +     if (ret)
>> +             return ret;
> You should validate that the provided routing table matches what the
> driver supports, so only [0/0]->[1/0]
>
> Now that I've said so, if the routing table is not modifiable I wonder
> if you should support set_routing() at all, or it could be left out
> until you don't add support for more streams to the driver.
>
> After all this driver implements support for routing but doesn't set
> the V4L2_SUBDEV_FL_STREAMS flag, so the operation is disallowed from
> userspace for now.

Will remove set_routing().

Now the driver dosen't require  routing configuration.

>> +
>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_init_routing(struct v4l2_subdev *sd,
>> +                                 struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_subdev_route routes;
>> +     struct v4l2_subdev_krouting routing;
>> +
>> +     routes.sink_pad = MIPI_CSI2_PAD_SINK;
>> +     routes.sink_stream = 0;
>> +     routes.source_pad = MIPI_CSI2_PAD_SRC;
>> +     routes.source_stream = 0;
>> +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>> +
>> +     routing.num_routes = 1;
>> +     routing.routes = &routes;
>> +
>> +     return c3_mipi_csi_cfg_routing(sd, state, &routing);
>> +}
>> +
>> +static int c3_mipi_csi_set_routing(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state,
>> +                                enum v4l2_subdev_format_whence which,
>> +                                struct v4l2_subdev_krouting *routing)
>> +{
>> +     bool is_streaming = v4l2_subdev_is_streaming(sd);
>> +
>> +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>> +             return -EBUSY;
>> +
>> +     return c3_mipi_csi_cfg_routing(sd, state, routing);
>> +}
>> +
>> +static int c3_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
>> +                                   struct v4l2_subdev_state *state,
>> +                                   struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +     switch (code->pad) {
>> +     case MIPI_CSI2_PAD_SINK:
>> +             if (code->index >= ARRAY_SIZE(c3_mipi_csi_formats))
>> +                     return -EINVAL;
>> +
>> +             code->code = c3_mipi_csi_formats[code->index];
>> +             break;
>> +     case MIPI_CSI2_PAD_SRC:
>> +             struct v4l2_mbus_framefmt *fmt;
>> +
>> +             if (code->index > 0)
>> +                     return -EINVAL;
>> +
>> +             fmt = v4l2_subdev_state_get_format(state, code->pad);
>> +             code->code = fmt->code;
>> +             break;
> I'm not sure if the V4L2 API specify that the formats on a pad should
> be enumerated in full, regardless of the configuration, or like you're
> doing here reflect the subdev configuration. I like what you have here
> more, so unless someone screams I think it's fine.

OK, thanks.

I will pay attention to the review of this function.

>> +     default:
>> +             return -EINVAL;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_set_fmt(struct v4l2_subdev *sd,
>> +                            struct v4l2_subdev_state *state,
>> +                            struct v4l2_subdev_format *format)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +     unsigned int i;
>> +
>> +     if (format->pad != MIPI_CSI2_PAD_SINK)
>> +             return v4l2_subdev_get_fmt(sd, state, format);
>> +
>> +     fmt = v4l2_subdev_state_get_format(state, format->pad);
> Could you clarify what other streams you plan to support ? As you
> support routing I presume you are preparing to capture
> multiple streams of data like image + embedded data, or to support
> sensors which sends data on different virtual channels ?
>
> How do you see this driver evolve ? Will it be augmented with an
> additional source pad directed to a video device where to capture
> embedded data from ?
>
> I'm wondering because if PAD_SINK become multiplexed, you won't be
> allowed to set a format there. It only works now because you have a
> single stream.
>
> Speaking of which, as you prepare to support multiple streams, I would
> specify the stream number when calling v4l2_subdev_state_get_format().
>
>          fmt = v4l2_subdev_state_get_format(state, format->pad, 0);
>
Thanks for your suggestion.

But this MIPI CSI2 hardware module doesn't have the ability to separate 
data , such as image + embedded data.

So there are no plans to support other streams.

>> +
>> +     for (i = 0; i < ARRAY_SIZE(c3_mipi_csi_formats); i++)
>> +             if (format->format.code == c3_mipi_csi_formats[i])
>> +                     break;
> nit: please use {} for the for loop
Will add { } for the for loop
>> +
>> +     if (i == ARRAY_SIZE(c3_mipi_csi_formats))
>> +             fmt->code = c3_mipi_csi_formats[0];
>> +     else
>> +             fmt->code = c3_mipi_csi_formats[i];
> You could set this in the for loop, before breaking.
Will put this in the for loop.
>> +
>> +     fmt->width = clamp_t(u32, format->format.width,
>> +                          MIPI_CSI2_MIN_WIDTH, MIPI_CSI2_MAX_WIDTH);
>> +     fmt->height = clamp_t(u32, format->format.height,
>> +                           MIPI_CSI2_MIN_HEIGHT, MIPI_CSI2_MAX_HEIGHT);
>> +
> You should set the colorspace related information too, as you
> initialize them, similar to what you do in init_state()
Will set the colorspace .
>> +     format->format = *fmt;
>> +
>> +     /* Synchronize the format to source pad */
>> +     fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
>> +     *fmt = format->format;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_init_state(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SINK);
>> +     src_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
>> +
>> +     sink_fmt->width = MIPI_CSI2_DEFAULT_WIDTH;
>> +     sink_fmt->height = MIPI_CSI2_DEFAULT_HEIGHT;
>> +     sink_fmt->field = V4L2_FIELD_NONE;
>> +     sink_fmt->code = MIPI_CSI2_DEFAULT_FMT;
>> +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>> +     sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
>> +     sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
>> +     sink_fmt->quantization =
>> +             V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
> If you could initialize them like you do above, with specific values
> instead of using _DEFAULT() I think it's better.
Will use the specific values.
>> +                                           sink_fmt->ycbcr_enc);
>> +     *src_fmt = *sink_fmt;
>> +
>> +     return c3_mipi_csi_init_routing(sd, state);
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops c3_mipi_csi_pad_ops = {
>> +     .enum_mbus_code = c3_mipi_csi_enum_mbus_code,
>> +     .get_fmt = v4l2_subdev_get_fmt,
>> +     .set_fmt = c3_mipi_csi_set_fmt,
>> +     .set_routing = c3_mipi_csi_set_routing,
>> +     .enable_streams = c3_mipi_csi_enable_streams,
>> +     .disable_streams = c3_mipi_csi_disable_streams,
>> +};
>> +
>> +static const struct v4l2_subdev_ops c3_mipi_csi_subdev_ops = {
>> +     .pad = &c3_mipi_csi_pad_ops,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops c3_mipi_csi_internal_ops = {
>> +     .init_state = c3_mipi_csi_init_state,
>> +};
>> +
>> +/* Media entity operations */
>> +static const struct media_entity_operations c3_mipi_csi_entity_ops = {
>> +     .link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +/* PM runtime */
>> +
>> +static int __maybe_unused c3_mipi_csi_runtime_suspend(struct device *dev)
>> +{
>> +     struct csi_device *csi = dev_get_drvdata(dev);
>> +
>> +     clk_bulk_disable_unprepare(csi->info->clock_num, csi->clks);
>> +
>> +     return 0;
>> +}
>> +
>> +static int __maybe_unused c3_mipi_csi_runtime_resume(struct device *dev)
>> +{
>> +     struct csi_device *csi = dev_get_drvdata(dev);
>> +
>> +     return clk_bulk_prepare_enable(csi->info->clock_num, csi->clks);
>> +}
>> +
>> +static const struct dev_pm_ops c3_mipi_csi_pm_ops = {
>> +     SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>> +                             pm_runtime_force_resume)
>> +     SET_RUNTIME_PM_OPS(c3_mipi_csi_runtime_suspend,
>> +                        c3_mipi_csi_runtime_resume, NULL)
> You could use SYSTEM_SLEEP_PM_OPS and RUNTIME_PM_OPS and set
>
>                  .pm = pm_ptr(&c3_mipi_csi_pm_ops),
>
> to avoid __maybe_unused in the functions, up to you
>
OK, will use your method to set pm.
>> +};
>> +
>> +/* Probe/remove & platform driver */
>> +
>> +static int c3_mipi_csi_subdev_init(struct csi_device *csi)
>> +{
>> +     struct v4l2_subdev *sd = &csi->sd;
>> +     int ret;
>> +
>> +     v4l2_subdev_init(sd, &c3_mipi_csi_subdev_ops);
>> +     sd->owner = THIS_MODULE;
>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +     sd->internal_ops = &c3_mipi_csi_internal_ops;
>> +     snprintf(sd->name, sizeof(sd->name), "%s", MIPI_CSI2_SUBDEV_NAME);
>> +
>> +     sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>> +     sd->entity.ops = &c3_mipi_csi_entity_ops;
>> +
>> +     sd->dev = csi->dev;
>> +     v4l2_set_subdevdata(sd, csi);
>> +
>> +     csi->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>> +     csi->pads[MIPI_CSI2_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
>> +     ret = media_entity_pads_init(&sd->entity, MIPI_CSI2_PAD_MAX, csi->pads);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_init_finalize(sd);
>> +     if (ret) {
>> +             media_entity_cleanup(&sd->entity);
>> +             return ret;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_mipi_csi_subdev_deinit(struct csi_device *csi)
>> +{
>> +     v4l2_subdev_cleanup(&csi->sd);
>> +     media_entity_cleanup(&csi->sd.entity);
>> +}
>> +
>> +/* Subdev notifier register */
>> +static int c3_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier,
>> +                                 struct v4l2_subdev *sd,
>> +                                 struct v4l2_async_connection *asc)
>> +{
>> +     struct csi_device *csi = v4l2_get_subdevdata(notifier->sd);
>> +     struct media_pad *sink = &csi->sd.entity.pads[MIPI_CSI2_PAD_SINK];
>> +     int ret;
>> +
>> +     ret = media_entity_get_fwnode_pad(&sd->entity,
>> +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
>> +     if (ret < 0) {
>> +             dev_err(csi->dev, "Failed to find pad for %s\n", sd->name);
>> +             return ret;
>> +     }
>> +
>> +     csi->src_sd = sd;
>> +     csi->src_sd_pad = ret;
>> +
>> +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
>> +                                            MEDIA_LNK_FL_IMMUTABLE);
>> +}
>> +
>> +static const struct v4l2_async_notifier_operations c3_mipi_csi_notify_ops = {
>> +     .bound = c3_mipi_csi_notify_bound,
>> +};
>> +
>> +static int c3_mipi_csi_async_register(struct csi_device *csi)
>> +{
>> +     struct v4l2_fwnode_endpoint vep = {
>> +             .bus_type = V4L2_MBUS_CSI2_DPHY,
>> +     };
>> +     struct v4l2_async_connection *asc;
>> +     struct fwnode_handle *ep;
>> +     int ret;
>> +
>> +     v4l2_async_subdev_nf_init(&csi->notifier, &csi->sd);
>> +
>> +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0,
>> +                                          FWNODE_GRAPH_ENDPOINT_NEXT);
>> +     if (!ep)
>> +             return -ENOTCONN;
>> +
>> +     ret = v4l2_fwnode_endpoint_parse(ep, &vep);
>> +     if (ret)
>> +             goto err_put_handle;
>> +
>> +     csi->bus = vep.bus.mipi_csi2;
>> +     if (csi->bus.num_data_lanes != 2 && csi->bus.num_data_lanes != 4)
>> +             goto err_put_handle;
> I would dev_err() here
>
> Thanks
>     j
Wil add dev_err().
>> +
>> +     asc = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep,
>> +                                           struct v4l2_async_connection);
>> +     if (IS_ERR(asc)) {
>> +             ret = PTR_ERR(asc);
>> +             goto err_put_handle;
>> +     }
>> +
>> +     csi->notifier.ops = &c3_mipi_csi_notify_ops;
>> +     ret = v4l2_async_nf_register(&csi->notifier);
>> +     if (ret)
>> +             goto err_cleanup_nf;
>> +
>> +     ret = v4l2_async_register_subdev(&csi->sd);
>> +     if (ret)
>> +             goto err_unregister_nf;
>> +
>> +     fwnode_handle_put(ep);
>> +
>> +     return 0;
>> +
>> +err_unregister_nf:
>> +     v4l2_async_nf_unregister(&csi->notifier);
>> +err_cleanup_nf:
>> +     v4l2_async_nf_cleanup(&csi->notifier);
>> +err_put_handle:
>> +     fwnode_handle_put(ep);
>> +     return ret;
>> +}
>> +
>> +static void c3_mipi_csi_async_unregister(struct csi_device *csi)
>> +{
>> +     v4l2_async_unregister_subdev(&csi->sd);
>> +     v4l2_async_nf_unregister(&csi->notifier);
>> +     v4l2_async_nf_cleanup(&csi->notifier);
>> +}
>> +
>> +static int c3_mipi_csi_ioremap_resource(struct csi_device *csi)
>> +{
>> +     struct device *dev = csi->dev;
>> +     struct platform_device *pdev = to_platform_device(dev);
>> +
>> +     csi->aphy = devm_platform_ioremap_resource_byname(pdev, "aphy");
>> +     if (IS_ERR(csi->aphy))
>> +             return PTR_ERR(csi->aphy);
>> +
>> +     csi->dphy = devm_platform_ioremap_resource_byname(pdev, "dphy");
>> +     if (IS_ERR(csi->dphy))
>> +             return PTR_ERR(csi->dphy);
>> +
>> +     csi->host = devm_platform_ioremap_resource_byname(pdev, "host");
>> +     if (IS_ERR(csi->host))
>> +             return PTR_ERR(csi->host);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_configure_clocks(struct csi_device *csi)
>> +{
>> +     const struct csi_info *info = csi->info;
>> +     int ret;
>> +     u32 i;
>> +
>> +     for (i = 0; i < info->clock_num; i++)
>> +             csi->clks[i].id = info->clocks[i];
>> +
>> +     ret = devm_clk_bulk_get(csi->dev, info->clock_num, csi->clks);
>> +     if (ret)
>> +             return ret;
>> +
>> +     for (i = 0; i < info->clock_num; i++) {
>> +             if (!info->clock_rates[i])
>> +                     continue;
>> +             ret = clk_set_rate(csi->clks[i].clk, info->clock_rates[i]);
>> +             if (ret) {
>> +                     dev_err(csi->dev, "Failed to set %s rate %u\n", info->clocks[i],
>> +                             info->clock_rates[i]);
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_probe(struct platform_device *pdev)
>> +{
>> +     struct device *dev = &pdev->dev;
>> +     struct csi_device *csi;
>> +     int ret;
>> +
>> +     csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
>> +     if (!csi)
>> +             return -ENOMEM;
>> +
>> +     csi->info = of_device_get_match_data(dev);
>> +     csi->dev = dev;
>> +
>> +     ret = c3_mipi_csi_ioremap_resource(csi);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
>> +
>> +     ret = c3_mipi_csi_configure_clocks(csi);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
>> +
>> +     platform_set_drvdata(pdev, csi);
>> +
>> +     mutex_init(&csi->lock);
>> +     pm_runtime_enable(dev);
>> +
>> +     ret = c3_mipi_csi_subdev_init(csi);
>> +     if (ret)
>> +             goto err_disable_runtime_pm;
>> +
>> +     ret = c3_mipi_csi_async_register(csi);
>> +     if (ret)
>> +             goto err_deinit_subdev;
>> +
>> +     return 0;
>> +
>> +err_deinit_subdev:
>> +     c3_mipi_csi_subdev_deinit(csi);
>> +err_disable_runtime_pm:
>> +     pm_runtime_disable(dev);
>> +     mutex_destroy(&csi->lock);
>> +     return ret;
>> +};
>> +
>> +static void c3_mipi_csi_remove(struct platform_device *pdev)
>> +{
>> +     struct csi_device *csi = platform_get_drvdata(pdev);
>> +
>> +     c3_mipi_csi_async_unregister(csi);
>> +     c3_mipi_csi_subdev_deinit(csi);
>> +
>> +     pm_runtime_disable(&pdev->dev);
>> +     mutex_destroy(&csi->lock);
>> +};
>> +
>> +static const struct csi_info c3_mipi_csi_info = {
>> +     .clocks = {"vapb", "phy0"},
>> +     .clock_rates = {0, 200000000},
>> +     .clock_num = 2
>> +};
>> +
>> +static const struct of_device_id c3_mipi_csi_of_match[] = {
>> +     { .compatible = "amlogic,c3-mipi-csi2",
>> +       .data = &c3_mipi_csi_info,
>> +     },
>> +     { },
>> +};
>> +MODULE_DEVICE_TABLE(of, c3_mipi_csi_of_match);
>> +
>> +static struct platform_driver c3_mipi_csi_driver = {
>> +     .probe = c3_mipi_csi_probe,
>> +     .remove = c3_mipi_csi_remove,
>> +     .driver = {
>> +             .name = "c3-mipi-csi2",
>> +             .of_match_table = c3_mipi_csi_of_match,
>> +             .pm = &c3_mipi_csi_pm_ops,
>> +     },
>> +};
>> +
>> +module_platform_driver(c3_mipi_csi_driver);
>> +
>> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
>> +MODULE_DESCRIPTION("Amlogic C3 MIPI CSI-2 receiver");
>> +MODULE_LICENSE("GPL");
>>
>> --
>> 2.46.1
>>
>>
>>

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

* Re: [PATCH v3 4/9] media: platform: Add c3 mipi adapter driver
  2024-09-18  6:07 ` [PATCH v3 4/9] media: platform: Add c3 mipi adapter driver Keke Li via B4 Relay
@ 2024-11-05 11:24   ` Jacopo Mondi
  2024-11-06  5:32     ` Keke Li
  0 siblings, 1 reply; 37+ messages in thread
From: Jacopo Mondi @ 2024-11-05 11:24 UTC (permalink / raw)
  To: keke.li
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi Keke

On Wed, Sep 18, 2024 at 02:07:15PM +0800, Keke Li via B4 Relay wrote:
> From: Keke Li <keke.li@amlogic.com>
>
> This driver mainly responsible for organizing
> MIPI data and sending raw data to ISP pipeline.

A few questions on the design of this subdevice.

In my understanding the adapter provides several functionalities:

- Data alignement to match the ISP size constraints
- Read from DDR memory or from the CSI-2 RX (direct path)
- Write to DDR memory or to the ISP (direct path)
- Split data streams to DDR or direct path based on CSI-2 Virtual
  Channel and Data type.

As I understand the current implementation only supports direct-path
to ISP of data sent on VC#0.

Do you plan to expand the adapter subdev with support for mulitple
paths ? Is this why you are supporting already a routing table ?

>
> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> Signed-off-by: Keke Li <keke.li@amlogic.com>
> ---
>  MAINTAINERS                                        |   7 +
>  drivers/media/platform/amlogic/Kconfig             |   1 +
>  drivers/media/platform/amlogic/Makefile            |   1 +
>  .../media/platform/amlogic/c3-mipi-adapter/Kconfig |  16 +
>  .../platform/amlogic/c3-mipi-adapter/Makefile      |   3 +
>  .../amlogic/c3-mipi-adapter/c3-mipi-adap.c         | 913 +++++++++++++++++++++
>  6 files changed, 941 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9e75874a6e69..31168c05f304 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1209,6 +1209,13 @@ F:	Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
>  F:	drivers/perf/amlogic/
>  F:	include/soc/amlogic/
>
> +AMLOGIC MIPI ADAPTER DRIVER
> +M:	Keke Li <keke.li@amlogic.com>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml
> +F:	drivers/media/platform/amlogic/c3-mipi-adapter/
> +
>  AMLOGIC MIPI CSI2 DRIVER
>  M:	Keke Li <keke.li@amlogic.com>
>  L:	linux-media@vger.kernel.org
> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
> index b7c2de14848b..df09717b28d0 100644
> --- a/drivers/media/platform/amlogic/Kconfig
> +++ b/drivers/media/platform/amlogic/Kconfig
> @@ -2,5 +2,6 @@
>
>  comment "Amlogic media platform drivers"
>
> +source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
>  source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>  source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
> index 4f571ce5d13e..b370154b090c 100644
> --- a/drivers/media/platform/amlogic/Makefile
> +++ b/drivers/media/platform/amlogic/Makefile
> @@ -1,4 +1,5 @@
>  # SPDX-License-Identifier: GPL-2.0-only
>
> +obj-y += c3-mipi-adapter/
>  obj-y += c3-mipi-csi2/
>  obj-y += meson-ge2d/
> diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig b/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig
> new file mode 100644
> index 000000000000..bf19059b3543
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig
> @@ -0,0 +1,16 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config VIDEO_C3_MIPI_ADAPTER
> +	tristate "Amlogic C3 MIPI adapter"
> +	depends on ARCH_MESON || COMPILE_TEST
> +	depends on VIDEO_DEV
> +	depends on OF
> +	select MEDIA_CONTROLLER
> +	select V4L2_FWNODE
> +	select VIDEO_V4L2_SUBDEV_API
> +	help
> +	  Video4Linux2 driver for Amlogic C3 MIPI adapter.
> +	  C3 MIPI adapter mainly responsible for organizing
> +	  MIPI data and sending raw data to ISP pipeline.
> +
> +	  To compile this driver as a module choose m here.
> diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile b/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile
> new file mode 100644
> index 000000000000..216fc310c5b4
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +obj-$(CONFIG_VIDEO_C3_MIPI_ADAPTER) += c3-mipi-adap.o
> diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c b/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c
> new file mode 100644
> index 000000000000..b64eb417c2e2
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c
> @@ -0,0 +1,913 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/v4l2-subdev.h>
> +
> +/* C3 adapter submodule definition */
> +enum {
> +	SUBMD_TOP,
> +	SUBMD_FD,
> +	SUBMD_RD,
> +};
> +
> +#define ADAP_SUBMD_MASK             GENMASK(17, 16)
> +#define ADAP_SUBMD_SHIFT            16
> +#define ADAP_SUBMD(x)               (((x) & (ADAP_SUBMD_MASK)) >> (ADAP_SUBMD_SHIFT))
> +#define ADAP_REG_ADDR_MASK          GENMASK(15, 0)
> +#define ADAP_REG_ADDR(x)            ((x) & (ADAP_REG_ADDR_MASK))
> +#define ADAP_REG_T(x)               ((SUBMD_TOP << ADAP_SUBMD_SHIFT) | (x))
> +#define ADAP_REG_F(x)               ((SUBMD_FD << ADAP_SUBMD_SHIFT) | (x))
> +#define ADAP_REG_R(x)               ((SUBMD_RD << ADAP_SUBMD_SHIFT) | (x))
> +
> +#define MIPI_ADAP_CLOCK_NUM_MAX     3
> +#define MIPI_ADAP_SUBDEV_NAME       "mipi-adapter"

I would add a "c3-" prefix

> +
> +/* C3 MIPI adapter TOP register */
> +#define MIPI_ADAPT_DE_CTRL0         ADAP_REG_T(0x40)
> +#define ADAP_DE_READ_BYPASS         BIT(3)
> +#define ADAP_DE_WRITE_BYPASS        BIT(7)
> +
> +/* C3 MIPI adapter FRONTEND register */
> +#define CSI2_CLK_RESET              ADAP_REG_F(0x00)
> +#define ADAP_FD_APPLY_RESET         BIT(0)
> +#define ADAP_FD_ENABLE              BIT(1)
> +
> +#define CSI2_GEN_CTRL0              ADAP_REG_F(0x04)
> +#define ADAP_FD_VIRTUAL_CHN0_EN     BIT(0)
> +#define ADAP_FD_VIRTUAL_CHN1_EN     BIT(1)
> +#define ADAP_FD_VIRTUAL_CHN2_EN     BIT(2)
> +#define ADAP_FD_VIRTUAL_CHN3_EN     BIT(3)
> +#define ADAP_FD_ENABLE_PACKETS      GENMASK(20, 16)
> +#define ADAP_FD_ENABLE_RAW          BIT(16)
> +
> +#define CSI2_X_START_END_ISP        ADAP_REG_F(0x0c)
> +#define ADAP_FD_X_END_MASK          GENMASK(31, 16)
> +#define ADAP_FD_X_END_SHIFT         16
> +#define ADAP_FD_X_END(x)            ((x) - 1)
> +
> +#define CSI2_Y_START_END_ISP        ADAP_REG_F(0x10)
> +#define ADAP_FD_Y_END_MASK          GENMASK(31, 16)
> +#define ADAP_FD_Y_END_SHIFT         16
> +#define ADAP_FD_Y_END(x)            ((x) - 1)
> +
> +#define CSI2_VC_MODE                ADAP_REG_F(0x1c)
> +#define ADAP_FD_VS_SEL_VC_MASK      GENMASK(19, 16)
> +#define ADAP_FD_VS_DIRECT_PATH      BIT(16)
> +#define ADAP_FD_HS_SEL_VC_MASK      GENMASK(23, 20)
> +#define ADAP_FD_HS_DIRECT_PATH      BIT(20)
> +
> +/* C3 MIPI adapter READER register */
> +#define MIPI_ADAPT_DDR_RD0_CNTL0    ADAP_REG_R(0x00)
> +#define ADAP_RD0_MODULE_ENABLE      BIT(0)
> +#define ADAP_RD0_LINE_STRIDE_MASK   GENMASK(13, 4)
> +#define ADAP_RD0_LINE_STRIDE_SHIFT  4
> +#define ADAP_RD0_SAMPLE_SEL_MASK    GENMASK(27, 26)
> +#define ADAP_RD0_DATA_IN_VSYNC      BIT(26)
> +#define ADAP_RD0_BURST_TYPE_MASK    GENMASK(29, 28)
> +#define ADAP_RD0_BURST_TYPE_SHIFT   28
> +#define ADAP_RD0_BURST_TYPE_INRC8   3
> +#define ADAP_RD0_FRAME_RD_START     BIT(31)
> +
> +#define MIPI_ADAPT_DDR_RD0_CNTL1    ADAP_REG_R(0x04)
> +#define ADAP_RD0_LINE_SIZE_MASK     GENMASK(9, 0)
> +#define ADAP_RD0_LINE_NUM_MASK      GENMASK(28, 16)
> +#define ADAP_RD0_LINE_NUM_SHIFT     16
> +
> +#define MIPI_ADAPT_PIXEL0_CNTL0     ADAP_REG_R(0x80)
> +#define ADAP_PIXEL0_WORK_MODE_MASK  GENMASK(17, 16)
> +#define ADAP_PIXEL0_DIRECT_PATH     BIT(16)
> +#define ADAP_PIXEL0_DATA_TYPE_MASK  GENMASK(25, 20)
> +#define ADAP_PIXEL0_DATA_TYPE_SHIFT 20
> +#define ADAP_PIXEL0_DATA_TYPE_10BITS 0x2b
> +#define ADAP_PIXEL0_DATA_TYPE_12BITS 0x2c
> +#define ADAP_PIXEL0_START_ENABLE    BIT(31)
> +
> +#define MIPI_ADAPT_PIXEL0_CNTL1     ADAP_REG_R(0x84)
> +#define ADAP_PIXEL0_X_END_MASK      GENMASK(15, 0)
> +#define ADAP_PIXEL0_X_END(x)        ((x) - 1)
> +
> +#define MIPI_ADAPT_PIXEL0_CNTL2     ADAP_REG_R(0x88)
> +#define ADAP_PIXEL0_FIFO_SIZE_MASK  GENMASK(9, 0)
> +#define ADAP_PIXEL0_PIXEL_NUM_MASK  GENMASK(27, 15)
> +#define ADAP_PIXEL0_PIXEL_NUM_SHIFT 15
> +
> +#define MIPI_ADAPT_ALIG_CNTL0       ADAP_REG_R(0x100)
> +#define ADAP_ALIG_V_TOTAL_NUM_MASK  GENMASK(15, 0)
> +/* Need to add a default blank */
> +#define ADAP_ALIG_V_TOTAL_NUM(x)    ((x) + 64)
> +#define ADAP_ALIG_H_TOTAL_NUM_MASK  GENMASK(31, 16)
> +#define ADAP_ALIG_H_TOTAL_NUM_SHIFT 16
> +/* Need to add a default blank */
> +#define ADAP_ALIG_H_TOTAL_NUM(x)    ((x) + 64)
> +
> +#define MIPI_ADAPT_ALIG_CNTL1       ADAP_REG_R(0x104)
> +#define ADAP_ALIG_HPE_NUM_MASK      GENMASK(31, 16)
> +#define ADAP_ALIG_HPE_NUM_SHIFT     16
> +
> +#define MIPI_ADAPT_ALIG_CNTL2       ADAP_REG_R(0x108)
> +#define ADAP_ALIG_VPE_NUM_MASK      GENMASK(31, 16)
> +#define ADAP_ALIG_VPE_NUM_SHIFT     16
> +
> +#define MIPI_ADAPT_ALIG_CNTL3       ADAP_REG_R(0x10c)
> +#define ADAP_ALIG_FRM_ST_PIXEL_MASK GENMASK(15, 0)
> +
> +#define MIPI_ADAPT_ALIG_CNTL6       ADAP_REG_R(0x118)
> +#define ADAP_ALIG_LANE0_ENABLE      BIT(0)
> +#define ADAP_ALIG_DATA_MODE0_MASK   BIT(4)
> +#define ADAP_ALIG_DIRECT_MODE       BIT(4)
> +#define ADAP_ALIG_VDATA0_ENABLE     BIT(12)
> +#define ADAP_ALIG_VDATA1_ENABLE     BIT(13)
> +#define ADAP_ALIG_VDATA2_ENABLE     BIT(14)
> +#define ADAP_ALIG_VDATA3_ENABLE     BIT(15)
> +
> +#define MIPI_ADAPT_ALIG_CNTL8       ADAP_REG_R(0x120)
> +#define ADAP_ALIG_FRAME_CONTINUE    BIT(5)
> +#define ADAP_ALIG_EXC_MASK_DIS      BIT(12)
> +#define ADAP_ALIG_START_ENABLE      BIT(31)
> +
> +#define MIPI_ADAP_MAX_WIDTH         2888
> +#define MIPI_ADAP_MIN_WIDTH         160
> +#define MIPI_ADAP_MAX_HEIGHT        2240
> +#define MIPI_ADAP_MIN_HEIGHT        120
> +#define MIPI_ADAP_DEFAULT_WIDTH     1920
> +#define MIPI_ADAP_DEFAULT_HEIGHT    1080
> +#define MIPI_ADAP_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
> +
> +/* C3 MIPI adapter pad list */
> +enum {
> +	MIPI_ADAP_PAD_SINK,
> +	MIPI_ADAP_PAD_SRC,
> +	MIPI_ADAP_PAD_MAX
> +};
> +
> +/*
> + * struct adap_info - mipi adapter information
> + *
> + * @clocks: array of mipi adapter clock names
> + * @clock_rates: array of mipi adapter clock rate
> + * @clock_num: actual clock number
> + */
> +struct adap_info {
> +	char *clocks[MIPI_ADAP_CLOCK_NUM_MAX];
> +	u32 clock_rates[MIPI_ADAP_CLOCK_NUM_MAX];
> +	u32 clock_num;
> +};
> +
> +/**

Here as well you can drop one * from /**

> + * struct adap_device - mipi adapter platform device
> + *
> + * @dev: pointer to the struct device
> + * @top: mipi adapter top register address
> + * @fd: mipi adapter frontend register address
> + * @rd: mipi adapter reader register address
> + * @clks: array of MIPI adapter clocks
> + * @sd: mipi adapter sub-device
> + * @pads: mipi adapter sub-device pads
> + * @notifier: notifier to register on the v4l2-async API
> + * @format: save sub-device format
> + * @src_sd: source sub-device
> + * @src_sd_pad: source sub-device pad
> + * @lock: protect mipi adapter device
> + * @info: version-specific MIPI adapter information
> + */
> +struct adap_device {
> +	struct device *dev;
> +	void __iomem *top;
> +	void __iomem *fd;
> +	void __iomem *rd;
> +	struct clk_bulk_data clks[MIPI_ADAP_CLOCK_NUM_MAX];
> +
> +	struct v4l2_subdev sd;
> +	struct media_pad pads[MIPI_ADAP_PAD_MAX];
> +	struct v4l2_async_notifier notifier;
> +	struct v4l2_subdev_format format;
> +	struct v4l2_subdev *src_sd;
> +
> +	u16 src_sd_pad;
> +	struct mutex lock; /* Protect adapter device */

Most comments on the csi2-rx patch apply to this one as well.
In this case, you don't need locking for functions that receive a
subdev_state


> +	const struct adap_info *info;
> +};
> +
> +/* Format helpers */
> +
> +struct adap_pix_format {
> +	u32 code;
> +	u8 width;

I would call it bpp

> +};
> +
> +static const struct adap_pix_format c3_mipi_adap_formats[] = {
> +	{ MEDIA_BUS_FMT_SBGGR10_1X10, 10 },
> +	{ MEDIA_BUS_FMT_SGBRG10_1X10, 10 },
> +	{ MEDIA_BUS_FMT_SGRBG10_1X10, 10 },
> +	{ MEDIA_BUS_FMT_SRGGB10_1X10, 10 },
> +	{ MEDIA_BUS_FMT_SBGGR12_1X12, 12 },
> +	{ MEDIA_BUS_FMT_SGBRG12_1X12, 12 },
> +	{ MEDIA_BUS_FMT_SGRBG12_1X12, 12 },
> +	{ MEDIA_BUS_FMT_SRGGB12_1X12, 12 },
> +};
> +
> +static const struct adap_pix_format
> +*c3_mipi_adap_find_format(u32 code)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(c3_mipi_adap_formats); i++)
> +		if (code == c3_mipi_adap_formats[i].code)
> +			return &c3_mipi_adap_formats[i];
> +
> +	return NULL;
> +}
> +
> +/* Hardware configuration */
> +
> +static void c3_mipi_adap_update_bits(struct adap_device *adap, u32 reg,
> +				     u32 mask, u32 val)
> +{
> +	void __iomem *addr;
> +	u32 orig, tmp;
> +
> +	switch (ADAP_SUBMD(reg)) {
> +	case SUBMD_TOP:
> +		addr = adap->top + ADAP_REG_ADDR(reg);
> +		break;
> +	case SUBMD_FD:
> +		addr = adap->fd + ADAP_REG_ADDR(reg);
> +		break;
> +	case SUBMD_RD:
> +		addr = adap->rd + ADAP_REG_ADDR(reg);
> +		break;
> +	default:
> +		dev_err(adap->dev, "Invalid sub-module: %lu\n", ADAP_SUBMD(reg));
> +		return;
> +	}
> +
> +	orig = readl(addr);
> +	tmp = orig & ~mask;
> +	tmp |= val & mask;
> +
> +	if (tmp != orig)
> +		writel(tmp, addr);
> +}
> +
> +/* Configure adapter top sub module */
> +static void c3_mipi_adap_cfg_top(struct adap_device *adap)
> +{
> +	/* Bypass decompress */
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0,
> +				 ADAP_DE_READ_BYPASS, ADAP_DE_READ_BYPASS);
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0,
> +				 ADAP_DE_WRITE_BYPASS, ADAP_DE_WRITE_BYPASS);
> +}
> +
> +/* Configure adapter frontend sub module */
> +static void c3_mipi_adap_cfg_frontend(struct adap_device *adap,
> +				      struct v4l2_mbus_framefmt *fmt)
> +{
> +	/* The default value of BIT_0 is 1, so need release reset firstly. */
> +	c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET, ADAP_FD_APPLY_RESET, 0);
> +
> +	c3_mipi_adap_update_bits(adap, CSI2_X_START_END_ISP, ADAP_FD_X_END_MASK,
> +				 ADAP_FD_X_END(fmt->width) << ADAP_FD_X_END_SHIFT);
> +	c3_mipi_adap_update_bits(adap, CSI2_Y_START_END_ISP, ADAP_FD_Y_END_MASK,
> +				 ADAP_FD_Y_END(fmt->height) << ADAP_FD_Y_END_SHIFT);
> +
> +	/* Select VS and HS signal to direct path */
> +	c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, ADAP_FD_VS_SEL_VC_MASK,
> +				 ADAP_FD_VS_DIRECT_PATH);
> +	c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, ADAP_FD_HS_SEL_VC_MASK,
> +				 ADAP_FD_HS_DIRECT_PATH);
> +
> +	/* Enable to receive RAW image */
> +	c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_ENABLE_PACKETS,
> +				 ADAP_FD_ENABLE_RAW);
> +
> +	/* Enable virtual channel 0~3 */
> +	c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN0_EN,
> +				 ADAP_FD_VIRTUAL_CHN0_EN);
> +	c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN1_EN,
> +				 ADAP_FD_VIRTUAL_CHN1_EN);
> +	c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN2_EN,
> +				 ADAP_FD_VIRTUAL_CHN2_EN);
> +	c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN3_EN,
> +				 ADAP_FD_VIRTUAL_CHN3_EN);
> +}
> +
> +/* Configure adapter reader sub module */
> +static void c3_mipi_adap_cfg_reader(struct adap_device *adap,
> +				    struct v4l2_mbus_framefmt *fmt)
> +{
> +	const struct adap_pix_format *pix_format;
> +	u32 line_size;
> +	u8 data_type;
> +
> +	/* Data size for a line, unit: 128 bits */
> +	pix_format = c3_mipi_adap_find_format(fmt->code);
> +	line_size = fmt->width * pix_format->width;
> +	line_size = DIV_ROUND_UP(line_size, 128);
> +
> +	if (pix_format->width == 10) {
> +		data_type = ADAP_PIXEL0_DATA_TYPE_10BITS;
> +	} else if (pix_format->width == 12) {
> +		data_type = ADAP_PIXEL0_DATA_TYPE_12BITS;

You should use MIPI_CSI2_DT_RAW10 and MIPI_CSI2_DT_RAW12 from
"include/media/mipi-csi2.h"

> +	} else {
> +		dev_err(adap->dev, "Invalid raw format width: %u\n", pix_format->width);
> +		return;
> +	}
> +
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL1,
> +				 ADAP_RD0_LINE_SIZE_MASK, line_size);
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL1, ADAP_RD0_LINE_NUM_MASK,
> +				 fmt->height << ADAP_RD0_LINE_NUM_SHIFT);
> +
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_MODULE_ENABLE,
> +				 ADAP_RD0_MODULE_ENABLE);

I'm not sure I get this. I read this bit as "Enable reading from DDR"
while I thought the adapter receives data directly from the CSI-2
receiver

> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_LINE_STRIDE_MASK,
> +				 line_size << ADAP_RD0_LINE_STRIDE_SHIFT);
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_SAMPLE_SEL_MASK,
> +				 ADAP_RD0_DATA_IN_VSYNC);
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_BURST_TYPE_MASK,
> +				 ADAP_RD0_BURST_TYPE_INRC8 << ADAP_RD0_BURST_TYPE_SHIFT);
> +
> +	/* Set data type and work mode */
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0,
> +				 ADAP_PIXEL0_WORK_MODE_MASK, ADAP_PIXEL0_DIRECT_PATH);

Ah maybe this setting "RAW from direct path" enables direct path from
CSI-2 receiver

> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0, ADAP_PIXEL0_DATA_TYPE_MASK,
> +				 data_type << ADAP_PIXEL0_DATA_TYPE_SHIFT);
> +
> +	/* Set pixel end number */
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL1, ADAP_PIXEL0_X_END_MASK,
> +				 ADAP_PIXEL0_X_END(fmt->width));
> +
> +	/* Set line pixel number and reader fifo size */
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL2,
> +				 ADAP_PIXEL0_FIFO_SIZE_MASK, line_size);
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL2, ADAP_PIXEL0_PIXEL_NUM_MASK,
> +				 fmt->width << ADAP_PIXEL0_PIXEL_NUM_SHIFT);
> +
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, ADAP_ALIG_V_TOTAL_NUM_MASK,
> +				 ADAP_ALIG_V_TOTAL_NUM(fmt->width));

What are the ISP alignment requirements ?

#define ADAP_ALIG_V_TOTAL_NUM(x)    ((x) + 64)

Are you aligning or just adding 64 here ?

> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, ADAP_ALIG_H_TOTAL_NUM_MASK,
> +				 ADAP_ALIG_H_TOTAL_NUM(fmt->height)
> +				 << ADAP_ALIG_H_TOTAL_NUM_SHIFT);
> +
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL1, ADAP_ALIG_HPE_NUM_MASK,
> +				 fmt->width << ADAP_ALIG_HPE_NUM_SHIFT);
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL2, ADAP_ALIG_VPE_NUM_MASK,
> +				 fmt->height << ADAP_ALIG_VPE_NUM_SHIFT);
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL3,
> +				 ADAP_ALIG_FRM_ST_PIXEL_MASK, fmt->width);
> +
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
> +				 ADAP_ALIG_LANE0_ENABLE, ADAP_ALIG_LANE0_ENABLE);
> +
> +	/* Select direct mode */
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
> +				 ADAP_ALIG_DATA_MODE0_MASK, ADAP_ALIG_DIRECT_MODE);
> +
> +	/* Enable vdata 0~3 */
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
> +				 ADAP_ALIG_VDATA0_ENABLE, ADAP_ALIG_VDATA0_ENABLE);
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
> +				 ADAP_ALIG_VDATA1_ENABLE, ADAP_ALIG_VDATA1_ENABLE);
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
> +				 ADAP_ALIG_VDATA2_ENABLE, ADAP_ALIG_VDATA2_ENABLE);
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
> +				 ADAP_ALIG_VDATA3_ENABLE, ADAP_ALIG_VDATA2_ENABLE);

For my education could you tell what these bit do ?

> +
> +	/* continue mode and disable hold counter */
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
> +				 ADAP_ALIG_FRAME_CONTINUE, ADAP_ALIG_FRAME_CONTINUE);
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
> +				 ADAP_ALIG_EXC_MASK_DIS, ADAP_ALIG_EXC_MASK_DIS);
> +}
> +
> +static void c3_mipi_adap_start_stream(struct adap_device *adap)
> +{
> +	/* Enable to start and will auto clear to 0 */
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
> +				 ADAP_ALIG_START_ENABLE, ADAP_ALIG_START_ENABLE);
> +
> +	/* Enable to start and will auto clear to 0 */
> +	c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0,
> +				 ADAP_PIXEL0_START_ENABLE, ADAP_PIXEL0_START_ENABLE);
> +
> +	/* Enable frontend */
> +	c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET,
> +				 ADAP_FD_ENABLE, ADAP_FD_ENABLE);
> +}
> +
> +static void c3_mipi_adap_cfg_format(struct adap_device *adap)
> +{
> +	struct v4l2_subdev_format *format = &adap->format;
> +
> +	c3_mipi_adap_cfg_top(adap);
> +	c3_mipi_adap_cfg_frontend(adap, &format->format);
> +	c3_mipi_adap_cfg_reader(adap, &format->format);
> +}
> +
> +/* V4L2 subdev operations */
> +
> +static int c3_mipi_adap_enable_streams(struct v4l2_subdev *sd,
> +				       struct v4l2_subdev_state *state,
> +				       u32 pad, u64 streams_mask)
> +{
> +	struct adap_device *adap = v4l2_get_subdevdata(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	guard(mutex)(&adap->lock);
> +
> +	pm_runtime_resume_and_get(adap->dev);
> +
> +	c3_mipi_adap_cfg_format(adap);
> +	c3_mipi_adap_start_stream(adap);
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       MIPI_ADAP_PAD_SINK,
> +						       &streams_mask);
> +	ret = v4l2_subdev_enable_streams(adap->src_sd,
> +					 adap->src_sd_pad,
> +					 sink_streams);
> +	if (ret) {
> +		pm_runtime_put(adap->dev);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_adap_disable_streams(struct v4l2_subdev *sd,
> +					struct v4l2_subdev_state *state,
> +					u32 pad, u64 streams_mask)
> +{
> +	struct adap_device *adap = v4l2_get_subdevdata(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	guard(mutex)(&adap->lock);
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       MIPI_ADAP_PAD_SINK,
> +						       &streams_mask);
> +	ret = v4l2_subdev_disable_streams(adap->src_sd,
> +					  adap->src_sd_pad,
> +					  sink_streams);
> +	if (ret)
> +		dev_err(adap->dev, "Failed to disable %s\n", adap->src_sd->name);
> +
> +	pm_runtime_put(adap->dev);
> +
> +	return ret;
> +}
> +
> +static int c3_mipi_adap_cfg_routing(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state,
> +				    struct v4l2_subdev_krouting *routing)
> +{
> +	static const struct v4l2_mbus_framefmt format = {
> +		.width = MIPI_ADAP_DEFAULT_WIDTH,
> +		.height = MIPI_ADAP_DEFAULT_HEIGHT,
> +		.code = MIPI_ADAP_DEFAULT_FMT,
> +		.field = V4L2_FIELD_NONE,
> +		.colorspace = V4L2_COLORSPACE_RAW,
> +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
> +		.xfer_func = V4L2_XFER_FUNC_NONE,
> +	};
> +	int ret;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_adap_init_routing(struct v4l2_subdev *sd,
> +				     struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes;
> +	struct v4l2_subdev_krouting routing;
> +
> +	routes.sink_pad = MIPI_ADAP_PAD_SINK;
> +	routes.sink_stream = 0;
> +	routes.source_pad = MIPI_ADAP_PAD_SRC;
> +	routes.source_stream = 0;
> +	routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> +
> +	routing.num_routes = 1;
> +	routing.routes = &routes;
> +
> +	return c3_mipi_adap_cfg_routing(sd, state, &routing);
> +}

Same comments as per the CSI-2 Rx, do you need to allow set routing
when a single route in enabled and valid ?

> +
> +static int c3_mipi_adap_set_routing(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state,
> +				    enum v4l2_subdev_format_whence which,
> +				    struct v4l2_subdev_krouting *routing)
> +{
> +	bool is_streaming = v4l2_subdev_is_streaming(sd);
> +
> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> +		return -EBUSY;
> +
> +	return c3_mipi_adap_cfg_routing(sd, state, routing);
> +}
> +
> +static int c3_mipi_adap_enum_mbus_code(struct v4l2_subdev *sd,
> +				       struct v4l2_subdev_state *state,
> +				       struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	switch (code->pad) {
> +	case MIPI_ADAP_PAD_SINK:
> +		if (code->index >= ARRAY_SIZE(c3_mipi_adap_formats))
> +			return -EINVAL;
> +
> +		code->code = c3_mipi_adap_formats[code->index].code;
> +		break;
> +	case MIPI_ADAP_PAD_SRC:
> +		struct v4l2_mbus_framefmt *fmt;
> +
> +		if (code->index > 0)
> +			return -EINVAL;
> +
> +		fmt = v4l2_subdev_state_get_format(state, code->pad);
> +		code->code = fmt->code;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_adap_set_fmt(struct v4l2_subdev *sd,
> +				struct v4l2_subdev_state *state,
> +				struct v4l2_subdev_format *format)
> +{
> +	struct adap_device *adap = v4l2_get_subdevdata(sd);
> +	struct v4l2_mbus_framefmt *fmt;
> +	const struct adap_pix_format *pix_format;
> +
> +	if (format->pad != MIPI_ADAP_PAD_SINK)
> +		return v4l2_subdev_get_fmt(sd, state, format);
> +
> +	pix_format = c3_mipi_adap_find_format(format->format.code);
> +	if (!pix_format)
> +		pix_format = &c3_mipi_adap_formats[0];
> +
> +	fmt = v4l2_subdev_state_get_format(state, format->pad);
> +	fmt->code = pix_format->code;
> +	fmt->width = clamp_t(u32, format->format.width,
> +			     MIPI_ADAP_MIN_WIDTH, MIPI_ADAP_MAX_WIDTH);
> +	fmt->height = clamp_t(u32, format->format.height,
> +			      MIPI_ADAP_MIN_HEIGHT, MIPI_ADAP_MAX_HEIGHT);
> +
> +	format->format = *fmt;
> +
> +	/* Synchronize the format to source pad */
> +	fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SRC);
> +	*fmt = format->format;
> +
> +	adap->format = *format;

There is not need to store the format in the driver-specific
structure. You use it c3_mipi_adap_cfg_format() called by
c3_mipi_adap_enable_streams() which receives a subdev_state where the format
is stored already.

Thanks
  j

> +
> +	return 0;
> +}
> +
> +static int c3_mipi_adap_init_state(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SINK);
> +	src_fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SRC);
> +
> +	sink_fmt->width = MIPI_ADAP_DEFAULT_WIDTH;
> +	sink_fmt->height = MIPI_ADAP_DEFAULT_HEIGHT;
> +	sink_fmt->field = V4L2_FIELD_NONE;
> +	sink_fmt->code = MIPI_ADAP_DEFAULT_FMT;
> +	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> +	sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
> +	sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
> +	sink_fmt->quantization =
> +		V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
> +					      sink_fmt->ycbcr_enc);
> +	*src_fmt = *sink_fmt;
> +
> +	return c3_mipi_adap_init_routing(sd, state);
> +}
> +
> +static const struct v4l2_subdev_pad_ops c3_mipi_adap_pad_ops = {
> +	.enum_mbus_code = c3_mipi_adap_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = c3_mipi_adap_set_fmt,
> +	.set_routing = c3_mipi_adap_set_routing,
> +	.enable_streams = c3_mipi_adap_enable_streams,
> +	.disable_streams = c3_mipi_adap_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops c3_mipi_adap_subdev_ops = {
> +	.pad = &c3_mipi_adap_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops c3_mipi_adap_internal_ops = {
> +	.init_state = c3_mipi_adap_init_state,
> +};
> +
> +/* Media entity operations */
> +static const struct media_entity_operations c3_mipi_adap_entity_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +/* PM runtime */
> +
> +static int __maybe_unused c3_mipi_adap_runtime_suspend(struct device *dev)
> +{
> +	struct adap_device *adap = dev_get_drvdata(dev);
> +
> +	clk_bulk_disable_unprepare(adap->info->clock_num, adap->clks);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused c3_mipi_adap_runtime_resume(struct device *dev)
> +{
> +	struct adap_device *adap = dev_get_drvdata(dev);
> +
> +	return clk_bulk_prepare_enable(adap->info->clock_num, adap->clks);
> +}
> +
> +static const struct dev_pm_ops c3_mipi_adap_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> +				pm_runtime_force_resume)
> +	SET_RUNTIME_PM_OPS(c3_mipi_adap_runtime_suspend,
> +			   c3_mipi_adap_runtime_resume, NULL)
> +};
> +
> +/* Probe/remove & platform driver */
> +
> +static int c3_mipi_adap_subdev_init(struct adap_device *adap)
> +{
> +	struct v4l2_subdev *sd = &adap->sd;
> +	int ret;
> +
> +	v4l2_subdev_init(sd, &c3_mipi_adap_subdev_ops);
> +	sd->owner = THIS_MODULE;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	sd->internal_ops = &c3_mipi_adap_internal_ops;
> +	snprintf(sd->name, sizeof(sd->name), "%s", MIPI_ADAP_SUBDEV_NAME);
> +
> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	sd->entity.ops = &c3_mipi_adap_entity_ops;
> +
> +	sd->dev = adap->dev;
> +	v4l2_set_subdevdata(sd, adap);
> +
> +	adap->pads[MIPI_ADAP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +	adap->pads[MIPI_ADAP_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sd->entity, MIPI_ADAP_PAD_MAX, adap->pads);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret) {
> +		media_entity_cleanup(&sd->entity);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void c3_mipi_adap_subdev_deinit(struct adap_device *adap)
> +{
> +	v4l2_subdev_cleanup(&adap->sd);
> +	media_entity_cleanup(&adap->sd.entity);
> +}
> +
> +/* Subdev notifier register */
> +static int c3_mipi_adap_notify_bound(struct v4l2_async_notifier *notifier,
> +				     struct v4l2_subdev *sd,
> +				     struct v4l2_async_connection *asc)
> +{
> +	struct adap_device *adap = v4l2_get_subdevdata(notifier->sd);
> +	struct media_pad *sink = &adap->sd.entity.pads[MIPI_ADAP_PAD_SINK];
> +	int ret;
> +
> +	ret = media_entity_get_fwnode_pad(&sd->entity,
> +					  sd->fwnode, MEDIA_PAD_FL_SOURCE);
> +	if (ret < 0) {
> +		dev_err(adap->dev, "Failed to find pad for %s\n", sd->name);
> +		return ret;
> +	}
> +
> +	adap->src_sd = sd;
> +	adap->src_sd_pad = ret;
> +
> +	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
> +					       MEDIA_LNK_FL_IMMUTABLE);
> +}
> +
> +static const struct v4l2_async_notifier_operations c3_mipi_adap_notify_ops = {
> +	.bound = c3_mipi_adap_notify_bound,
> +};
> +
> +static int c3_mipi_adap_async_register(struct adap_device *adap)
> +{
> +	struct v4l2_async_connection *asc;
> +	struct fwnode_handle *ep;
> +	int ret;
> +
> +	v4l2_async_subdev_nf_init(&adap->notifier, &adap->sd);
> +
> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(adap->dev), 0, 0,
> +					     FWNODE_GRAPH_ENDPOINT_NEXT);
> +	if (!ep)
> +		return -ENOTCONN;
> +
> +	asc = v4l2_async_nf_add_fwnode_remote(&adap->notifier, ep,
> +					      struct v4l2_async_connection);
> +	if (IS_ERR(asc)) {
> +		ret = PTR_ERR(asc);
> +		goto err_put_handle;
> +	}
> +
> +	adap->notifier.ops = &c3_mipi_adap_notify_ops;
> +	ret = v4l2_async_nf_register(&adap->notifier);
> +	if (ret)
> +		goto err_cleanup_nf;
> +
> +	ret = v4l2_async_register_subdev(&adap->sd);
> +	if (ret)
> +		goto err_unregister_nf;
> +
> +	fwnode_handle_put(ep);
> +
> +	return 0;
> +
> +err_unregister_nf:
> +	v4l2_async_nf_unregister(&adap->notifier);
> +err_cleanup_nf:
> +	v4l2_async_nf_cleanup(&adap->notifier);
> +err_put_handle:
> +	fwnode_handle_put(ep);
> +	return ret;
> +}
> +
> +static void c3_mipi_adap_async_unregister(struct adap_device *adap)
> +{
> +	v4l2_async_unregister_subdev(&adap->sd);
> +	v4l2_async_nf_unregister(&adap->notifier);
> +	v4l2_async_nf_cleanup(&adap->notifier);
> +}
> +
> +static int c3_mipi_adap_ioremap_resource(struct adap_device *adap)
> +{
> +	struct device *dev = adap->dev;
> +	struct platform_device *pdev = to_platform_device(dev);
> +
> +	adap->top = devm_platform_ioremap_resource_byname(pdev, "top");
> +	if (IS_ERR(adap->top))
> +		return PTR_ERR(adap->top);
> +
> +	adap->fd = devm_platform_ioremap_resource_byname(pdev, "fd");
> +	if (IS_ERR(adap->fd))
> +		return PTR_ERR(adap->fd);
> +
> +	adap->rd = devm_platform_ioremap_resource_byname(pdev, "rd");
> +	if (IS_ERR(adap->rd))
> +		return PTR_ERR(adap->rd);
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_adap_configure_clocks(struct adap_device *adap)
> +{
> +	const struct adap_info *info = adap->info;
> +	int ret;
> +	u32 i;
> +
> +	for (i = 0; i < info->clock_num; i++)
> +		adap->clks[i].id = info->clocks[i];
> +
> +	ret = devm_clk_bulk_get(adap->dev, info->clock_num, adap->clks);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < info->clock_num; i++) {
> +		if (!info->clock_rates[i])
> +			continue;
> +		ret = clk_set_rate(adap->clks[i].clk, info->clock_rates[i]);
> +		if (ret) {
> +			dev_err(adap->dev, "Failed to set %s rate %u\n", info->clocks[i],
> +				info->clock_rates[i]);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_mipi_adap_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct adap_device *adap;
> +	int ret;
> +
> +	adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL);
> +	if (!adap)
> +		return -ENOMEM;
> +
> +	adap->info = of_device_get_match_data(dev);
> +	adap->dev = dev;
> +
> +	ret = c3_mipi_adap_ioremap_resource(adap);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
> +
> +	ret = c3_mipi_adap_configure_clocks(adap);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to configure clocks\n");
> +
> +	platform_set_drvdata(pdev, adap);
> +
> +	mutex_init(&adap->lock);
> +	pm_runtime_enable(dev);
> +
> +	ret = c3_mipi_adap_subdev_init(adap);
> +	if (ret < 0)
> +		goto err_disable_runtime_pm;
> +
> +	ret = c3_mipi_adap_async_register(adap);
> +	if (ret < 0)
> +		goto err_deinit_subdev;
> +
> +	return 0;
> +
> +err_deinit_subdev:
> +	c3_mipi_adap_subdev_deinit(adap);
> +err_disable_runtime_pm:
> +	pm_runtime_disable(dev);
> +	mutex_destroy(&adap->lock);
> +	return ret;
> +};
> +
> +static void c3_mipi_adap_remove(struct platform_device *pdev)
> +{
> +	struct adap_device *adap = platform_get_drvdata(pdev);
> +
> +	c3_mipi_adap_async_unregister(adap);
> +	c3_mipi_adap_subdev_deinit(adap);
> +
> +	pm_runtime_disable(&pdev->dev);
> +	mutex_destroy(&adap->lock);
> +};
> +
> +static const struct adap_info c3_mipi_adap_info = {
> +	.clocks = {"vapb", "isp0"},
> +	.clock_rates = {0, 400000000},
> +	.clock_num = 2
> +};
> +
> +static const struct of_device_id c3_mipi_adap_of_match[] = {
> +	{ .compatible = "amlogic,c3-mipi-adapter",
> +	  .data = &c3_mipi_adap_info },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, c3_mipi_adap_of_match);
> +
> +static struct platform_driver c3_mipi_adap_driver = {
> +	.probe = c3_mipi_adap_probe,
> +	.remove = c3_mipi_adap_remove,
> +	.driver = {
> +		.name = "c3-mipi-adapter",
> +		.of_match_table = c3_mipi_adap_of_match,
> +		.pm = &c3_mipi_adap_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(c3_mipi_adap_driver);
> +
> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
> +MODULE_DESCRIPTION("Amlogic C3 MIPI adapter");
> +MODULE_LICENSE("GPL");
>
> --
> 2.46.1
>
>
>

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

* Re: [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver
  2024-11-05  8:21   ` Jacopo Mondi
@ 2024-11-05 11:36     ` Keke Li
  0 siblings, 0 replies; 37+ messages in thread
From: Keke Li @ 2024-11-05 11:36 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi Jacopo

Thanks very much for your reply.

On 2024/11/5 16:21, Jacopo Mondi wrote:
> [You don't often get email from jacopo.mondi@ideasonboard.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>
> [ EXTERNAL EMAIL ]
>
> Hi Keke, one more thing
>
> On Wed, Sep 18, 2024 at 02:07:13PM +0800, Keke Li via B4 Relay wrote:
>> From: Keke Li <keke.li@amlogic.com>
>>
>> This driver is used to receive mipi data from image sensor.
>>
>> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
>> Signed-off-by: Keke Li <keke.li@amlogic.com>
>> ---
>>   MAINTAINERS                                        |   7 +
>>   drivers/media/platform/amlogic/Kconfig             |   1 +
>>   drivers/media/platform/amlogic/Makefile            |   2 +
>>   .../media/platform/amlogic/c3-mipi-csi2/Kconfig    |  16 +
>>   .../media/platform/amlogic/c3-mipi-csi2/Makefile   |   3 +
>>   .../platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c   | 910 +++++++++++++++++++++
>>   6 files changed, 939 insertions(+)
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 2cdd7cacec86..9e75874a6e69 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -1209,6 +1209,13 @@ F:     Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
>>   F:   drivers/perf/amlogic/
>>   F:   include/soc/amlogic/
>>
>> +AMLOGIC MIPI CSI2 DRIVER
>> +M:   Keke Li <keke.li@amlogic.com>
>> +L:   linux-media@vger.kernel.org
>> +S:   Maintained
>> +F:   Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml
>> +F:   drivers/media/platform/amlogic/c3-mipi-csi2/
>> +
>>   AMPHENOL CHIPCAP 2 HUMIDITY-TEMPERATURE IIO DRIVER
>>   M:   Javier Carrasco <javier.carrasco.cruz@gmail.com>
>>   L:   linux-hwmon@vger.kernel.org
>> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
>> index 5014957404e9..b7c2de14848b 100644
>> --- a/drivers/media/platform/amlogic/Kconfig
>> +++ b/drivers/media/platform/amlogic/Kconfig
>> @@ -2,4 +2,5 @@
>>
>>   comment "Amlogic media platform drivers"
>>
>> +source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>>   source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
>> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
>> index d3cdb8fa4ddb..4f571ce5d13e 100644
>> --- a/drivers/media/platform/amlogic/Makefile
>> +++ b/drivers/media/platform/amlogic/Makefile
>> @@ -1,2 +1,4 @@
>>   # SPDX-License-Identifier: GPL-2.0-only
>> +
>> +obj-y += c3-mipi-csi2/
>>   obj-y += meson-ge2d/
>> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
>> new file mode 100644
>> index 000000000000..0d7b2e203273
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
>> @@ -0,0 +1,16 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +config VIDEO_C3_MIPI_CSI2
>> +     tristate "Amlogic C3 MIPI CSI-2 receiver"
>> +     depends on ARCH_MESON || COMPILE_TEST
>> +     depends on VIDEO_DEV
>> +     depends on OF
>> +     select MEDIA_CONTROLLER
>> +     select V4L2_FWNODE
>> +     select VIDEO_V4L2_SUBDEV_API
>> +     help
>> +       Video4Linux2 driver for Amlogic C3 MIPI CSI-2 receiver.
>> +       C3 MIPI CSI-2 receiver is used to receive MIPI data from
>> +       image sensor.
>> +
>> +       To compile this driver as a module choose m here.
>> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
>> new file mode 100644
>> index 000000000000..cc08fc722bfd
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
>> @@ -0,0 +1,3 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +obj-$(CONFIG_VIDEO_C3_MIPI_CSI2) += c3-mipi-csi2.o
>> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
>> new file mode 100644
>> index 000000000000..6ac60d5b26a8
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
>> @@ -0,0 +1,910 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/cleanup.h>
>> +#include <linux/clk.h>
>> +#include <linux/device.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-async.h>
>> +#include <media/v4l2-common.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-fwnode.h>
>> +#include <media/v4l2-mc.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +/* C3 CSI-2 submodule definition */
>> +enum {
>> +     SUBMD_APHY,
>> +     SUBMD_DPHY,
>> +     SUBMD_HOST,
>> +};
>> +
>> +#define CSI2_SUBMD_MASK             GENMASK(17, 16)
>> +#define CSI2_SUBMD_SHIFT            16
>> +#define CSI2_SUBMD(x)               (((x) & (CSI2_SUBMD_MASK)) >> (CSI2_SUBMD_SHIFT))
>> +#define CSI2_REG_ADDR_MASK          GENMASK(15, 0)
>> +#define CSI2_REG_ADDR(x)            ((x) & (CSI2_REG_ADDR_MASK))
>> +#define CSI2_REG_A(x)               ((SUBMD_APHY << CSI2_SUBMD_SHIFT) | (x))
>> +#define CSI2_REG_D(x)               ((SUBMD_DPHY << CSI2_SUBMD_SHIFT) | (x))
>> +#define CSI2_REG_H(x)               ((SUBMD_HOST << CSI2_SUBMD_SHIFT) | (x))
>> +
>> +#define MIPI_CSI2_CLOCK_NUM_MAX     3
>> +#define MIPI_CSI2_SUBDEV_NAME       "mipi-csi2"
>> +
>> +/* C3 CSI-2 APHY register */
>> +#define MIPI_CSI_2M_PHY2_CNTL1      CSI2_REG_A(0x44)
>> +#define MIPI_APHY_NORMAL_CNTL1      0x3f425C00
>> +
>> +#define MIPI_CSI_2M_PHY2_CNTL2      CSI2_REG_A(0x48)
>> +#define MIPI_APHY_4LANES_CNTL2      0x033a0000
>> +#define MIPI_APHY_NORMAL_CNTL2      0x333a0000
>> +
>> +#define MIPI_CSI_2M_PHY2_CNTL3      CSI2_REG_A(0x4c)
>> +#define MIPI_APHY_2LANES_CNTL3      0x03800000
>> +
>> +/* C3 CSI-2 DPHY register */
>> +#define MIPI_PHY_CTRL                    CSI2_REG_D(0x00)
>> +#define MIPI_DPHY_LANES_ENABLE      0x0
>> +
>> +#define MIPI_PHY_CLK_LANE_CTRL           CSI2_REG_D(0x04)
>> +#define MIPI_DPHY_CLK_CONTINUE_MODE 0x3d8
> I was checking the registers settings, and I've noticed the values
> used to configure the interface group together settings from different
> register bitfields.
>
> I think to allow the driver to be easily consumable and extensible,
> each bit field should be described by its own macro.
>
> Instead of defining a magic value like
>
> #define MIPI_DPHY_CLK_CONTINUE_MODE 0x3d8
>
> The single register bitfield should be described
>
> #define MIPI_PHY_CLK_LANE_CTRL                  CSI2_REG_D(0x04)
> #define MIPI_PHY_CLK_LANE_CTRL_HS_RX_EN         BIT(9)
> #define MIPI_PHY_CLK_LANE_CTRL_END_EN           BIT(8)
> #define MIPI_PHY_CLK_LANE_CTRL_LPEN_DIS         BIT(7)
> #define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_EN     BIT(6)
> #define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS     (0 << 3)
> #define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_2   (1 << 3)
> #define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_4   (2 << 3)
> #define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_8   (3 << 3)
> #define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_16  (4 << 3)
> #define MIPI_PHY_CLK_LANE_CTRL_FORCE_ULPS_EXIT  BIT(1)
> #define MIPI_PHY_CLK_LANE_CTRL_FORCE_ULPS_ENTER BIT(0)
>
> and you configure it with
>
>          c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL,
>                            MIPI_PHY_CLK_LANE_CTRL_HS_RX_EN |
>                            MIPI_PHY_CLK_LANE_CTRL_END_EN |
>                            MIPI_PHY_CLK_LANE_CTRL_LPEN_DIS |
>                            MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_EN |
>                            MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_2);
>
> Otherwise iy you do
>
>          c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL, MIPI_DPHY_CLK_CONTINUE_MODE);
>
> if MIPI_DPHY_CLK_CONTINUE_MODE has to be made configurable for
> whatever reason, it's hard to untangle.
>
> The above suggestion only applies to register where it makes sense to
> describe the single fields of course.


Will  solve this issue with your suggestion.

>> +
>> +#define MIPI_PHY_DATA_LANE_CTRL     CSI2_REG_D(0x08)
>> +#define MIPI_DPHY_LANE_CTRL_DISABLE 0x0
>> +
>> +#define MIPI_PHY_DATA_LANE_CTRL1    CSI2_REG_D(0x0c)
>> +#define MIPI_DPHY_INSERT_ERRESC     BIT(0)
>> +#define MIPI_DPHY_HS_SYNC_CHECK     BIT(1)
>> +#define MIPI_DPHY_FIVE_HS_PIPE      GENMASK(6, 2)
>> +#define MIPI_DPHY_FIVE_HS_PIPE_SHIFT           2
>> +#define MIPI_DPHY_DATA_PIPE_SELECT  GENMASK(9, 7)
>> +#define MIPI_DPHY_DATA_PIPE_SELECT_SHIFT       7
> In example, this is done right!
OK!
>> +
>> +#define MIPI_PHY_TCLK_MISS       CSI2_REG_D(0x10)
>> +#define MIPI_DPHY_CLK_MISS          0x9
> and here you're just programming a counter, so it's of course fine to
> have the raw number.
>
Will use the raw number.
>> +
>> +#define MIPI_PHY_TCLK_SETTLE     CSI2_REG_D(0x14)
>> +#define MIPI_DPHY_CLK_SETTLE        0x1F
> nit: while at it, use small caps for hex as you're using them in most places
>
> Thanks
>    j


Will use small caps.

>> +
>> +#define MIPI_PHY_THS_EXIT        CSI2_REG_D(0x18)
>> +#define MIPI_DPHY_HS_EXIT           0x8
>> +
>> +#define MIPI_PHY_THS_SKIP        CSI2_REG_D(0x1c)
>> +#define MIPI_DPHY_HS_SKIP           0xa
>> +
>> +#define MIPI_PHY_THS_SETTLE      CSI2_REG_D(0x20)
>> +#define MIPI_PHY_TINIT                   CSI2_REG_D(0x24)
>> +#define MIPI_DPHY_INIT_CYCLES       0x4e20
>> +
>> +#define MIPI_PHY_TULPS_C         CSI2_REG_D(0x28)
>> +#define MIPI_DPHY_ULPS_CHECK_CYCLES 0x1000
>> +
>> +#define MIPI_PHY_TULPS_S         CSI2_REG_D(0x2c)
>> +#define MIPI_DPHY_ULPS_START_CYCLES 0x100
>> +
>> +#define MIPI_PHY_TMBIAS             CSI2_REG_D(0x30)
>> +#define MIPI_DPHY_MBIAS_CYCLES      0x100
>> +
>> +#define MIPI_PHY_TLP_EN_W           CSI2_REG_D(0x34)
>> +#define MIPI_DPHY_ULPS_STOP_CYCLES  0xC
>> +
>> +#define MIPI_PHY_TLPOK                   CSI2_REG_D(0x38)
>> +#define MIPI_DPHY_POWER_UP_CYCLES   0x100
>> +
>> +#define MIPI_PHY_TWD_INIT        CSI2_REG_D(0x3c)
>> +#define MIPI_DPHY_INIT_WATCH_DOG    0x400000
>> +
>> +#define MIPI_PHY_TWD_HS             CSI2_REG_D(0x40)
>> +#define MIPI_DPHY_HS_WATCH_DOG      0x400000
>> +
>> +#define MIPI_PHY_MUX_CTRL0       CSI2_REG_D(0x284)
>> +#define MIPI_DPHY_LANE3_SELECT      GENMASK(3, 0)
>> +#define MIPI_DPHY_LANE2_SELECT      GENMASK(7, 4)
>> +#define MIPI_DPHY_LANE2_SELECT_SHIFT           4
>> +#define MIPI_DPHY_LANE1_SELECT      GENMASK(11, 8)
>> +#define MIPI_DPHY_LANE1_SELECT_SHIFT            8
>> +#define MIPI_DPHY_LANE0_SELECT      GENMASK(14, 12)
>> +
>> +#define MIPI_PHY_MUX_CTRL1       CSI2_REG_D(0x288)
>> +#define MIPI_DPHY_LANE3_CTRL_SIGNAL GENMASK(3, 0)
>> +#define MIPI_DPHY_LANE2_CTRL_SIGNAL GENMASK(7, 4)
>> +#define MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT      4
>> +#define MIPI_DPHY_LANE1_CTRL_SIGNAL GENMASK(11, 8)
>> +#define MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT       8
>> +#define MIPI_DPHY_LANE0_CTRL_SIGNAL GENMASK(14, 12)
>> +#define MIPI_DPHY_CLK_SELECT        BIT(17)
>> +
>> +/* C3 CSI-2 HOST register */
>> +#define CSI2_HOST_N_LANES           CSI2_REG_H(0x04)
>> +#define CSI2_HOST_CSI2_RESETN       CSI2_REG_H(0x10)
>> +#define CSI2_HOST_RESETN_DEFAULT    0x0
>> +#define CSI2_HOST_RESETN_RST_VALUE  BIT(0)
>> +
>> +#define CSI2_HOST_MASK1             CSI2_REG_H(0x28)
>> +#define CSI2_HOST_ERROR_MASK1       GENMASK(28, 0)
>> +
>> +#define MIPI_CSI2_MAX_WIDTH         2888
>> +#define MIPI_CSI2_MIN_WIDTH         160
>> +#define MIPI_CSI2_MAX_HEIGHT        2240
>> +#define MIPI_CSI2_MIN_HEIGHT        120
>> +#define MIPI_CSI2_DEFAULT_WIDTH     1920
>> +#define MIPI_CSI2_DEFAULT_HEIGHT    1080
>> +#define MIPI_CSI2_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
>> +
>> +/* C3 CSI-2 pad list */
>> +enum {
>> +     MIPI_CSI2_PAD_SINK,
>> +     MIPI_CSI2_PAD_SRC,
>> +     MIPI_CSI2_PAD_MAX
>> +};
>> +
>> +/**
>> + * struct csi_info - MIPI CSI2 information
>> + *
>> + * @clocks: array of MIPI CSI2 clock names
>> + * @clock_rates: array of MIPI CSI2 clock rate
>> + * @clock_num: actual clock number
>> + */
>> +struct csi_info {
>> +     char *clocks[MIPI_CSI2_CLOCK_NUM_MAX];
>> +     u32 clock_rates[MIPI_CSI2_CLOCK_NUM_MAX];
>> +     u32 clock_num;
>> +};
>> +
>> +/**
>> + * struct csi_device - MIPI CSI2 platform device
>> + *
>> + * @dev: pointer to the struct device
>> + * @aphy: MIPI CSI2 aphy register address
>> + * @dphy: MIPI CSI2 dphy register address
>> + * @host: MIPI CSI2 host register address
>> + * @clks: array of MIPI CSI2 clocks
>> + * @sd: MIPI CSI2 sub-device
>> + * @pads: MIPI CSI2 sub-device pads
>> + * @notifier: notifier to register on the v4l2-async API
>> + * @src_sd: source sub-device
>> + * @bus: MIPI CSI2 bus information
>> + * @src_sd_pad: source sub-device pad
>> + * @lock: protect MIPI CSI2 device
>> + * @info: version-specific MIPI CSI2 information
>> + */
>> +struct csi_device {
>> +     struct device *dev;
>> +     void __iomem *aphy;
>> +     void __iomem *dphy;
>> +     void __iomem *host;
>> +     struct clk_bulk_data clks[MIPI_CSI2_CLOCK_NUM_MAX];
>> +
>> +     struct v4l2_subdev sd;
>> +     struct media_pad pads[MIPI_CSI2_PAD_MAX];
>> +     struct v4l2_async_notifier notifier;
>> +     struct v4l2_subdev *src_sd;
>> +     struct v4l2_mbus_config_mipi_csi2 bus;
>> +
>> +     u16 src_sd_pad;
>> +     struct mutex lock; /* Protect csi device */
>> +     const struct csi_info *info;
>> +};
>> +
>> +static const u32 c3_mipi_csi_formats[] = {
>> +     MEDIA_BUS_FMT_SBGGR10_1X10,
>> +     MEDIA_BUS_FMT_SGBRG10_1X10,
>> +     MEDIA_BUS_FMT_SGRBG10_1X10,
>> +     MEDIA_BUS_FMT_SRGGB10_1X10,
>> +     MEDIA_BUS_FMT_SBGGR12_1X12,
>> +     MEDIA_BUS_FMT_SGBRG12_1X12,
>> +     MEDIA_BUS_FMT_SGRBG12_1X12,
>> +     MEDIA_BUS_FMT_SRGGB12_1X12,
>> +};
>> +
>> +/* Hardware configuration */
>> +
>> +static void c3_mipi_csi_write(struct csi_device *csi, u32 reg, u32 val)
>> +{
>> +     void __iomem *addr;
>> +
>> +     switch (CSI2_SUBMD(reg)) {
>> +     case SUBMD_APHY:
>> +             addr = csi->aphy + CSI2_REG_ADDR(reg);
>> +             break;
>> +     case SUBMD_DPHY:
>> +             addr = csi->dphy + CSI2_REG_ADDR(reg);
>> +             break;
>> +     case SUBMD_HOST:
>> +             addr = csi->host + CSI2_REG_ADDR(reg);
>> +             break;
>> +     default:
>> +             dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
>> +             return;
>> +     }
>> +
>> +     writel(val, addr);
>> +}
>> +
>> +static void c3_mipi_csi_update_bits(struct csi_device *csi, u32 reg,
>> +                                 u32 mask, u32 val)
>> +{
>> +     void __iomem *addr;
>> +     u32 orig, tmp;
>> +
>> +     switch (CSI2_SUBMD(reg)) {
>> +     case SUBMD_APHY:
>> +             addr = csi->aphy + CSI2_REG_ADDR(reg);
>> +             break;
>> +     case SUBMD_DPHY:
>> +             addr = csi->dphy + CSI2_REG_ADDR(reg);
>> +             break;
>> +     case SUBMD_HOST:
>> +             addr = csi->host + CSI2_REG_ADDR(reg);
>> +             break;
>> +     default:
>> +             dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
>> +             return;
>> +     }
>> +
>> +     orig = readl(addr);
>> +     tmp = orig & ~mask;
>> +     tmp |= val & mask;
>> +
>> +     if (tmp != orig)
>> +             writel(tmp, addr);
>> +}
>> +
>> +static void c3_mipi_csi_cfg_aphy(struct csi_device *csi, u32 lanes)
>> +{
>> +     c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL1, MIPI_APHY_NORMAL_CNTL1);
>> +
>> +     if (lanes == 4)
>> +             c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_4LANES_CNTL2);
>> +     else
>> +             c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_NORMAL_CNTL2);
>> +
>> +     if (lanes == 2)
>> +             c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL3, MIPI_APHY_2LANES_CNTL3);
>> +}
>> +
>> +static void c3_mipi_csi_2lanes_setting(struct csi_device *csi)
>> +{
>> +     /* Disable lane 2 and lane 3 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0xf);
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
>> +                             0xf << MIPI_DPHY_LANE2_SELECT_SHIFT);
>> +     /* Select analog data lane 1 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
>> +                             0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
>> +     /* Select analog data lane 0 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
>> +
>> +     /* Disable lane 2 and lane 3 control signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0xf);
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
>> +                             0xf << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
>> +     /* Select lane 1 signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
>> +                             0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
>> +     /* Select lane 0 signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
>> +     /* Select input 0 as clock */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
>> +                             MIPI_DPHY_CLK_SELECT);
>> +}
>> +
>> +static void c3_mipi_csi_4lanes_setting(struct csi_device *csi)
>> +{
>> +     /* Select analog data lane 3 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0x3);
>> +     /* Select analog data lane 2 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
>> +                             0x2 << MIPI_DPHY_LANE2_SELECT_SHIFT);
>> +     /* Select analog data lane 1 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
>> +                             0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
>> +     /* Select analog data lane 0 */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
>> +
>> +     /* Select lane 3 signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0x3);
>> +     /* Select lane 2 signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
>> +                             0x2 << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
>> +     /* Select lane 1 signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
>> +                             0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
>> +     /* Select lane 0 signal */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
>> +     /* Select input 0 as clock */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
>> +                             MIPI_DPHY_CLK_SELECT);
>> +}
>> +
>> +static void c3_mipi_csi_cfg_dphy(struct csi_device *csi, u32 lanes, s64 rate)
>> +{
>> +     u32 val;
>> +     u32 settle;
>> +
>> +     /* Calculate the high speed settle */
>> +     val = DIV_ROUND_UP(1000000000, rate);
>> +     settle = (16 * val + 230) / 10;
>> +
>> +     c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL, MIPI_DPHY_CLK_CONTINUE_MODE);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TCLK_MISS, MIPI_DPHY_CLK_MISS);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TCLK_SETTLE, MIPI_DPHY_CLK_SETTLE);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_THS_EXIT, MIPI_DPHY_HS_EXIT);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_THS_SKIP, MIPI_DPHY_HS_SKIP);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_THS_SETTLE, settle);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TINIT, MIPI_DPHY_INIT_CYCLES);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TMBIAS, MIPI_DPHY_MBIAS_CYCLES);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TULPS_C, MIPI_DPHY_ULPS_CHECK_CYCLES);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TULPS_S, MIPI_DPHY_ULPS_START_CYCLES);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TLP_EN_W, MIPI_DPHY_ULPS_STOP_CYCLES);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TLPOK, MIPI_DPHY_POWER_UP_CYCLES);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TWD_INIT, MIPI_DPHY_INIT_WATCH_DOG);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_TWD_HS, MIPI_DPHY_HS_WATCH_DOG);
>> +     c3_mipi_csi_write(csi, MIPI_PHY_DATA_LANE_CTRL, MIPI_DPHY_LANE_CTRL_DISABLE);
>> +
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_INSERT_ERRESC,
>> +                             MIPI_DPHY_INSERT_ERRESC);
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_HS_SYNC_CHECK,
>> +                             MIPI_DPHY_HS_SYNC_CHECK);
>> +     /*
>> +      * Set 5 pipe lines to the same high speed.
>> +      * Each bit for one pipe line.
>> +      */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_FIVE_HS_PIPE,
>> +                             0x1f << MIPI_DPHY_FIVE_HS_PIPE_SHIFT);
>> +
>> +     /* Output data with pipe line data. */
>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_DATA_PIPE_SELECT,
>> +                             0x3 << MIPI_DPHY_DATA_PIPE_SELECT_SHIFT);
>> +     if (lanes == 2)
>> +             c3_mipi_csi_2lanes_setting(csi);
>> +     else
>> +             c3_mipi_csi_4lanes_setting(csi);
>> +
>> +     /* Enable digital data and clock lanes */
>> +     c3_mipi_csi_write(csi, MIPI_PHY_CTRL, MIPI_DPHY_LANES_ENABLE);
>> +}
>> +
>> +static void c3_mipi_csi_cfg_host(struct csi_device *csi, u32 lanes)
>> +{
>> +     /* Reset CSI-2 controller output */
>> +     c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_DEFAULT);
>> +     c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_RST_VALUE);
>> +
>> +     /* Set data lane number */
>> +     c3_mipi_csi_write(csi, CSI2_HOST_N_LANES, lanes - 1);
>> +
>> +     /* Enable error mask */
>> +     c3_mipi_csi_write(csi, CSI2_HOST_MASK1, CSI2_HOST_ERROR_MASK1);
>> +}
>> +
>> +static int c3_mipi_csi_start_stream(struct csi_device *csi)
>> +{
>> +     s64 link_freq;
>> +     s64 lane_rate;
>> +
>> +     link_freq = v4l2_get_link_freq(csi->src_sd->ctrl_handler, 0, 0);
>> +     if (link_freq < 0) {
>> +             dev_err(csi->dev, "Unable to obtain link frequency: %lld\n", link_freq);
>> +             return link_freq;
>> +     }
>> +
>> +     lane_rate = link_freq * 2;
>> +     if (lane_rate > 1500000000)
>> +             return -EINVAL;
>> +
>> +     c3_mipi_csi_cfg_aphy(csi, csi->bus.num_data_lanes);
>> +     c3_mipi_csi_cfg_dphy(csi, csi->bus.num_data_lanes, lane_rate);
>> +     c3_mipi_csi_cfg_host(csi, csi->bus.num_data_lanes);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_enable_streams(struct v4l2_subdev *sd,
>> +                                   struct v4l2_subdev_state *state,
>> +                                   u32 pad, u64 streams_mask)
>> +{
>> +     struct csi_device *csi = v4l2_get_subdevdata(sd);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     guard(mutex)(&csi->lock);
>> +
>> +     pm_runtime_resume_and_get(csi->dev);
>> +
>> +     c3_mipi_csi_start_stream(csi);
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +                                                    MIPI_CSI2_PAD_SINK,
>> +                                                    &streams_mask);
>> +     ret = v4l2_subdev_enable_streams(csi->src_sd,
>> +                                      csi->src_sd_pad,
>> +                                      sink_streams);
>> +     if (ret) {
>> +             pm_runtime_put(csi->dev);
>> +             return ret;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_disable_streams(struct v4l2_subdev *sd,
>> +                                    struct v4l2_subdev_state *state,
>> +                                    u32 pad, u64 streams_mask)
>> +{
>> +     struct csi_device *csi = v4l2_get_subdevdata(sd);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     guard(mutex)(&csi->lock);
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +                                                    MIPI_CSI2_PAD_SINK,
>> +                                                    &streams_mask);
>> +     ret = v4l2_subdev_disable_streams(csi->src_sd,
>> +                                       csi->src_sd_pad,
>> +                                       sink_streams);
>> +     if (ret)
>> +             dev_err(csi->dev, "Failed to disable %s\n", csi->src_sd->name);
>> +
>> +     pm_runtime_put(csi->dev);
>> +
>> +     return ret;
>> +}
>> +
>> +static int c3_mipi_csi_cfg_routing(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state,
>> +                                struct v4l2_subdev_krouting *routing)
>> +{
>> +     static const struct v4l2_mbus_framefmt format = {
>> +             .width = MIPI_CSI2_DEFAULT_WIDTH,
>> +             .height = MIPI_CSI2_DEFAULT_HEIGHT,
>> +             .code = MIPI_CSI2_DEFAULT_FMT,
>> +             .field = V4L2_FIELD_NONE,
>> +             .colorspace = V4L2_COLORSPACE_RAW,
>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>> +             .xfer_func = V4L2_XFER_FUNC_NONE,
>> +     };
>> +     int ret;
>> +
>> +     ret = v4l2_subdev_routing_validate(sd, routing,
>> +                                        V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_init_routing(struct v4l2_subdev *sd,
>> +                                 struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_subdev_route routes;
>> +     struct v4l2_subdev_krouting routing;
>> +
>> +     routes.sink_pad = MIPI_CSI2_PAD_SINK;
>> +     routes.sink_stream = 0;
>> +     routes.source_pad = MIPI_CSI2_PAD_SRC;
>> +     routes.source_stream = 0;
>> +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>> +
>> +     routing.num_routes = 1;
>> +     routing.routes = &routes;
>> +
>> +     return c3_mipi_csi_cfg_routing(sd, state, &routing);
>> +}
>> +
>> +static int c3_mipi_csi_set_routing(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state,
>> +                                enum v4l2_subdev_format_whence which,
>> +                                struct v4l2_subdev_krouting *routing)
>> +{
>> +     bool is_streaming = v4l2_subdev_is_streaming(sd);
>> +
>> +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>> +             return -EBUSY;
>> +
>> +     return c3_mipi_csi_cfg_routing(sd, state, routing);
>> +}
>> +
>> +static int c3_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
>> +                                   struct v4l2_subdev_state *state,
>> +                                   struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +     switch (code->pad) {
>> +     case MIPI_CSI2_PAD_SINK:
>> +             if (code->index >= ARRAY_SIZE(c3_mipi_csi_formats))
>> +                     return -EINVAL;
>> +
>> +             code->code = c3_mipi_csi_formats[code->index];
>> +             break;
>> +     case MIPI_CSI2_PAD_SRC:
>> +             struct v4l2_mbus_framefmt *fmt;
>> +
>> +             if (code->index > 0)
>> +                     return -EINVAL;
>> +
>> +             fmt = v4l2_subdev_state_get_format(state, code->pad);
>> +             code->code = fmt->code;
>> +             break;
>> +     default:
>> +             return -EINVAL;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_set_fmt(struct v4l2_subdev *sd,
>> +                            struct v4l2_subdev_state *state,
>> +                            struct v4l2_subdev_format *format)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +     unsigned int i;
>> +
>> +     if (format->pad != MIPI_CSI2_PAD_SINK)
>> +             return v4l2_subdev_get_fmt(sd, state, format);
>> +
>> +     fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +
>> +     for (i = 0; i < ARRAY_SIZE(c3_mipi_csi_formats); i++)
>> +             if (format->format.code == c3_mipi_csi_formats[i])
>> +                     break;
>> +
>> +     if (i == ARRAY_SIZE(c3_mipi_csi_formats))
>> +             fmt->code = c3_mipi_csi_formats[0];
>> +     else
>> +             fmt->code = c3_mipi_csi_formats[i];
>> +
>> +     fmt->width = clamp_t(u32, format->format.width,
>> +                          MIPI_CSI2_MIN_WIDTH, MIPI_CSI2_MAX_WIDTH);
>> +     fmt->height = clamp_t(u32, format->format.height,
>> +                           MIPI_CSI2_MIN_HEIGHT, MIPI_CSI2_MAX_HEIGHT);
>> +
>> +     format->format = *fmt;
>> +
>> +     /* Synchronize the format to source pad */
>> +     fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
>> +     *fmt = format->format;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_init_state(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SINK);
>> +     src_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
>> +
>> +     sink_fmt->width = MIPI_CSI2_DEFAULT_WIDTH;
>> +     sink_fmt->height = MIPI_CSI2_DEFAULT_HEIGHT;
>> +     sink_fmt->field = V4L2_FIELD_NONE;
>> +     sink_fmt->code = MIPI_CSI2_DEFAULT_FMT;
>> +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>> +     sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
>> +     sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
>> +     sink_fmt->quantization =
>> +             V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
>> +                                           sink_fmt->ycbcr_enc);
>> +     *src_fmt = *sink_fmt;
>> +
>> +     return c3_mipi_csi_init_routing(sd, state);
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops c3_mipi_csi_pad_ops = {
>> +     .enum_mbus_code = c3_mipi_csi_enum_mbus_code,
>> +     .get_fmt = v4l2_subdev_get_fmt,
>> +     .set_fmt = c3_mipi_csi_set_fmt,
>> +     .set_routing = c3_mipi_csi_set_routing,
>> +     .enable_streams = c3_mipi_csi_enable_streams,
>> +     .disable_streams = c3_mipi_csi_disable_streams,
>> +};
>> +
>> +static const struct v4l2_subdev_ops c3_mipi_csi_subdev_ops = {
>> +     .pad = &c3_mipi_csi_pad_ops,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops c3_mipi_csi_internal_ops = {
>> +     .init_state = c3_mipi_csi_init_state,
>> +};
>> +
>> +/* Media entity operations */
>> +static const struct media_entity_operations c3_mipi_csi_entity_ops = {
>> +     .link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +/* PM runtime */
>> +
>> +static int __maybe_unused c3_mipi_csi_runtime_suspend(struct device *dev)
>> +{
>> +     struct csi_device *csi = dev_get_drvdata(dev);
>> +
>> +     clk_bulk_disable_unprepare(csi->info->clock_num, csi->clks);
>> +
>> +     return 0;
>> +}
>> +
>> +static int __maybe_unused c3_mipi_csi_runtime_resume(struct device *dev)
>> +{
>> +     struct csi_device *csi = dev_get_drvdata(dev);
>> +
>> +     return clk_bulk_prepare_enable(csi->info->clock_num, csi->clks);
>> +}
>> +
>> +static const struct dev_pm_ops c3_mipi_csi_pm_ops = {
>> +     SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>> +                             pm_runtime_force_resume)
>> +     SET_RUNTIME_PM_OPS(c3_mipi_csi_runtime_suspend,
>> +                        c3_mipi_csi_runtime_resume, NULL)
>> +};
>> +
>> +/* Probe/remove & platform driver */
>> +
>> +static int c3_mipi_csi_subdev_init(struct csi_device *csi)
>> +{
>> +     struct v4l2_subdev *sd = &csi->sd;
>> +     int ret;
>> +
>> +     v4l2_subdev_init(sd, &c3_mipi_csi_subdev_ops);
>> +     sd->owner = THIS_MODULE;
>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +     sd->internal_ops = &c3_mipi_csi_internal_ops;
>> +     snprintf(sd->name, sizeof(sd->name), "%s", MIPI_CSI2_SUBDEV_NAME);
>> +
>> +     sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>> +     sd->entity.ops = &c3_mipi_csi_entity_ops;
>> +
>> +     sd->dev = csi->dev;
>> +     v4l2_set_subdevdata(sd, csi);
>> +
>> +     csi->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>> +     csi->pads[MIPI_CSI2_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
>> +     ret = media_entity_pads_init(&sd->entity, MIPI_CSI2_PAD_MAX, csi->pads);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_init_finalize(sd);
>> +     if (ret) {
>> +             media_entity_cleanup(&sd->entity);
>> +             return ret;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_mipi_csi_subdev_deinit(struct csi_device *csi)
>> +{
>> +     v4l2_subdev_cleanup(&csi->sd);
>> +     media_entity_cleanup(&csi->sd.entity);
>> +}
>> +
>> +/* Subdev notifier register */
>> +static int c3_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier,
>> +                                 struct v4l2_subdev *sd,
>> +                                 struct v4l2_async_connection *asc)
>> +{
>> +     struct csi_device *csi = v4l2_get_subdevdata(notifier->sd);
>> +     struct media_pad *sink = &csi->sd.entity.pads[MIPI_CSI2_PAD_SINK];
>> +     int ret;
>> +
>> +     ret = media_entity_get_fwnode_pad(&sd->entity,
>> +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
>> +     if (ret < 0) {
>> +             dev_err(csi->dev, "Failed to find pad for %s\n", sd->name);
>> +             return ret;
>> +     }
>> +
>> +     csi->src_sd = sd;
>> +     csi->src_sd_pad = ret;
>> +
>> +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
>> +                                            MEDIA_LNK_FL_IMMUTABLE);
>> +}
>> +
>> +static const struct v4l2_async_notifier_operations c3_mipi_csi_notify_ops = {
>> +     .bound = c3_mipi_csi_notify_bound,
>> +};
>> +
>> +static int c3_mipi_csi_async_register(struct csi_device *csi)
>> +{
>> +     struct v4l2_fwnode_endpoint vep = {
>> +             .bus_type = V4L2_MBUS_CSI2_DPHY,
>> +     };
>> +     struct v4l2_async_connection *asc;
>> +     struct fwnode_handle *ep;
>> +     int ret;
>> +
>> +     v4l2_async_subdev_nf_init(&csi->notifier, &csi->sd);
>> +
>> +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0,
>> +                                          FWNODE_GRAPH_ENDPOINT_NEXT);
>> +     if (!ep)
>> +             return -ENOTCONN;
>> +
>> +     ret = v4l2_fwnode_endpoint_parse(ep, &vep);
>> +     if (ret)
>> +             goto err_put_handle;
>> +
>> +     csi->bus = vep.bus.mipi_csi2;
>> +     if (csi->bus.num_data_lanes != 2 && csi->bus.num_data_lanes != 4)
>> +             goto err_put_handle;
>> +
>> +     asc = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep,
>> +                                           struct v4l2_async_connection);
>> +     if (IS_ERR(asc)) {
>> +             ret = PTR_ERR(asc);
>> +             goto err_put_handle;
>> +     }
>> +
>> +     csi->notifier.ops = &c3_mipi_csi_notify_ops;
>> +     ret = v4l2_async_nf_register(&csi->notifier);
>> +     if (ret)
>> +             goto err_cleanup_nf;
>> +
>> +     ret = v4l2_async_register_subdev(&csi->sd);
>> +     if (ret)
>> +             goto err_unregister_nf;
>> +
>> +     fwnode_handle_put(ep);
>> +
>> +     return 0;
>> +
>> +err_unregister_nf:
>> +     v4l2_async_nf_unregister(&csi->notifier);
>> +err_cleanup_nf:
>> +     v4l2_async_nf_cleanup(&csi->notifier);
>> +err_put_handle:
>> +     fwnode_handle_put(ep);
>> +     return ret;
>> +}
>> +
>> +static void c3_mipi_csi_async_unregister(struct csi_device *csi)
>> +{
>> +     v4l2_async_unregister_subdev(&csi->sd);
>> +     v4l2_async_nf_unregister(&csi->notifier);
>> +     v4l2_async_nf_cleanup(&csi->notifier);
>> +}
>> +
>> +static int c3_mipi_csi_ioremap_resource(struct csi_device *csi)
>> +{
>> +     struct device *dev = csi->dev;
>> +     struct platform_device *pdev = to_platform_device(dev);
>> +
>> +     csi->aphy = devm_platform_ioremap_resource_byname(pdev, "aphy");
>> +     if (IS_ERR(csi->aphy))
>> +             return PTR_ERR(csi->aphy);
>> +
>> +     csi->dphy = devm_platform_ioremap_resource_byname(pdev, "dphy");
>> +     if (IS_ERR(csi->dphy))
>> +             return PTR_ERR(csi->dphy);
>> +
>> +     csi->host = devm_platform_ioremap_resource_byname(pdev, "host");
>> +     if (IS_ERR(csi->host))
>> +             return PTR_ERR(csi->host);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_configure_clocks(struct csi_device *csi)
>> +{
>> +     const struct csi_info *info = csi->info;
>> +     int ret;
>> +     u32 i;
>> +
>> +     for (i = 0; i < info->clock_num; i++)
>> +             csi->clks[i].id = info->clocks[i];
>> +
>> +     ret = devm_clk_bulk_get(csi->dev, info->clock_num, csi->clks);
>> +     if (ret)
>> +             return ret;
>> +
>> +     for (i = 0; i < info->clock_num; i++) {
>> +             if (!info->clock_rates[i])
>> +                     continue;
>> +             ret = clk_set_rate(csi->clks[i].clk, info->clock_rates[i]);
>> +             if (ret) {
>> +                     dev_err(csi->dev, "Failed to set %s rate %u\n", info->clocks[i],
>> +                             info->clock_rates[i]);
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_csi_probe(struct platform_device *pdev)
>> +{
>> +     struct device *dev = &pdev->dev;
>> +     struct csi_device *csi;
>> +     int ret;
>> +
>> +     csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
>> +     if (!csi)
>> +             return -ENOMEM;
>> +
>> +     csi->info = of_device_get_match_data(dev);
>> +     csi->dev = dev;
>> +
>> +     ret = c3_mipi_csi_ioremap_resource(csi);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
>> +
>> +     ret = c3_mipi_csi_configure_clocks(csi);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
>> +
>> +     platform_set_drvdata(pdev, csi);
>> +
>> +     mutex_init(&csi->lock);
>> +     pm_runtime_enable(dev);
>> +
>> +     ret = c3_mipi_csi_subdev_init(csi);
>> +     if (ret)
>> +             goto err_disable_runtime_pm;
>> +
>> +     ret = c3_mipi_csi_async_register(csi);
>> +     if (ret)
>> +             goto err_deinit_subdev;
>> +
>> +     return 0;
>> +
>> +err_deinit_subdev:
>> +     c3_mipi_csi_subdev_deinit(csi);
>> +err_disable_runtime_pm:
>> +     pm_runtime_disable(dev);
>> +     mutex_destroy(&csi->lock);
>> +     return ret;
>> +};
>> +
>> +static void c3_mipi_csi_remove(struct platform_device *pdev)
>> +{
>> +     struct csi_device *csi = platform_get_drvdata(pdev);
>> +
>> +     c3_mipi_csi_async_unregister(csi);
>> +     c3_mipi_csi_subdev_deinit(csi);
>> +
>> +     pm_runtime_disable(&pdev->dev);
>> +     mutex_destroy(&csi->lock);
>> +};
>> +
>> +static const struct csi_info c3_mipi_csi_info = {
>> +     .clocks = {"vapb", "phy0"},
>> +     .clock_rates = {0, 200000000},
>> +     .clock_num = 2
>> +};
>> +
>> +static const struct of_device_id c3_mipi_csi_of_match[] = {
>> +     { .compatible = "amlogic,c3-mipi-csi2",
>> +       .data = &c3_mipi_csi_info,
>> +     },
>> +     { },
>> +};
>> +MODULE_DEVICE_TABLE(of, c3_mipi_csi_of_match);
>> +
>> +static struct platform_driver c3_mipi_csi_driver = {
>> +     .probe = c3_mipi_csi_probe,
>> +     .remove = c3_mipi_csi_remove,
>> +     .driver = {
>> +             .name = "c3-mipi-csi2",
>> +             .of_match_table = c3_mipi_csi_of_match,
>> +             .pm = &c3_mipi_csi_pm_ops,
>> +     },
>> +};
>> +
>> +module_platform_driver(c3_mipi_csi_driver);
>> +
>> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
>> +MODULE_DESCRIPTION("Amlogic C3 MIPI CSI-2 receiver");
>> +MODULE_LICENSE("GPL");
>>
>> --
>> 2.46.1
>>
>>
>>

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

* Re: [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver
  2024-11-05 11:21     ` Keke Li
@ 2024-11-05 11:39       ` Jacopo Mondi
  2024-11-05 11:58         ` Keke Li
  2024-11-05 12:08         ` Tomi Valkeinen
  0 siblings, 2 replies; 37+ messages in thread
From: Jacopo Mondi @ 2024-11-05 11:39 UTC (permalink / raw)
  To: Keke Li, Tomi Valkeinen
  Cc: Jacopo Mondi, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-media, devicetree,
	linux-kernel, kieran.bingham, laurent.pinchart, dan.scally,
	tomi.valkeinen@ideasonboard.com

Hi Keke

On Tue, Nov 05, 2024 at 07:21:36PM +0800, Keke Li wrote:
> Hi Jacopo
>
> Thanks very much for your reply.
>
> On 2024/11/5 01:50, Jacopo Mondi wrote:
> > [You don't often get email from jacopo.mondi@ideasonboard.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
> >
> > [ EXTERNAL EMAIL ]
> >
> > Hi Keke
> >     sorry for the late feedback, hope you're still interested in
> > upstreaming this driver
> >
> > On Wed, Sep 18, 2024 at 02:07:13PM +0800, Keke Li via B4 Relay wrote:
> > > From: Keke Li <keke.li@amlogic.com>
> > >
> > > This driver is used to receive mipi data from image sensor.
> > >
> > > Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> > > Signed-off-by: Keke Li <keke.li@amlogic.com>
> > > ---
> > >   MAINTAINERS                                        |   7 +
> > >   drivers/media/platform/amlogic/Kconfig             |   1 +
> > >   drivers/media/platform/amlogic/Makefile            |   2 +
> > >   .../media/platform/amlogic/c3-mipi-csi2/Kconfig    |  16 +
> > >   .../media/platform/amlogic/c3-mipi-csi2/Makefile   |   3 +
> > >   .../platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c   | 910 +++++++++++++++++++++
> > >   6 files changed, 939 insertions(+)
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index 2cdd7cacec86..9e75874a6e69 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -1209,6 +1209,13 @@ F:     Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
> > >   F:   drivers/perf/amlogic/
> > >   F:   include/soc/amlogic/
> > >
> > > +AMLOGIC MIPI CSI2 DRIVER
> > > +M:   Keke Li <keke.li@amlogic.com>
> > > +L:   linux-media@vger.kernel.org
> > > +S:   Maintained
> > > +F:   Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml
> > > +F:   drivers/media/platform/amlogic/c3-mipi-csi2/
> > > +
> > >   AMPHENOL CHIPCAP 2 HUMIDITY-TEMPERATURE IIO DRIVER
> > >   M:   Javier Carrasco <javier.carrasco.cruz@gmail.com>
> > >   L:   linux-hwmon@vger.kernel.org
> > > diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
> > > index 5014957404e9..b7c2de14848b 100644
> > > --- a/drivers/media/platform/amlogic/Kconfig
> > > +++ b/drivers/media/platform/amlogic/Kconfig
> > > @@ -2,4 +2,5 @@
> > >
> > >   comment "Amlogic media platform drivers"
> > >
> > > +source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
> > >   source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
> > > diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
> > > index d3cdb8fa4ddb..4f571ce5d13e 100644
> > > --- a/drivers/media/platform/amlogic/Makefile
> > > +++ b/drivers/media/platform/amlogic/Makefile
> > > @@ -1,2 +1,4 @@
> > >   # SPDX-License-Identifier: GPL-2.0-only
> > > +
> > > +obj-y += c3-mipi-csi2/
> > >   obj-y += meson-ge2d/
> > > diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
> > > new file mode 100644
> > > index 000000000000..0d7b2e203273
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
> > > @@ -0,0 +1,16 @@
> > > +# SPDX-License-Identifier: GPL-2.0-only
> > > +
> > > +config VIDEO_C3_MIPI_CSI2
> > > +     tristate "Amlogic C3 MIPI CSI-2 receiver"
> > > +     depends on ARCH_MESON || COMPILE_TEST
> > > +     depends on VIDEO_DEV
> > > +     depends on OF
> > > +     select MEDIA_CONTROLLER
> > > +     select V4L2_FWNODE
> > > +     select VIDEO_V4L2_SUBDEV_API
> > > +     help
> > > +       Video4Linux2 driver for Amlogic C3 MIPI CSI-2 receiver.
> > > +       C3 MIPI CSI-2 receiver is used to receive MIPI data from
> > > +       image sensor.
> > > +
> > > +       To compile this driver as a module choose m here.
> > > diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
> > > new file mode 100644
> > > index 000000000000..cc08fc722bfd
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
> > > @@ -0,0 +1,3 @@
> > > +# SPDX-License-Identifier: GPL-2.0-only
> > > +
> > > +obj-$(CONFIG_VIDEO_C3_MIPI_CSI2) += c3-mipi-csi2.o
> > > diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
> > > new file mode 100644
> > > index 000000000000..6ac60d5b26a8
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
> > > @@ -0,0 +1,910 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#include <linux/cleanup.h>
> > > +#include <linux/clk.h>
> > > +#include <linux/device.h>
> > > +#include <linux/module.h>
> > > +#include <linux/mutex.h>
> > > +#include <linux/platform_device.h>
> > > +#include <linux/pm_runtime.h>
> > > +
> > > +#include <media/v4l2-async.h>
> > > +#include <media/v4l2-common.h>
> > > +#include <media/v4l2-device.h>
> > > +#include <media/v4l2-fwnode.h>
> > > +#include <media/v4l2-mc.h>
> > > +#include <media/v4l2-subdev.h>
> > > +
> > > +/* C3 CSI-2 submodule definition */
> > > +enum {
> > > +     SUBMD_APHY,
> > > +     SUBMD_DPHY,
> > > +     SUBMD_HOST,
> > > +};
> > > +
> > > +#define CSI2_SUBMD_MASK             GENMASK(17, 16)
> > > +#define CSI2_SUBMD_SHIFT            16
> > > +#define CSI2_SUBMD(x)               (((x) & (CSI2_SUBMD_MASK)) >> (CSI2_SUBMD_SHIFT))
> > > +#define CSI2_REG_ADDR_MASK          GENMASK(15, 0)
> > > +#define CSI2_REG_ADDR(x)            ((x) & (CSI2_REG_ADDR_MASK))
> > > +#define CSI2_REG_A(x)               ((SUBMD_APHY << CSI2_SUBMD_SHIFT) | (x))
> > > +#define CSI2_REG_D(x)               ((SUBMD_DPHY << CSI2_SUBMD_SHIFT) | (x))
> > > +#define CSI2_REG_H(x)               ((SUBMD_HOST << CSI2_SUBMD_SHIFT) | (x))
> > > +
> > > +#define MIPI_CSI2_CLOCK_NUM_MAX     3
> > > +#define MIPI_CSI2_SUBDEV_NAME       "mipi-csi2"
> > Isn't the name too generic ? Should it report at least "c3-mipi-csi2"
> > ?
> Will modify the name with  "C3_MIPI_CSI2".
> > > +
> > > +/* C3 CSI-2 APHY register */
> > > +#define MIPI_CSI_2M_PHY2_CNTL1      CSI2_REG_A(0x44)
> > > +#define MIPI_APHY_NORMAL_CNTL1      0x3f425C00
> > All other hex addresses use small capitals for letters
> OK, will use small capitals for letters.
> > > +
> > > +#define MIPI_CSI_2M_PHY2_CNTL2      CSI2_REG_A(0x48)
> > > +#define MIPI_APHY_4LANES_CNTL2      0x033a0000
> > > +#define MIPI_APHY_NORMAL_CNTL2      0x333a0000
> > > +
> > > +#define MIPI_CSI_2M_PHY2_CNTL3      CSI2_REG_A(0x4c)
> > > +#define MIPI_APHY_2LANES_CNTL3      0x03800000
> > > +
> > > +/* C3 CSI-2 DPHY register */
> > > +#define MIPI_PHY_CTRL                    CSI2_REG_D(0x00)
> > > +#define MIPI_DPHY_LANES_ENABLE      0x0
> > > +
> > > +#define MIPI_PHY_CLK_LANE_CTRL           CSI2_REG_D(0x04)
> > > +#define MIPI_DPHY_CLK_CONTINUE_MODE 0x3d8
> > > +
> > > +#define MIPI_PHY_DATA_LANE_CTRL     CSI2_REG_D(0x08)
> > > +#define MIPI_DPHY_LANE_CTRL_DISABLE 0x0
> > > +
> > > +#define MIPI_PHY_DATA_LANE_CTRL1    CSI2_REG_D(0x0c)
> > > +#define MIPI_DPHY_INSERT_ERRESC     BIT(0)
> > > +#define MIPI_DPHY_HS_SYNC_CHECK     BIT(1)
> > > +#define MIPI_DPHY_FIVE_HS_PIPE      GENMASK(6, 2)
> > > +#define MIPI_DPHY_FIVE_HS_PIPE_SHIFT           2
> > > +#define MIPI_DPHY_DATA_PIPE_SELECT  GENMASK(9, 7)
> > > +#define MIPI_DPHY_DATA_PIPE_SELECT_SHIFT       7
> > > +
> > > +#define MIPI_PHY_TCLK_MISS       CSI2_REG_D(0x10)
> > > +#define MIPI_DPHY_CLK_MISS          0x9
> > > +
> > > +#define MIPI_PHY_TCLK_SETTLE     CSI2_REG_D(0x14)
> > > +#define MIPI_DPHY_CLK_SETTLE        0x1F
> > > +
> > > +#define MIPI_PHY_THS_EXIT        CSI2_REG_D(0x18)
> > > +#define MIPI_DPHY_HS_EXIT           0x8
> > > +
> > > +#define MIPI_PHY_THS_SKIP        CSI2_REG_D(0x1c)
> > > +#define MIPI_DPHY_HS_SKIP           0xa
> > > +
> > > +#define MIPI_PHY_THS_SETTLE      CSI2_REG_D(0x20)
> > > +#define MIPI_PHY_TINIT                   CSI2_REG_D(0x24)
> > > +#define MIPI_DPHY_INIT_CYCLES       0x4e20
> > > +
> > > +#define MIPI_PHY_TULPS_C         CSI2_REG_D(0x28)
> > > +#define MIPI_DPHY_ULPS_CHECK_CYCLES 0x1000
> > > +
> > > +#define MIPI_PHY_TULPS_S         CSI2_REG_D(0x2c)
> > > +#define MIPI_DPHY_ULPS_START_CYCLES 0x100
> > > +
> > > +#define MIPI_PHY_TMBIAS             CSI2_REG_D(0x30)
> > > +#define MIPI_DPHY_MBIAS_CYCLES      0x100
> > > +
> > > +#define MIPI_PHY_TLP_EN_W           CSI2_REG_D(0x34)
> > > +#define MIPI_DPHY_ULPS_STOP_CYCLES  0xC
> > > +
> > > +#define MIPI_PHY_TLPOK                   CSI2_REG_D(0x38)
> > > +#define MIPI_DPHY_POWER_UP_CYCLES   0x100
> > > +
> > > +#define MIPI_PHY_TWD_INIT        CSI2_REG_D(0x3c)
> > > +#define MIPI_DPHY_INIT_WATCH_DOG    0x400000
> > > +
> > > +#define MIPI_PHY_TWD_HS             CSI2_REG_D(0x40)
> > > +#define MIPI_DPHY_HS_WATCH_DOG      0x400000
> > > +
> > > +#define MIPI_PHY_MUX_CTRL0       CSI2_REG_D(0x284)
> > > +#define MIPI_DPHY_LANE3_SELECT      GENMASK(3, 0)
> > > +#define MIPI_DPHY_LANE2_SELECT      GENMASK(7, 4)
> > > +#define MIPI_DPHY_LANE2_SELECT_SHIFT           4
> > > +#define MIPI_DPHY_LANE1_SELECT      GENMASK(11, 8)
> > > +#define MIPI_DPHY_LANE1_SELECT_SHIFT            8
> > > +#define MIPI_DPHY_LANE0_SELECT      GENMASK(14, 12)
> > > +
> > > +#define MIPI_PHY_MUX_CTRL1       CSI2_REG_D(0x288)
> > > +#define MIPI_DPHY_LANE3_CTRL_SIGNAL GENMASK(3, 0)
> > > +#define MIPI_DPHY_LANE2_CTRL_SIGNAL GENMASK(7, 4)
> > > +#define MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT      4
> > > +#define MIPI_DPHY_LANE1_CTRL_SIGNAL GENMASK(11, 8)
> > > +#define MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT       8
> > > +#define MIPI_DPHY_LANE0_CTRL_SIGNAL GENMASK(14, 12)
> > > +#define MIPI_DPHY_CLK_SELECT        BIT(17)
> > > +
> > > +/* C3 CSI-2 HOST register */
> > > +#define CSI2_HOST_N_LANES           CSI2_REG_H(0x04)
> > > +#define CSI2_HOST_CSI2_RESETN       CSI2_REG_H(0x10)
> > > +#define CSI2_HOST_RESETN_DEFAULT    0x0
> > > +#define CSI2_HOST_RESETN_RST_VALUE  BIT(0)
> > > +
> > > +#define CSI2_HOST_MASK1             CSI2_REG_H(0x28)
> > > +#define CSI2_HOST_ERROR_MASK1       GENMASK(28, 0)
> > > +
> > > +#define MIPI_CSI2_MAX_WIDTH         2888
> > > +#define MIPI_CSI2_MIN_WIDTH         160
> > > +#define MIPI_CSI2_MAX_HEIGHT        2240
> > > +#define MIPI_CSI2_MIN_HEIGHT        120
> > > +#define MIPI_CSI2_DEFAULT_WIDTH     1920
> > > +#define MIPI_CSI2_DEFAULT_HEIGHT    1080
> > > +#define MIPI_CSI2_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
> > > +
> > > +/* C3 CSI-2 pad list */
> > > +enum {
> > > +     MIPI_CSI2_PAD_SINK,
> > > +     MIPI_CSI2_PAD_SRC,
> > > +     MIPI_CSI2_PAD_MAX
> > > +};
> > > +
> > > +/**
> > You don't need to kernel-doc in-driver types and functions.
> > Documentation is always good, but this won't be parsed by kernel-doc
> > (afaiu) so you should drop one * from /**
> >
> Will drop one * from /**
> > > + * struct csi_info - MIPI CSI2 information
> > > + *
> > > + * @clocks: array of MIPI CSI2 clock names
> > > + * @clock_rates: array of MIPI CSI2 clock rate
> > > + * @clock_num: actual clock number
> > > + */
> > > +struct csi_info {
> > > +     char *clocks[MIPI_CSI2_CLOCK_NUM_MAX];
> > > +     u32 clock_rates[MIPI_CSI2_CLOCK_NUM_MAX];
> > > +     u32 clock_num;
> > > +};
> > > +
> > > +/**
> > > + * struct csi_device - MIPI CSI2 platform device
> > > + *
> > > + * @dev: pointer to the struct device
> > > + * @aphy: MIPI CSI2 aphy register address
> > > + * @dphy: MIPI CSI2 dphy register address
> > > + * @host: MIPI CSI2 host register address
> > > + * @clks: array of MIPI CSI2 clocks
> > > + * @sd: MIPI CSI2 sub-device
> > > + * @pads: MIPI CSI2 sub-device pads
> > > + * @notifier: notifier to register on the v4l2-async API
> > > + * @src_sd: source sub-device
> > > + * @bus: MIPI CSI2 bus information
> > > + * @src_sd_pad: source sub-device pad
> > > + * @lock: protect MIPI CSI2 device
> > > + * @info: version-specific MIPI CSI2 information
> > > + */
> > > +struct csi_device {
> > > +     struct device *dev;
> > > +     void __iomem *aphy;
> > > +     void __iomem *dphy;
> > > +     void __iomem *host;
> > > +     struct clk_bulk_data clks[MIPI_CSI2_CLOCK_NUM_MAX];
> > > +
> > > +     struct v4l2_subdev sd;
> > > +     struct media_pad pads[MIPI_CSI2_PAD_MAX];
> > > +     struct v4l2_async_notifier notifier;
> > > +     struct v4l2_subdev *src_sd;
> > > +     struct v4l2_mbus_config_mipi_csi2 bus;
> > > +
> > > +     u16 src_sd_pad;
> > > +     struct mutex lock; /* Protect csi device */
> > All the operations which receive a subdev_state are guaranteed to be locked
> > so you can avoid manually locking in enable/disable streams
> > (and drop #include cleanup.h if you don't use guards in any other
> > place)
> >
> OK, will remove this lock and drop "#include cleanup.h"
> > > +     const struct csi_info *info;
> > > +};
> > > +
> > > +static const u32 c3_mipi_csi_formats[] = {
> > > +     MEDIA_BUS_FMT_SBGGR10_1X10,
> > > +     MEDIA_BUS_FMT_SGBRG10_1X10,
> > > +     MEDIA_BUS_FMT_SGRBG10_1X10,
> > > +     MEDIA_BUS_FMT_SRGGB10_1X10,
> > > +     MEDIA_BUS_FMT_SBGGR12_1X12,
> > > +     MEDIA_BUS_FMT_SGBRG12_1X12,
> > > +     MEDIA_BUS_FMT_SGRBG12_1X12,
> > > +     MEDIA_BUS_FMT_SRGGB12_1X12,
> > > +};
> > > +
> > > +/* Hardware configuration */
> > > +
> > > +static void c3_mipi_csi_write(struct csi_device *csi, u32 reg, u32 val)
> > > +{
> > > +     void __iomem *addr;
> > > +
> > > +     switch (CSI2_SUBMD(reg)) {
> > > +     case SUBMD_APHY:
> > > +             addr = csi->aphy + CSI2_REG_ADDR(reg);
> > > +             break;
> > > +     case SUBMD_DPHY:
> > > +             addr = csi->dphy + CSI2_REG_ADDR(reg);
> > > +             break;
> > > +     case SUBMD_HOST:
> > > +             addr = csi->host + CSI2_REG_ADDR(reg);
> > > +             break;
> > > +     default:
> > > +             dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
> > > +             return;
> > > +     }
> > > +
> > > +     writel(val, addr);
> > > +}
> > > +
> > > +static void c3_mipi_csi_update_bits(struct csi_device *csi, u32 reg,
> > > +                                 u32 mask, u32 val)
> > > +{
> > > +     void __iomem *addr;
> > > +     u32 orig, tmp;
> > > +
> > > +     switch (CSI2_SUBMD(reg)) {
> > > +     case SUBMD_APHY:
> > > +             addr = csi->aphy + CSI2_REG_ADDR(reg);
> > > +             break;
> > > +     case SUBMD_DPHY:
> > > +             addr = csi->dphy + CSI2_REG_ADDR(reg);
> > > +             break;
> > > +     case SUBMD_HOST:
> > > +             addr = csi->host + CSI2_REG_ADDR(reg);
> > > +             break;
> > > +     default:
> > > +             dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
> > > +             return;
> > > +     }
> > This is repeated in two functions and could be grouped to a common
> > place. Up to you
> Will use a common function to replace this part.
> > > +
> > > +     orig = readl(addr);
> > > +     tmp = orig & ~mask;
> > > +     tmp |= val & mask;
> > > +
> > > +     if (tmp != orig)
> > > +             writel(tmp, addr);
> > > +}
> > > +
> > > +static void c3_mipi_csi_cfg_aphy(struct csi_device *csi, u32 lanes)
> > > +{
> > > +     c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL1, MIPI_APHY_NORMAL_CNTL1);
> > > +
> > > +     if (lanes == 4)
> > > +             c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_4LANES_CNTL2);
> > > +     else
> > > +             c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_NORMAL_CNTL2);
> > > +
> > > +     if (lanes == 2)
> > > +             c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL3, MIPI_APHY_2LANES_CNTL3);
> > The driver seems to only accept 2 or 4 lanes. What is
> > MIPI_APHY_NORMAL_CNTL2 for ?
>
> Will modify MIPI_APHY_NORMAL_CNTL2 to MIPI_APHY_2LANES_CNTL2.
>
> MIPI_APHY_2LANES_CNTL2 can indicate this 2 lanes setting.
>
> > > +}
> > > +
> > > +static void c3_mipi_csi_2lanes_setting(struct csi_device *csi)
> > > +{
> > > +     /* Disable lane 2 and lane 3 */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0xf);
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
> > > +                             0xf << MIPI_DPHY_LANE2_SELECT_SHIFT);
> > > +     /* Select analog data lane 1 */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
> > > +                             0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
> > > +     /* Select analog data lane 0 */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
> > > +
> > > +     /* Disable lane 2 and lane 3 control signal */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0xf);
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
> > > +                             0xf << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
> > > +     /* Select lane 1 signal */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
> > > +                             0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
> > > +     /* Select lane 0 signal */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
> > > +     /* Select input 0 as clock */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
> > > +                             MIPI_DPHY_CLK_SELECT);
> > > +}
> > > +
> > > +static void c3_mipi_csi_4lanes_setting(struct csi_device *csi)
> > > +{
> > > +     /* Select analog data lane 3 */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0x3);
> > > +     /* Select analog data lane 2 */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
> > > +                             0x2 << MIPI_DPHY_LANE2_SELECT_SHIFT);
> > > +     /* Select analog data lane 1 */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
> > > +                             0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
> > > +     /* Select analog data lane 0 */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
> > > +
> > > +     /* Select lane 3 signal */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0x3);
> > > +     /* Select lane 2 signal */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
> > > +                             0x2 << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
> > > +     /* Select lane 1 signal */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
> > > +                             0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
> > > +     /* Select lane 0 signal */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
> > > +     /* Select input 0 as clock */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
> > > +                             MIPI_DPHY_CLK_SELECT);
> > > +}
> > > +
> > > +static void c3_mipi_csi_cfg_dphy(struct csi_device *csi, u32 lanes, s64 rate)
> > > +{
> > > +     u32 val;
> > > +     u32 settle;
> > > +
> > > +     /* Calculate the high speed settle */
> > > +     val = DIV_ROUND_UP(1000000000, rate);
> > > +     settle = (16 * val + 230) / 10;
> > > +
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL, MIPI_DPHY_CLK_CONTINUE_MODE);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_TCLK_MISS, MIPI_DPHY_CLK_MISS);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_TCLK_SETTLE, MIPI_DPHY_CLK_SETTLE);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_THS_EXIT, MIPI_DPHY_HS_EXIT);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_THS_SKIP, MIPI_DPHY_HS_SKIP);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_THS_SETTLE, settle);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_TINIT, MIPI_DPHY_INIT_CYCLES);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_TMBIAS, MIPI_DPHY_MBIAS_CYCLES);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_TULPS_C, MIPI_DPHY_ULPS_CHECK_CYCLES);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_TULPS_S, MIPI_DPHY_ULPS_START_CYCLES);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_TLP_EN_W, MIPI_DPHY_ULPS_STOP_CYCLES);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_TLPOK, MIPI_DPHY_POWER_UP_CYCLES);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_TWD_INIT, MIPI_DPHY_INIT_WATCH_DOG);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_TWD_HS, MIPI_DPHY_HS_WATCH_DOG);
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_DATA_LANE_CTRL, MIPI_DPHY_LANE_CTRL_DISABLE);
> > > +
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_INSERT_ERRESC,
> > > +                             MIPI_DPHY_INSERT_ERRESC);
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_HS_SYNC_CHECK,
> > > +                             MIPI_DPHY_HS_SYNC_CHECK);
> > > +     /*
> > > +      * Set 5 pipe lines to the same high speed.
> > > +      * Each bit for one pipe line.
> > > +      */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_FIVE_HS_PIPE,
> > > +                             0x1f << MIPI_DPHY_FIVE_HS_PIPE_SHIFT);
> > > +
> > > +     /* Output data with pipe line data. */
> > > +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_DATA_PIPE_SELECT,
> > > +                             0x3 << MIPI_DPHY_DATA_PIPE_SELECT_SHIFT);
> > Would it be possible to provide a definition for these 0x1f and 0x3
> > values ?
> OK, will provide two macros to represent  0x1f and 0x3.
> > > +     if (lanes == 2)
> > > +             c3_mipi_csi_2lanes_setting(csi);
> > > +     else
> > > +             c3_mipi_csi_4lanes_setting(csi);
> > > +
> > > +     /* Enable digital data and clock lanes */
> > > +     c3_mipi_csi_write(csi, MIPI_PHY_CTRL, MIPI_DPHY_LANES_ENABLE);
> > > +}
> > > +
> > > +static void c3_mipi_csi_cfg_host(struct csi_device *csi, u32 lanes)
> > > +{
> > > +     /* Reset CSI-2 controller output */
> > > +     c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_DEFAULT);
> > > +     c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_RST_VALUE);
> > > +
> > > +     /* Set data lane number */
> > > +     c3_mipi_csi_write(csi, CSI2_HOST_N_LANES, lanes - 1);
> > > +
> > > +     /* Enable error mask */
> > > +     c3_mipi_csi_write(csi, CSI2_HOST_MASK1, CSI2_HOST_ERROR_MASK1);
> > > +}
> > > +
> > > +static int c3_mipi_csi_start_stream(struct csi_device *csi)
> > > +{
> > > +     s64 link_freq;
> > > +     s64 lane_rate;
> > > +
> > > +     link_freq = v4l2_get_link_freq(csi->src_sd->ctrl_handler, 0, 0);
> > > +     if (link_freq < 0) {
> > > +             dev_err(csi->dev, "Unable to obtain link frequency: %lld\n", link_freq);
> > > +             return link_freq;
> > > +     }
> > > +
> > > +     lane_rate = link_freq * 2;
> > > +     if (lane_rate > 1500000000)
> > I would dev_err here too
> Will add dev_err.
> > > +             return -EINVAL;
> > > +
> > > +     c3_mipi_csi_cfg_aphy(csi, csi->bus.num_data_lanes);
> > > +     c3_mipi_csi_cfg_dphy(csi, csi->bus.num_data_lanes, lane_rate);
> > > +     c3_mipi_csi_cfg_host(csi, csi->bus.num_data_lanes);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_mipi_csi_enable_streams(struct v4l2_subdev *sd,
> > > +                                   struct v4l2_subdev_state *state,
> > > +                                   u32 pad, u64 streams_mask)
> > > +{
> > > +     struct csi_device *csi = v4l2_get_subdevdata(sd);
> > > +     u64 sink_streams;
> > > +     int ret;
> > > +
> > > +     guard(mutex)(&csi->lock);
> > > +
> > > +     pm_runtime_resume_and_get(csi->dev);
> > > +
> > > +     c3_mipi_csi_start_stream(csi);
> > > +
> > > +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > > +                                                    MIPI_CSI2_PAD_SINK,
> > > +                                                    &streams_mask);
> > > +     ret = v4l2_subdev_enable_streams(csi->src_sd,
> > > +                                      csi->src_sd_pad,
> > > +                                      sink_streams);
> > > +     if (ret) {
> > > +             pm_runtime_put(csi->dev);
> > > +             return ret;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_mipi_csi_disable_streams(struct v4l2_subdev *sd,
> > > +                                    struct v4l2_subdev_state *state,
> > > +                                    u32 pad, u64 streams_mask)
> > > +{
> > > +     struct csi_device *csi = v4l2_get_subdevdata(sd);
> > > +     u64 sink_streams;
> > > +     int ret;
> > > +
> > > +     guard(mutex)(&csi->lock);
> > > +
> > > +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > > +                                                    MIPI_CSI2_PAD_SINK,
> > > +                                                    &streams_mask);
> > > +     ret = v4l2_subdev_disable_streams(csi->src_sd,
> > > +                                       csi->src_sd_pad,
> > > +                                       sink_streams);
> > > +     if (ret)
> > > +             dev_err(csi->dev, "Failed to disable %s\n", csi->src_sd->name);
> > > +
> > > +     pm_runtime_put(csi->dev);
> > > +
> > > +     return ret;
> > > +}
> > > +
> > > +static int c3_mipi_csi_cfg_routing(struct v4l2_subdev *sd,
> > > +                                struct v4l2_subdev_state *state,
> > > +                                struct v4l2_subdev_krouting *routing)
> > > +{
> > > +     static const struct v4l2_mbus_framefmt format = {
> > > +             .width = MIPI_CSI2_DEFAULT_WIDTH,
> > > +             .height = MIPI_CSI2_DEFAULT_HEIGHT,
> > > +             .code = MIPI_CSI2_DEFAULT_FMT,
> > > +             .field = V4L2_FIELD_NONE,
> > > +             .colorspace = V4L2_COLORSPACE_RAW,
> > > +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
> > > +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
> > I presume for Raw Bayer data the quantization range is full ?
> OK, will modify V4L2_QUANTIZATION_LIM_RANGE to V4L2_QUANTIZATION_FULL_RANGE.
> > > +             .xfer_func = V4L2_XFER_FUNC_NONE,
> > > +     };
> > > +     int ret;
> > > +
> > > +     ret = v4l2_subdev_routing_validate(sd, routing,
> > > +                                        V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> > > +     if (ret)
> > > +             return ret;
> > You should validate that the provided routing table matches what the
> > driver supports, so only [0/0]->[1/0]
> >
> > Now that I've said so, if the routing table is not modifiable I wonder
> > if you should support set_routing() at all, or it could be left out
> > until you don't add support for more streams to the driver.
> >
> > After all this driver implements support for routing but doesn't set
> > the V4L2_SUBDEV_FL_STREAMS flag, so the operation is disallowed from
> > userspace for now.
>
> Will remove set_routing().
>
> Now the driver dosen't require  routing configuration.
>
> > > +
> > > +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_mipi_csi_init_routing(struct v4l2_subdev *sd,
> > > +                                 struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_subdev_route routes;
> > > +     struct v4l2_subdev_krouting routing;
> > > +
> > > +     routes.sink_pad = MIPI_CSI2_PAD_SINK;
> > > +     routes.sink_stream = 0;
> > > +     routes.source_pad = MIPI_CSI2_PAD_SRC;
> > > +     routes.source_stream = 0;
> > > +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> > > +
> > > +     routing.num_routes = 1;
> > > +     routing.routes = &routes;
> > > +
> > > +     return c3_mipi_csi_cfg_routing(sd, state, &routing);
> > > +}
> > > +
> > > +static int c3_mipi_csi_set_routing(struct v4l2_subdev *sd,
> > > +                                struct v4l2_subdev_state *state,
> > > +                                enum v4l2_subdev_format_whence which,
> > > +                                struct v4l2_subdev_krouting *routing)
> > > +{
> > > +     bool is_streaming = v4l2_subdev_is_streaming(sd);
> > > +
> > > +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> > > +             return -EBUSY;
> > > +
> > > +     return c3_mipi_csi_cfg_routing(sd, state, routing);
> > > +}
> > > +
> > > +static int c3_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
> > > +                                   struct v4l2_subdev_state *state,
> > > +                                   struct v4l2_subdev_mbus_code_enum *code)
> > > +{
> > > +     switch (code->pad) {
> > > +     case MIPI_CSI2_PAD_SINK:
> > > +             if (code->index >= ARRAY_SIZE(c3_mipi_csi_formats))
> > > +                     return -EINVAL;
> > > +
> > > +             code->code = c3_mipi_csi_formats[code->index];
> > > +             break;
> > > +     case MIPI_CSI2_PAD_SRC:
> > > +             struct v4l2_mbus_framefmt *fmt;
> > > +
> > > +             if (code->index > 0)
> > > +                     return -EINVAL;
> > > +
> > > +             fmt = v4l2_subdev_state_get_format(state, code->pad);
> > > +             code->code = fmt->code;
> > > +             break;
> > I'm not sure if the V4L2 API specify that the formats on a pad should
> > be enumerated in full, regardless of the configuration, or like you're
> > doing here reflect the subdev configuration. I like what you have here
> > more, so unless someone screams I think it's fine.
>
> OK, thanks.
>
> I will pay attention to the review of this function.
>
> > > +     default:
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_mipi_csi_set_fmt(struct v4l2_subdev *sd,
> > > +                            struct v4l2_subdev_state *state,
> > > +                            struct v4l2_subdev_format *format)
> > > +{
> > > +     struct v4l2_mbus_framefmt *fmt;
> > > +     unsigned int i;
> > > +
> > > +     if (format->pad != MIPI_CSI2_PAD_SINK)
> > > +             return v4l2_subdev_get_fmt(sd, state, format);
> > > +
> > > +     fmt = v4l2_subdev_state_get_format(state, format->pad);
> > Could you clarify what other streams you plan to support ? As you
> > support routing I presume you are preparing to capture
> > multiple streams of data like image + embedded data, or to support
> > sensors which sends data on different virtual channels ?
> >
> > How do you see this driver evolve ? Will it be augmented with an
> > additional source pad directed to a video device where to capture
> > embedded data from ?
> >
> > I'm wondering because if PAD_SINK become multiplexed, you won't be
> > allowed to set a format there. It only works now because you have a
> > single stream.
> >
> > Speaking of which, as you prepare to support multiple streams, I would
> > specify the stream number when calling v4l2_subdev_state_get_format().
> >
> >          fmt = v4l2_subdev_state_get_format(state, format->pad, 0);
> >
> Thanks for your suggestion.
>
> But this MIPI CSI2 hardware module doesn't have the ability to separate data
> , such as image + embedded data.
>
> So there are no plans to support other streams.

I see. Now that I've reviewed the adapter subdevice path I realized
that it's the adapter that can split data based on VC/DT to either the
ISP direct path or to DDR.

The CSI-2 RX then transports streams as received from the sensor
directly to the adapter.

In order to support capturing embedded data to DDR in the adapter the
embedded data stream needs a representation in this subdevice as well,
so that the source pad is multiplexed as well and the adapter receives
two streams that it can eventually split.


      Sensor         CSI-2Rx         Adapter
   +-----------+     +------+       +--------+
   0-- ED --\  |     |      |       |     /->0---> Embedded Data (DDR)
   |         ->0====>0 ==== 0 ====> 0====|   |
   0-- I ---/  |     |      |       |     \->0--->    Image (ISP)
   +-----------+     +------+       +--------+

When going to support embedded data capture this driver should create
two routes and allow enabling/disabling the embedded data one.

Tomi in cc for inputs.

For now, if you don't support capturing embedded data, I would remove
routing support from here and from the adapter.

>
> > > +
> > > +     for (i = 0; i < ARRAY_SIZE(c3_mipi_csi_formats); i++)
> > > +             if (format->format.code == c3_mipi_csi_formats[i])
> > > +                     break;
> > nit: please use {} for the for loop
> Will add { } for the for loop
> > > +
> > > +     if (i == ARRAY_SIZE(c3_mipi_csi_formats))
> > > +             fmt->code = c3_mipi_csi_formats[0];
> > > +     else
> > > +             fmt->code = c3_mipi_csi_formats[i];
> > You could set this in the for loop, before breaking.
> Will put this in the for loop.
> > > +
> > > +     fmt->width = clamp_t(u32, format->format.width,
> > > +                          MIPI_CSI2_MIN_WIDTH, MIPI_CSI2_MAX_WIDTH);
> > > +     fmt->height = clamp_t(u32, format->format.height,
> > > +                           MIPI_CSI2_MIN_HEIGHT, MIPI_CSI2_MAX_HEIGHT);
> > > +
> > You should set the colorspace related information too, as you
> > initialize them, similar to what you do in init_state()
> Will set the colorspace .
> > > +     format->format = *fmt;
> > > +
> > > +     /* Synchronize the format to source pad */
> > > +     fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
> > > +     *fmt = format->format;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_mipi_csi_init_state(struct v4l2_subdev *sd,
> > > +                               struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_mbus_framefmt *sink_fmt;
> > > +     struct v4l2_mbus_framefmt *src_fmt;
> > > +
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SINK);
> > > +     src_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
> > > +
> > > +     sink_fmt->width = MIPI_CSI2_DEFAULT_WIDTH;
> > > +     sink_fmt->height = MIPI_CSI2_DEFAULT_HEIGHT;
> > > +     sink_fmt->field = V4L2_FIELD_NONE;
> > > +     sink_fmt->code = MIPI_CSI2_DEFAULT_FMT;
> > > +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> > > +     sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
> > > +     sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
> > > +     sink_fmt->quantization =
> > > +             V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
> > If you could initialize them like you do above, with specific values
> > instead of using _DEFAULT() I think it's better.
> Will use the specific values.
> > > +                                           sink_fmt->ycbcr_enc);
> > > +     *src_fmt = *sink_fmt;
> > > +
> > > +     return c3_mipi_csi_init_routing(sd, state);
> > > +}
> > > +
> > > +static const struct v4l2_subdev_pad_ops c3_mipi_csi_pad_ops = {
> > > +     .enum_mbus_code = c3_mipi_csi_enum_mbus_code,
> > > +     .get_fmt = v4l2_subdev_get_fmt,
> > > +     .set_fmt = c3_mipi_csi_set_fmt,
> > > +     .set_routing = c3_mipi_csi_set_routing,
> > > +     .enable_streams = c3_mipi_csi_enable_streams,
> > > +     .disable_streams = c3_mipi_csi_disable_streams,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_ops c3_mipi_csi_subdev_ops = {
> > > +     .pad = &c3_mipi_csi_pad_ops,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_internal_ops c3_mipi_csi_internal_ops = {
> > > +     .init_state = c3_mipi_csi_init_state,
> > > +};
> > > +
> > > +/* Media entity operations */
> > > +static const struct media_entity_operations c3_mipi_csi_entity_ops = {
> > > +     .link_validate = v4l2_subdev_link_validate,
> > > +};
> > > +
> > > +/* PM runtime */
> > > +
> > > +static int __maybe_unused c3_mipi_csi_runtime_suspend(struct device *dev)
> > > +{
> > > +     struct csi_device *csi = dev_get_drvdata(dev);
> > > +
> > > +     clk_bulk_disable_unprepare(csi->info->clock_num, csi->clks);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int __maybe_unused c3_mipi_csi_runtime_resume(struct device *dev)
> > > +{
> > > +     struct csi_device *csi = dev_get_drvdata(dev);
> > > +
> > > +     return clk_bulk_prepare_enable(csi->info->clock_num, csi->clks);
> > > +}
> > > +
> > > +static const struct dev_pm_ops c3_mipi_csi_pm_ops = {
> > > +     SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> > > +                             pm_runtime_force_resume)
> > > +     SET_RUNTIME_PM_OPS(c3_mipi_csi_runtime_suspend,
> > > +                        c3_mipi_csi_runtime_resume, NULL)
> > You could use SYSTEM_SLEEP_PM_OPS and RUNTIME_PM_OPS and set
> >
> >                  .pm = pm_ptr(&c3_mipi_csi_pm_ops),
> >
> > to avoid __maybe_unused in the functions, up to you
> >
> OK, will use your method to set pm.
> > > +};
> > > +
> > > +/* Probe/remove & platform driver */
> > > +
> > > +static int c3_mipi_csi_subdev_init(struct csi_device *csi)
> > > +{
> > > +     struct v4l2_subdev *sd = &csi->sd;
> > > +     int ret;
> > > +
> > > +     v4l2_subdev_init(sd, &c3_mipi_csi_subdev_ops);
> > > +     sd->owner = THIS_MODULE;
> > > +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > > +     sd->internal_ops = &c3_mipi_csi_internal_ops;
> > > +     snprintf(sd->name, sizeof(sd->name), "%s", MIPI_CSI2_SUBDEV_NAME);
> > > +
> > > +     sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> > > +     sd->entity.ops = &c3_mipi_csi_entity_ops;
> > > +
> > > +     sd->dev = csi->dev;
> > > +     v4l2_set_subdevdata(sd, csi);
> > > +
> > > +     csi->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> > > +     csi->pads[MIPI_CSI2_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
> > > +     ret = media_entity_pads_init(&sd->entity, MIPI_CSI2_PAD_MAX, csi->pads);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = v4l2_subdev_init_finalize(sd);
> > > +     if (ret) {
> > > +             media_entity_cleanup(&sd->entity);
> > > +             return ret;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static void c3_mipi_csi_subdev_deinit(struct csi_device *csi)
> > > +{
> > > +     v4l2_subdev_cleanup(&csi->sd);
> > > +     media_entity_cleanup(&csi->sd.entity);
> > > +}
> > > +
> > > +/* Subdev notifier register */
> > > +static int c3_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier,
> > > +                                 struct v4l2_subdev *sd,
> > > +                                 struct v4l2_async_connection *asc)
> > > +{
> > > +     struct csi_device *csi = v4l2_get_subdevdata(notifier->sd);
> > > +     struct media_pad *sink = &csi->sd.entity.pads[MIPI_CSI2_PAD_SINK];
> > > +     int ret;
> > > +
> > > +     ret = media_entity_get_fwnode_pad(&sd->entity,
> > > +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
> > > +     if (ret < 0) {
> > > +             dev_err(csi->dev, "Failed to find pad for %s\n", sd->name);
> > > +             return ret;
> > > +     }
> > > +
> > > +     csi->src_sd = sd;
> > > +     csi->src_sd_pad = ret;
> > > +
> > > +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
> > > +                                            MEDIA_LNK_FL_IMMUTABLE);
> > > +}
> > > +
> > > +static const struct v4l2_async_notifier_operations c3_mipi_csi_notify_ops = {
> > > +     .bound = c3_mipi_csi_notify_bound,
> > > +};
> > > +
> > > +static int c3_mipi_csi_async_register(struct csi_device *csi)
> > > +{
> > > +     struct v4l2_fwnode_endpoint vep = {
> > > +             .bus_type = V4L2_MBUS_CSI2_DPHY,
> > > +     };
> > > +     struct v4l2_async_connection *asc;
> > > +     struct fwnode_handle *ep;
> > > +     int ret;
> > > +
> > > +     v4l2_async_subdev_nf_init(&csi->notifier, &csi->sd);
> > > +
> > > +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0,
> > > +                                          FWNODE_GRAPH_ENDPOINT_NEXT);
> > > +     if (!ep)
> > > +             return -ENOTCONN;
> > > +
> > > +     ret = v4l2_fwnode_endpoint_parse(ep, &vep);
> > > +     if (ret)
> > > +             goto err_put_handle;
> > > +
> > > +     csi->bus = vep.bus.mipi_csi2;
> > > +     if (csi->bus.num_data_lanes != 2 && csi->bus.num_data_lanes != 4)
> > > +             goto err_put_handle;
> > I would dev_err() here
> >
> > Thanks
> >     j
> Wil add dev_err().
> > > +
> > > +     asc = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep,
> > > +                                           struct v4l2_async_connection);
> > > +     if (IS_ERR(asc)) {
> > > +             ret = PTR_ERR(asc);
> > > +             goto err_put_handle;
> > > +     }
> > > +
> > > +     csi->notifier.ops = &c3_mipi_csi_notify_ops;
> > > +     ret = v4l2_async_nf_register(&csi->notifier);
> > > +     if (ret)
> > > +             goto err_cleanup_nf;
> > > +
> > > +     ret = v4l2_async_register_subdev(&csi->sd);
> > > +     if (ret)
> > > +             goto err_unregister_nf;
> > > +
> > > +     fwnode_handle_put(ep);
> > > +
> > > +     return 0;
> > > +
> > > +err_unregister_nf:
> > > +     v4l2_async_nf_unregister(&csi->notifier);
> > > +err_cleanup_nf:
> > > +     v4l2_async_nf_cleanup(&csi->notifier);
> > > +err_put_handle:
> > > +     fwnode_handle_put(ep);
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_mipi_csi_async_unregister(struct csi_device *csi)
> > > +{
> > > +     v4l2_async_unregister_subdev(&csi->sd);
> > > +     v4l2_async_nf_unregister(&csi->notifier);
> > > +     v4l2_async_nf_cleanup(&csi->notifier);
> > > +}
> > > +
> > > +static int c3_mipi_csi_ioremap_resource(struct csi_device *csi)
> > > +{
> > > +     struct device *dev = csi->dev;
> > > +     struct platform_device *pdev = to_platform_device(dev);
> > > +
> > > +     csi->aphy = devm_platform_ioremap_resource_byname(pdev, "aphy");
> > > +     if (IS_ERR(csi->aphy))
> > > +             return PTR_ERR(csi->aphy);
> > > +
> > > +     csi->dphy = devm_platform_ioremap_resource_byname(pdev, "dphy");
> > > +     if (IS_ERR(csi->dphy))
> > > +             return PTR_ERR(csi->dphy);
> > > +
> > > +     csi->host = devm_platform_ioremap_resource_byname(pdev, "host");
> > > +     if (IS_ERR(csi->host))
> > > +             return PTR_ERR(csi->host);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_mipi_csi_configure_clocks(struct csi_device *csi)
> > > +{
> > > +     const struct csi_info *info = csi->info;
> > > +     int ret;
> > > +     u32 i;
> > > +
> > > +     for (i = 0; i < info->clock_num; i++)
> > > +             csi->clks[i].id = info->clocks[i];
> > > +
> > > +     ret = devm_clk_bulk_get(csi->dev, info->clock_num, csi->clks);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     for (i = 0; i < info->clock_num; i++) {
> > > +             if (!info->clock_rates[i])
> > > +                     continue;
> > > +             ret = clk_set_rate(csi->clks[i].clk, info->clock_rates[i]);
> > > +             if (ret) {
> > > +                     dev_err(csi->dev, "Failed to set %s rate %u\n", info->clocks[i],
> > > +                             info->clock_rates[i]);
> > > +                     return ret;
> > > +             }
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_mipi_csi_probe(struct platform_device *pdev)
> > > +{
> > > +     struct device *dev = &pdev->dev;
> > > +     struct csi_device *csi;
> > > +     int ret;
> > > +
> > > +     csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
> > > +     if (!csi)
> > > +             return -ENOMEM;
> > > +
> > > +     csi->info = of_device_get_match_data(dev);
> > > +     csi->dev = dev;
> > > +
> > > +     ret = c3_mipi_csi_ioremap_resource(csi);
> > > +     if (ret)
> > > +             return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
> > > +
> > > +     ret = c3_mipi_csi_configure_clocks(csi);
> > > +     if (ret)
> > > +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
> > > +
> > > +     platform_set_drvdata(pdev, csi);
> > > +
> > > +     mutex_init(&csi->lock);
> > > +     pm_runtime_enable(dev);
> > > +
> > > +     ret = c3_mipi_csi_subdev_init(csi);
> > > +     if (ret)
> > > +             goto err_disable_runtime_pm;
> > > +
> > > +     ret = c3_mipi_csi_async_register(csi);
> > > +     if (ret)
> > > +             goto err_deinit_subdev;
> > > +
> > > +     return 0;
> > > +
> > > +err_deinit_subdev:
> > > +     c3_mipi_csi_subdev_deinit(csi);
> > > +err_disable_runtime_pm:
> > > +     pm_runtime_disable(dev);
> > > +     mutex_destroy(&csi->lock);
> > > +     return ret;
> > > +};
> > > +
> > > +static void c3_mipi_csi_remove(struct platform_device *pdev)
> > > +{
> > > +     struct csi_device *csi = platform_get_drvdata(pdev);
> > > +
> > > +     c3_mipi_csi_async_unregister(csi);
> > > +     c3_mipi_csi_subdev_deinit(csi);
> > > +
> > > +     pm_runtime_disable(&pdev->dev);
> > > +     mutex_destroy(&csi->lock);
> > > +};
> > > +
> > > +static const struct csi_info c3_mipi_csi_info = {
> > > +     .clocks = {"vapb", "phy0"},
> > > +     .clock_rates = {0, 200000000},
> > > +     .clock_num = 2
> > > +};
> > > +
> > > +static const struct of_device_id c3_mipi_csi_of_match[] = {
> > > +     { .compatible = "amlogic,c3-mipi-csi2",
> > > +       .data = &c3_mipi_csi_info,
> > > +     },
> > > +     { },
> > > +};
> > > +MODULE_DEVICE_TABLE(of, c3_mipi_csi_of_match);
> > > +
> > > +static struct platform_driver c3_mipi_csi_driver = {
> > > +     .probe = c3_mipi_csi_probe,
> > > +     .remove = c3_mipi_csi_remove,
> > > +     .driver = {
> > > +             .name = "c3-mipi-csi2",
> > > +             .of_match_table = c3_mipi_csi_of_match,
> > > +             .pm = &c3_mipi_csi_pm_ops,
> > > +     },
> > > +};
> > > +
> > > +module_platform_driver(c3_mipi_csi_driver);
> > > +
> > > +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
> > > +MODULE_DESCRIPTION("Amlogic C3 MIPI CSI-2 receiver");
> > > +MODULE_LICENSE("GPL");
> > >
> > > --
> > > 2.46.1
> > >
> > >
> > >

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

* Re: [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver
  2024-11-05 10:06     ` Jacopo Mondi
@ 2024-11-05 11:40       ` Keke Li
  0 siblings, 0 replies; 37+ messages in thread
From: Keke Li @ 2024-11-05 11:40 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally,
	tomi.valkeinen@ideasonboard.com

Hi Jacopo

Thanks very much for your reply.

On 2024/11/5 18:06, Jacopo Mondi wrote:
> [You don't often get email from jacopo.mondi@ideasonboard.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>
> [ EXTERNAL EMAIL ]
>
> Hi again Keke
>
> On Mon, Nov 04, 2024 at 06:50:11PM +0100, Jacopo Mondi wrote:
>> Hi Keke
>>     sorry for the late feedback, hope you're still interested in
>> upstreaming this driver
>>
>> On Wed, Sep 18, 2024 at 02:07:13PM +0800, Keke Li via B4 Relay wrote:
>>> From: Keke Li <keke.li@amlogic.com>
>>>
>>> This driver is used to receive mipi data from image sensor.
>>>
>>> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
>>> Signed-off-by: Keke Li <keke.li@amlogic.com>
>>> ---
>>>   MAINTAINERS                                        |   7 +
>>>   drivers/media/platform/amlogic/Kconfig             |   1 +
>>>   drivers/media/platform/amlogic/Makefile            |   2 +
>>>   .../media/platform/amlogic/c3-mipi-csi2/Kconfig    |  16 +
>>>   .../media/platform/amlogic/c3-mipi-csi2/Makefile   |   3 +
>>>   .../platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c   | 910 +++++++++++++++++++++
>>>   6 files changed, 939 insertions(+)
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index 2cdd7cacec86..9e75874a6e69 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -1209,6 +1209,13 @@ F:   Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
>>>   F: drivers/perf/amlogic/
>>>   F: include/soc/amlogic/
>>>
>>> +AMLOGIC MIPI CSI2 DRIVER
>>> +M: Keke Li <keke.li@amlogic.com>
>>> +L: linux-media@vger.kernel.org
>>> +S: Maintained
>>> +F: Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml
>>> +F: drivers/media/platform/amlogic/c3-mipi-csi2/
>>> +
>>>   AMPHENOL CHIPCAP 2 HUMIDITY-TEMPERATURE IIO DRIVER
>>>   M: Javier Carrasco <javier.carrasco.cruz@gmail.com>
>>>   L: linux-hwmon@vger.kernel.org
>>> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
>>> index 5014957404e9..b7c2de14848b 100644
>>> --- a/drivers/media/platform/amlogic/Kconfig
>>> +++ b/drivers/media/platform/amlogic/Kconfig
>>> @@ -2,4 +2,5 @@
>>>
>>>   comment "Amlogic media platform drivers"
>>>
>>> +source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>>>   source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
>>> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
>>> index d3cdb8fa4ddb..4f571ce5d13e 100644
>>> --- a/drivers/media/platform/amlogic/Makefile
>>> +++ b/drivers/media/platform/amlogic/Makefile
>>> @@ -1,2 +1,4 @@
>>>   # SPDX-License-Identifier: GPL-2.0-only
>>> +
>>> +obj-y += c3-mipi-csi2/
>>>   obj-y += meson-ge2d/
>>> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
>>> new file mode 100644
>>> index 000000000000..0d7b2e203273
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
>>> @@ -0,0 +1,16 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +
>>> +config VIDEO_C3_MIPI_CSI2
>>> +   tristate "Amlogic C3 MIPI CSI-2 receiver"
>>> +   depends on ARCH_MESON || COMPILE_TEST
>>> +   depends on VIDEO_DEV
>>> +   depends on OF
>>> +   select MEDIA_CONTROLLER
>>> +   select V4L2_FWNODE
>>> +   select VIDEO_V4L2_SUBDEV_API
>>> +   help
>>> +     Video4Linux2 driver for Amlogic C3 MIPI CSI-2 receiver.
>>> +     C3 MIPI CSI-2 receiver is used to receive MIPI data from
>>> +     image sensor.
>>> +
>>> +     To compile this driver as a module choose m here.
>>> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
>>> new file mode 100644
>>> index 000000000000..cc08fc722bfd
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
>>> @@ -0,0 +1,3 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +
>>> +obj-$(CONFIG_VIDEO_C3_MIPI_CSI2) += c3-mipi-csi2.o
>>> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
>>> new file mode 100644
>>> index 000000000000..6ac60d5b26a8
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
>>> @@ -0,0 +1,910 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/cleanup.h>
>>> +#include <linux/clk.h>
>>> +#include <linux/device.h>
>>> +#include <linux/module.h>
>>> +#include <linux/mutex.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pm_runtime.h>
>>> +
>>> +#include <media/v4l2-async.h>
>>> +#include <media/v4l2-common.h>
>>> +#include <media/v4l2-device.h>
>>> +#include <media/v4l2-fwnode.h>
>>> +#include <media/v4l2-mc.h>
>>> +#include <media/v4l2-subdev.h>
>>> +
>>> +/* C3 CSI-2 submodule definition */
>>> +enum {
>>> +   SUBMD_APHY,
>>> +   SUBMD_DPHY,
>>> +   SUBMD_HOST,
>>> +};
>>> +
>>> +#define CSI2_SUBMD_MASK             GENMASK(17, 16)
>>> +#define CSI2_SUBMD_SHIFT            16
>>> +#define CSI2_SUBMD(x)               (((x) & (CSI2_SUBMD_MASK)) >> (CSI2_SUBMD_SHIFT))
>>> +#define CSI2_REG_ADDR_MASK          GENMASK(15, 0)
>>> +#define CSI2_REG_ADDR(x)            ((x) & (CSI2_REG_ADDR_MASK))
>>> +#define CSI2_REG_A(x)               ((SUBMD_APHY << CSI2_SUBMD_SHIFT) | (x))
>>> +#define CSI2_REG_D(x)               ((SUBMD_DPHY << CSI2_SUBMD_SHIFT) | (x))
>>> +#define CSI2_REG_H(x)               ((SUBMD_HOST << CSI2_SUBMD_SHIFT) | (x))
>>> +
>>> +#define MIPI_CSI2_CLOCK_NUM_MAX     3
>>> +#define MIPI_CSI2_SUBDEV_NAME       "mipi-csi2"
>> Isn't the name too generic ? Should it report at least "c3-mipi-csi2"
>> ?
>>> +
>>> +/* C3 CSI-2 APHY register */
>>> +#define MIPI_CSI_2M_PHY2_CNTL1      CSI2_REG_A(0x44)
>>> +#define MIPI_APHY_NORMAL_CNTL1      0x3f425C00
>> All other hex addresses use small capitals for letters
>>
>>> +
>>> +#define MIPI_CSI_2M_PHY2_CNTL2      CSI2_REG_A(0x48)
>>> +#define MIPI_APHY_4LANES_CNTL2      0x033a0000
>>> +#define MIPI_APHY_NORMAL_CNTL2      0x333a0000
>>> +
>>> +#define MIPI_CSI_2M_PHY2_CNTL3      CSI2_REG_A(0x4c)
>>> +#define MIPI_APHY_2LANES_CNTL3      0x03800000
>>> +
>>> +/* C3 CSI-2 DPHY register */
>>> +#define MIPI_PHY_CTRL                  CSI2_REG_D(0x00)
>>> +#define MIPI_DPHY_LANES_ENABLE      0x0
>>> +
>>> +#define MIPI_PHY_CLK_LANE_CTRL         CSI2_REG_D(0x04)
>>> +#define MIPI_DPHY_CLK_CONTINUE_MODE 0x3d8
>>> +
>>> +#define MIPI_PHY_DATA_LANE_CTRL     CSI2_REG_D(0x08)
>>> +#define MIPI_DPHY_LANE_CTRL_DISABLE 0x0
>>> +
>>> +#define MIPI_PHY_DATA_LANE_CTRL1    CSI2_REG_D(0x0c)
>>> +#define MIPI_DPHY_INSERT_ERRESC     BIT(0)
>>> +#define MIPI_DPHY_HS_SYNC_CHECK     BIT(1)
>>> +#define MIPI_DPHY_FIVE_HS_PIPE      GENMASK(6, 2)
>>> +#define MIPI_DPHY_FIVE_HS_PIPE_SHIFT           2
>>> +#define MIPI_DPHY_DATA_PIPE_SELECT  GENMASK(9, 7)
>>> +#define MIPI_DPHY_DATA_PIPE_SELECT_SHIFT       7
>>> +
>>> +#define MIPI_PHY_TCLK_MISS     CSI2_REG_D(0x10)
>>> +#define MIPI_DPHY_CLK_MISS          0x9
>>> +
>>> +#define MIPI_PHY_TCLK_SETTLE           CSI2_REG_D(0x14)
>>> +#define MIPI_DPHY_CLK_SETTLE        0x1F
>>> +
>>> +#define MIPI_PHY_THS_EXIT      CSI2_REG_D(0x18)
>>> +#define MIPI_DPHY_HS_EXIT           0x8
>>> +
>>> +#define MIPI_PHY_THS_SKIP      CSI2_REG_D(0x1c)
>>> +#define MIPI_DPHY_HS_SKIP           0xa
>>> +
>>> +#define MIPI_PHY_THS_SETTLE            CSI2_REG_D(0x20)
>>> +#define MIPI_PHY_TINIT                 CSI2_REG_D(0x24)
>>> +#define MIPI_DPHY_INIT_CYCLES       0x4e20
>>> +
>>> +#define MIPI_PHY_TULPS_C       CSI2_REG_D(0x28)
>>> +#define MIPI_DPHY_ULPS_CHECK_CYCLES 0x1000
>>> +
>>> +#define MIPI_PHY_TULPS_S       CSI2_REG_D(0x2c)
>>> +#define MIPI_DPHY_ULPS_START_CYCLES 0x100
>>> +
>>> +#define MIPI_PHY_TMBIAS             CSI2_REG_D(0x30)
>>> +#define MIPI_DPHY_MBIAS_CYCLES      0x100
>>> +
>>> +#define MIPI_PHY_TLP_EN_W           CSI2_REG_D(0x34)
>>> +#define MIPI_DPHY_ULPS_STOP_CYCLES  0xC
>>> +
>>> +#define MIPI_PHY_TLPOK                 CSI2_REG_D(0x38)
>>> +#define MIPI_DPHY_POWER_UP_CYCLES   0x100
>>> +
>>> +#define MIPI_PHY_TWD_INIT      CSI2_REG_D(0x3c)
>>> +#define MIPI_DPHY_INIT_WATCH_DOG    0x400000
>>> +
>>> +#define MIPI_PHY_TWD_HS             CSI2_REG_D(0x40)
>>> +#define MIPI_DPHY_HS_WATCH_DOG      0x400000
>>> +
>>> +#define MIPI_PHY_MUX_CTRL0     CSI2_REG_D(0x284)
>>> +#define MIPI_DPHY_LANE3_SELECT      GENMASK(3, 0)
>>> +#define MIPI_DPHY_LANE2_SELECT      GENMASK(7, 4)
>>> +#define MIPI_DPHY_LANE2_SELECT_SHIFT           4
>>> +#define MIPI_DPHY_LANE1_SELECT      GENMASK(11, 8)
>>> +#define MIPI_DPHY_LANE1_SELECT_SHIFT            8
>>> +#define MIPI_DPHY_LANE0_SELECT      GENMASK(14, 12)
>>> +
>>> +#define MIPI_PHY_MUX_CTRL1     CSI2_REG_D(0x288)
>>> +#define MIPI_DPHY_LANE3_CTRL_SIGNAL GENMASK(3, 0)
>>> +#define MIPI_DPHY_LANE2_CTRL_SIGNAL GENMASK(7, 4)
>>> +#define MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT      4
>>> +#define MIPI_DPHY_LANE1_CTRL_SIGNAL GENMASK(11, 8)
>>> +#define MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT       8
>>> +#define MIPI_DPHY_LANE0_CTRL_SIGNAL GENMASK(14, 12)
>>> +#define MIPI_DPHY_CLK_SELECT        BIT(17)
>>> +
>>> +/* C3 CSI-2 HOST register */
>>> +#define CSI2_HOST_N_LANES           CSI2_REG_H(0x04)
>>> +#define CSI2_HOST_CSI2_RESETN       CSI2_REG_H(0x10)
>>> +#define CSI2_HOST_RESETN_DEFAULT    0x0
>>> +#define CSI2_HOST_RESETN_RST_VALUE  BIT(0)
>>> +
>>> +#define CSI2_HOST_MASK1             CSI2_REG_H(0x28)
>>> +#define CSI2_HOST_ERROR_MASK1       GENMASK(28, 0)
>>> +
>>> +#define MIPI_CSI2_MAX_WIDTH         2888
>>> +#define MIPI_CSI2_MIN_WIDTH         160
>>> +#define MIPI_CSI2_MAX_HEIGHT        2240
>>> +#define MIPI_CSI2_MIN_HEIGHT        120
>>> +#define MIPI_CSI2_DEFAULT_WIDTH     1920
>>> +#define MIPI_CSI2_DEFAULT_HEIGHT    1080
>>> +#define MIPI_CSI2_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
>>> +
>>> +/* C3 CSI-2 pad list */
>>> +enum {
>>> +   MIPI_CSI2_PAD_SINK,
>>> +   MIPI_CSI2_PAD_SRC,
>>> +   MIPI_CSI2_PAD_MAX
>>> +};
>>> +
>>> +/**
>> You don't need to kernel-doc in-driver types and functions.
>> Documentation is always good, but this won't be parsed by kernel-doc
>> (afaiu) so you should drop one * from /**
>>
>>> + * struct csi_info - MIPI CSI2 information
>>> + *
>>> + * @clocks: array of MIPI CSI2 clock names
>>> + * @clock_rates: array of MIPI CSI2 clock rate
>>> + * @clock_num: actual clock number
>>> + */
>>> +struct csi_info {
>>> +   char *clocks[MIPI_CSI2_CLOCK_NUM_MAX];
>>> +   u32 clock_rates[MIPI_CSI2_CLOCK_NUM_MAX];
>>> +   u32 clock_num;
>>> +};
>>> +
>>> +/**
>>> + * struct csi_device - MIPI CSI2 platform device
>>> + *
>>> + * @dev: pointer to the struct device
>>> + * @aphy: MIPI CSI2 aphy register address
>>> + * @dphy: MIPI CSI2 dphy register address
>>> + * @host: MIPI CSI2 host register address
>>> + * @clks: array of MIPI CSI2 clocks
>>> + * @sd: MIPI CSI2 sub-device
>>> + * @pads: MIPI CSI2 sub-device pads
>>> + * @notifier: notifier to register on the v4l2-async API
>>> + * @src_sd: source sub-device
>>> + * @bus: MIPI CSI2 bus information
>>> + * @src_sd_pad: source sub-device pad
>>> + * @lock: protect MIPI CSI2 device
>>> + * @info: version-specific MIPI CSI2 information
>>> + */
>>> +struct csi_device {
>>> +   struct device *dev;
>>> +   void __iomem *aphy;
>>> +   void __iomem *dphy;
>>> +   void __iomem *host;
>>> +   struct clk_bulk_data clks[MIPI_CSI2_CLOCK_NUM_MAX];
>>> +
>>> +   struct v4l2_subdev sd;
>>> +   struct media_pad pads[MIPI_CSI2_PAD_MAX];
>>> +   struct v4l2_async_notifier notifier;
>>> +   struct v4l2_subdev *src_sd;
>>> +   struct v4l2_mbus_config_mipi_csi2 bus;
>>> +
>>> +   u16 src_sd_pad;
>>> +   struct mutex lock; /* Protect csi device */
>> All the operations which receive a subdev_state are guaranteed to be locked
>> so you can avoid manually locking in enable/disable streams
>> (and drop #include cleanup.h if you don't use guards in any other
>> place)
>>
>>> +   const struct csi_info *info;
>>> +};
>>> +
>>> +static const u32 c3_mipi_csi_formats[] = {
>>> +   MEDIA_BUS_FMT_SBGGR10_1X10,
>>> +   MEDIA_BUS_FMT_SGBRG10_1X10,
>>> +   MEDIA_BUS_FMT_SGRBG10_1X10,
>>> +   MEDIA_BUS_FMT_SRGGB10_1X10,
>>> +   MEDIA_BUS_FMT_SBGGR12_1X12,
>>> +   MEDIA_BUS_FMT_SGBRG12_1X12,
>>> +   MEDIA_BUS_FMT_SGRBG12_1X12,
>>> +   MEDIA_BUS_FMT_SRGGB12_1X12,
>>> +};
>>> +
>>> +/* Hardware configuration */
>>> +
>>> +static void c3_mipi_csi_write(struct csi_device *csi, u32 reg, u32 val)
>>> +{
>>> +   void __iomem *addr;
>>> +
>>> +   switch (CSI2_SUBMD(reg)) {
>>> +   case SUBMD_APHY:
>>> +           addr = csi->aphy + CSI2_REG_ADDR(reg);
>>> +           break;
>>> +   case SUBMD_DPHY:
>>> +           addr = csi->dphy + CSI2_REG_ADDR(reg);
>>> +           break;
>>> +   case SUBMD_HOST:
>>> +           addr = csi->host + CSI2_REG_ADDR(reg);
>>> +           break;
>>> +   default:
>>> +           dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
>>> +           return;
>>> +   }
>>> +
>>> +   writel(val, addr);
>>> +}
>>> +
>>> +static void c3_mipi_csi_update_bits(struct csi_device *csi, u32 reg,
>>> +                               u32 mask, u32 val)
>>> +{
>>> +   void __iomem *addr;
>>> +   u32 orig, tmp;
>>> +
>>> +   switch (CSI2_SUBMD(reg)) {
>>> +   case SUBMD_APHY:
>>> +           addr = csi->aphy + CSI2_REG_ADDR(reg);
>>> +           break;
>>> +   case SUBMD_DPHY:
>>> +           addr = csi->dphy + CSI2_REG_ADDR(reg);
>>> +           break;
>>> +   case SUBMD_HOST:
>>> +           addr = csi->host + CSI2_REG_ADDR(reg);
>>> +           break;
>>> +   default:
>>> +           dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
>>> +           return;
>>> +   }
>> This is repeated in two functions and could be grouped to a common
>> place. Up to you
>>
>>> +
>>> +   orig = readl(addr);
>>> +   tmp = orig & ~mask;
>>> +   tmp |= val & mask;
>>> +
>>> +   if (tmp != orig)
>>> +           writel(tmp, addr);
>>> +}
>>> +
>>> +static void c3_mipi_csi_cfg_aphy(struct csi_device *csi, u32 lanes)
>>> +{
>>> +   c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL1, MIPI_APHY_NORMAL_CNTL1);
>>> +
>>> +   if (lanes == 4)
>>> +           c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_4LANES_CNTL2);
>>> +   else
>>> +           c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_NORMAL_CNTL2);
>>> +
>>> +   if (lanes == 2)
>>> +           c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL3, MIPI_APHY_2LANES_CNTL3);
>> The driver seems to only accept 2 or 4 lanes. What is
>> MIPI_APHY_NORMAL_CNTL2 for ?
>>
>>> +}
>>> +
>>> +static void c3_mipi_csi_2lanes_setting(struct csi_device *csi)
>>> +{
>>> +   /* Disable lane 2 and lane 3 */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0xf);
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
>>> +                           0xf << MIPI_DPHY_LANE2_SELECT_SHIFT);
>>> +   /* Select analog data lane 1 */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
>>> +                           0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
>>> +   /* Select analog data lane 0 */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
>>> +
>>> +   /* Disable lane 2 and lane 3 control signal */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0xf);
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
>>> +                           0xf << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
>>> +   /* Select lane 1 signal */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
>>> +                           0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
>>> +   /* Select lane 0 signal */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
>>> +   /* Select input 0 as clock */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
>>> +                           MIPI_DPHY_CLK_SELECT);
>>> +}
>>> +
>>> +static void c3_mipi_csi_4lanes_setting(struct csi_device *csi)
>>> +{
>>> +   /* Select analog data lane 3 */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0x3);
>>> +   /* Select analog data lane 2 */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
>>> +                           0x2 << MIPI_DPHY_LANE2_SELECT_SHIFT);
>>> +   /* Select analog data lane 1 */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
>>> +                           0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
>>> +   /* Select analog data lane 0 */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
>>> +
>>> +   /* Select lane 3 signal */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0x3);
>>> +   /* Select lane 2 signal */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
>>> +                           0x2 << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
>>> +   /* Select lane 1 signal */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
>>> +                           0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
>>> +   /* Select lane 0 signal */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
>>> +   /* Select input 0 as clock */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
>>> +                           MIPI_DPHY_CLK_SELECT);
>>> +}
>>> +
>>> +static void c3_mipi_csi_cfg_dphy(struct csi_device *csi, u32 lanes, s64 rate)
>>> +{
>>> +   u32 val;
>>> +   u32 settle;
>>> +
>>> +   /* Calculate the high speed settle */
>>> +   val = DIV_ROUND_UP(1000000000, rate);
>>> +   settle = (16 * val + 230) / 10;
>>> +
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL, MIPI_DPHY_CLK_CONTINUE_MODE);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_TCLK_MISS, MIPI_DPHY_CLK_MISS);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_TCLK_SETTLE, MIPI_DPHY_CLK_SETTLE);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_THS_EXIT, MIPI_DPHY_HS_EXIT);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_THS_SKIP, MIPI_DPHY_HS_SKIP);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_THS_SETTLE, settle);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_TINIT, MIPI_DPHY_INIT_CYCLES);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_TMBIAS, MIPI_DPHY_MBIAS_CYCLES);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_TULPS_C, MIPI_DPHY_ULPS_CHECK_CYCLES);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_TULPS_S, MIPI_DPHY_ULPS_START_CYCLES);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_TLP_EN_W, MIPI_DPHY_ULPS_STOP_CYCLES);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_TLPOK, MIPI_DPHY_POWER_UP_CYCLES);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_TWD_INIT, MIPI_DPHY_INIT_WATCH_DOG);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_TWD_HS, MIPI_DPHY_HS_WATCH_DOG);
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_DATA_LANE_CTRL, MIPI_DPHY_LANE_CTRL_DISABLE);
>>> +
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_INSERT_ERRESC,
>>> +                           MIPI_DPHY_INSERT_ERRESC);
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_HS_SYNC_CHECK,
>>> +                           MIPI_DPHY_HS_SYNC_CHECK);
>>> +   /*
>>> +    * Set 5 pipe lines to the same high speed.
>>> +    * Each bit for one pipe line.
>>> +    */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_FIVE_HS_PIPE,
>>> +                           0x1f << MIPI_DPHY_FIVE_HS_PIPE_SHIFT);
>>> +
>>> +   /* Output data with pipe line data. */
>>> +   c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_DATA_PIPE_SELECT,
>>> +                           0x3 << MIPI_DPHY_DATA_PIPE_SELECT_SHIFT);
>> Would it be possible to provide a definition for these 0x1f and 0x3
>> values ?
>>
>>> +   if (lanes == 2)
>>> +           c3_mipi_csi_2lanes_setting(csi);
>>> +   else
>>> +           c3_mipi_csi_4lanes_setting(csi);
>>> +
>>> +   /* Enable digital data and clock lanes */
>>> +   c3_mipi_csi_write(csi, MIPI_PHY_CTRL, MIPI_DPHY_LANES_ENABLE);
>>> +}
>>> +
>>> +static void c3_mipi_csi_cfg_host(struct csi_device *csi, u32 lanes)
>>> +{
>>> +   /* Reset CSI-2 controller output */
>>> +   c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_DEFAULT);
>>> +   c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_RST_VALUE);
>>> +
>>> +   /* Set data lane number */
>>> +   c3_mipi_csi_write(csi, CSI2_HOST_N_LANES, lanes - 1);
>>> +
>>> +   /* Enable error mask */
>>> +   c3_mipi_csi_write(csi, CSI2_HOST_MASK1, CSI2_HOST_ERROR_MASK1);
>>> +}
>>> +
>>> +static int c3_mipi_csi_start_stream(struct csi_device *csi)
>>> +{
>>> +   s64 link_freq;
>>> +   s64 lane_rate;
>>> +
>>> +   link_freq = v4l2_get_link_freq(csi->src_sd->ctrl_handler, 0, 0);
>>> +   if (link_freq < 0) {
>>> +           dev_err(csi->dev, "Unable to obtain link frequency: %lld\n", link_freq);
>>> +           return link_freq;
>>> +   }
>>> +
>>> +   lane_rate = link_freq * 2;
>>> +   if (lane_rate > 1500000000)
>> I would dev_err here too
>>
>>> +           return -EINVAL;
>>> +
>>> +   c3_mipi_csi_cfg_aphy(csi, csi->bus.num_data_lanes);
>>> +   c3_mipi_csi_cfg_dphy(csi, csi->bus.num_data_lanes, lane_rate);
>>> +   c3_mipi_csi_cfg_host(csi, csi->bus.num_data_lanes);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_mipi_csi_enable_streams(struct v4l2_subdev *sd,
>>> +                                 struct v4l2_subdev_state *state,
>>> +                                 u32 pad, u64 streams_mask)
>>> +{
>>> +   struct csi_device *csi = v4l2_get_subdevdata(sd);
>>> +   u64 sink_streams;
>>> +   int ret;
>>> +
>>> +   guard(mutex)(&csi->lock);
>>> +
>>> +   pm_runtime_resume_and_get(csi->dev);
>>> +
>>> +   c3_mipi_csi_start_stream(csi);
>>> +
>>> +   sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>> +                                                  MIPI_CSI2_PAD_SINK,
>>> +                                                  &streams_mask);
>>> +   ret = v4l2_subdev_enable_streams(csi->src_sd,
>>> +                                    csi->src_sd_pad,
>>> +                                    sink_streams);
>>> +   if (ret) {
>>> +           pm_runtime_put(csi->dev);
>>> +           return ret;
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_mipi_csi_disable_streams(struct v4l2_subdev *sd,
>>> +                                  struct v4l2_subdev_state *state,
>>> +                                  u32 pad, u64 streams_mask)
>>> +{
>>> +   struct csi_device *csi = v4l2_get_subdevdata(sd);
>>> +   u64 sink_streams;
>>> +   int ret;
>>> +
>>> +   guard(mutex)(&csi->lock);
>>> +
>>> +   sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>> +                                                  MIPI_CSI2_PAD_SINK,
>>> +                                                  &streams_mask);
>>> +   ret = v4l2_subdev_disable_streams(csi->src_sd,
>>> +                                     csi->src_sd_pad,
>>> +                                     sink_streams);
>>> +   if (ret)
>>> +           dev_err(csi->dev, "Failed to disable %s\n", csi->src_sd->name);
>>> +
>>> +   pm_runtime_put(csi->dev);
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static int c3_mipi_csi_cfg_routing(struct v4l2_subdev *sd,
>>> +                              struct v4l2_subdev_state *state,
>>> +                              struct v4l2_subdev_krouting *routing)
>>> +{
>>> +   static const struct v4l2_mbus_framefmt format = {
>>> +           .width = MIPI_CSI2_DEFAULT_WIDTH,
>>> +           .height = MIPI_CSI2_DEFAULT_HEIGHT,
>>> +           .code = MIPI_CSI2_DEFAULT_FMT,
>>> +           .field = V4L2_FIELD_NONE,
>>> +           .colorspace = V4L2_COLORSPACE_RAW,
>>> +           .ycbcr_enc = V4L2_YCBCR_ENC_601,
>>> +           .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>> I presume for Raw Bayer data the quantization range is full ?
>>
>>> +           .xfer_func = V4L2_XFER_FUNC_NONE,
>>> +   };
>>> +   int ret;
>>> +
>>> +   ret = v4l2_subdev_routing_validate(sd, routing,
>>> +                                      V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>>> +   if (ret)
>>> +           return ret;
>> You should validate that the provided routing table matches what the
>> driver supports, so only [0/0]->[1/0]
>>
>> Now that I've said so, if the routing table is not modifiable I wonder
>> if you should support set_routing() at all, or it could be left out
>> until you don't add support for more streams to the driver.
>>
>> After all this driver implements support for routing but doesn't set
>> the V4L2_SUBDEV_FL_STREAMS flag, so the operation is disallowed from
>> userspace for now.
>>
>>> +
>>> +   ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_mipi_csi_init_routing(struct v4l2_subdev *sd,
>>> +                               struct v4l2_subdev_state *state)
>>> +{
>>> +   struct v4l2_subdev_route routes;
>>> +   struct v4l2_subdev_krouting routing;
>>> +
>>> +   routes.sink_pad = MIPI_CSI2_PAD_SINK;
>>> +   routes.sink_stream = 0;
>>> +   routes.source_pad = MIPI_CSI2_PAD_SRC;
>>> +   routes.source_stream = 0;
>>> +   routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>>> +
>>> +   routing.num_routes = 1;
>>> +   routing.routes = &routes;
>>> +
>>> +   return c3_mipi_csi_cfg_routing(sd, state, &routing);
>>> +}
>>> +
>>> +static int c3_mipi_csi_set_routing(struct v4l2_subdev *sd,
>>> +                              struct v4l2_subdev_state *state,
>>> +                              enum v4l2_subdev_format_whence which,
>>> +                              struct v4l2_subdev_krouting *routing)
>>> +{
>>> +   bool is_streaming = v4l2_subdev_is_streaming(sd);
>>> +
>>> +   if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>>> +           return -EBUSY;
>>> +
>>> +   return c3_mipi_csi_cfg_routing(sd, state, routing);
>>> +}
>>> +
>>> +static int c3_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
>>> +                                 struct v4l2_subdev_state *state,
>>> +                                 struct v4l2_subdev_mbus_code_enum *code)
>>> +{
>>> +   switch (code->pad) {
>>> +   case MIPI_CSI2_PAD_SINK:
>>> +           if (code->index >= ARRAY_SIZE(c3_mipi_csi_formats))
>>> +                   return -EINVAL;
>>> +
>>> +           code->code = c3_mipi_csi_formats[code->index];
>>> +           break;
>>> +   case MIPI_CSI2_PAD_SRC:
>>> +           struct v4l2_mbus_framefmt *fmt;
>>> +
>>> +           if (code->index > 0)
>>> +                   return -EINVAL;
>>> +
>>> +           fmt = v4l2_subdev_state_get_format(state, code->pad);
>>> +           code->code = fmt->code;
>>> +           break;
>> I'm not sure if the V4L2 API specify that the formats on a pad should
>> be enumerated in full, regardless of the configuration, or like you're
>> doing here reflect the subdev configuration. I like what you have here
>> more, so unless someone screams I think it's fine.
>>
>>> +   default:
>>> +           return -EINVAL;
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_mipi_csi_set_fmt(struct v4l2_subdev *sd,
>>> +                          struct v4l2_subdev_state *state,
>>> +                          struct v4l2_subdev_format *format)
>>> +{
>>> +   struct v4l2_mbus_framefmt *fmt;
>>> +   unsigned int i;
>>> +
>>> +   if (format->pad != MIPI_CSI2_PAD_SINK)
>>> +           return v4l2_subdev_get_fmt(sd, state, format);
>>> +
>>> +   fmt = v4l2_subdev_state_get_format(state, format->pad);
>> Could you clarify what other streams you plan to support ? As you
>> support routing I presume you are preparing to capture
>> multiple streams of data like image + embedded data, or to support
>> sensors which sends data on different virtual channels ?
>>
>> How do you see this driver evolve ? Will it be augmented with an
>> additional source pad directed to a video device where to capture
>> embedded data from ?
>>
>> I'm wondering because if PAD_SINK become multiplexed, you won't be
>> allowed to set a format there. It only works now because you have a
>> single stream.
> Pardon me, I've been made to notice the stream API allows userspace to
> set a format on a [pad/stream] combination, so the above statement is
> not correct: you will still be allowed to set a format a multiplexed
> sink pad.
>
> Knowing however how you plan to expand this to support multiple
> streams is still something I would be interested in :)
>
> Thanks
>    j
>

Thanks for your suggestion.

But this MIPI CSI2 hardware module doesn't have the ability to separate
data , such as image + embedded data.

So there are no plans to support other streams.

>> Speaking of which, as you prepare to support multiple streams, I would
>> specify the stream number when calling v4l2_subdev_state_get_format().
>>
>>        fmt = v4l2_subdev_state_get_format(state, format->pad, 0);
>>
>>> +
>>> +   for (i = 0; i < ARRAY_SIZE(c3_mipi_csi_formats); i++)
>>> +           if (format->format.code == c3_mipi_csi_formats[i])
>>> +                   break;
>> nit: please use {} for the for loop
>>
>>> +
>>> +   if (i == ARRAY_SIZE(c3_mipi_csi_formats))
>>> +           fmt->code = c3_mipi_csi_formats[0];
>>> +   else
>>> +           fmt->code = c3_mipi_csi_formats[i];
>> You could set this in the for loop, before breaking.
>>
>>> +
>>> +   fmt->width = clamp_t(u32, format->format.width,
>>> +                        MIPI_CSI2_MIN_WIDTH, MIPI_CSI2_MAX_WIDTH);
>>> +   fmt->height = clamp_t(u32, format->format.height,
>>> +                         MIPI_CSI2_MIN_HEIGHT, MIPI_CSI2_MAX_HEIGHT);
>>> +
>> You should set the colorspace related information too, as you
>> initialize them, similar to what you do in init_state()
>>
>>> +   format->format = *fmt;
>>> +
>>> +   /* Synchronize the format to source pad */
>>> +   fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
>>> +   *fmt = format->format;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_mipi_csi_init_state(struct v4l2_subdev *sd,
>>> +                             struct v4l2_subdev_state *state)
>>> +{
>>> +   struct v4l2_mbus_framefmt *sink_fmt;
>>> +   struct v4l2_mbus_framefmt *src_fmt;
>>> +
>>> +   sink_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SINK);
>>> +   src_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
>>> +
>>> +   sink_fmt->width = MIPI_CSI2_DEFAULT_WIDTH;
>>> +   sink_fmt->height = MIPI_CSI2_DEFAULT_HEIGHT;
>>> +   sink_fmt->field = V4L2_FIELD_NONE;
>>> +   sink_fmt->code = MIPI_CSI2_DEFAULT_FMT;
>>> +   sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>>> +   sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
>>> +   sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
>>> +   sink_fmt->quantization =
>>> +           V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
>> If you could initialize them like you do above, with specific values
>> instead of using _DEFAULT() I think it's better.
>>
>>> +                                         sink_fmt->ycbcr_enc);
>>> +   *src_fmt = *sink_fmt;
>>> +
>>> +   return c3_mipi_csi_init_routing(sd, state);
>>> +}
>>> +
>>> +static const struct v4l2_subdev_pad_ops c3_mipi_csi_pad_ops = {
>>> +   .enum_mbus_code = c3_mipi_csi_enum_mbus_code,
>>> +   .get_fmt = v4l2_subdev_get_fmt,
>>> +   .set_fmt = c3_mipi_csi_set_fmt,
>>> +   .set_routing = c3_mipi_csi_set_routing,
>>> +   .enable_streams = c3_mipi_csi_enable_streams,
>>> +   .disable_streams = c3_mipi_csi_disable_streams,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_ops c3_mipi_csi_subdev_ops = {
>>> +   .pad = &c3_mipi_csi_pad_ops,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_internal_ops c3_mipi_csi_internal_ops = {
>>> +   .init_state = c3_mipi_csi_init_state,
>>> +};
>>> +
>>> +/* Media entity operations */
>>> +static const struct media_entity_operations c3_mipi_csi_entity_ops = {
>>> +   .link_validate = v4l2_subdev_link_validate,
>>> +};
>>> +
>>> +/* PM runtime */
>>> +
>>> +static int __maybe_unused c3_mipi_csi_runtime_suspend(struct device *dev)
>>> +{
>>> +   struct csi_device *csi = dev_get_drvdata(dev);
>>> +
>>> +   clk_bulk_disable_unprepare(csi->info->clock_num, csi->clks);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int __maybe_unused c3_mipi_csi_runtime_resume(struct device *dev)
>>> +{
>>> +   struct csi_device *csi = dev_get_drvdata(dev);
>>> +
>>> +   return clk_bulk_prepare_enable(csi->info->clock_num, csi->clks);
>>> +}
>>> +
>>> +static const struct dev_pm_ops c3_mipi_csi_pm_ops = {
>>> +   SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>>> +                           pm_runtime_force_resume)
>>> +   SET_RUNTIME_PM_OPS(c3_mipi_csi_runtime_suspend,
>>> +                      c3_mipi_csi_runtime_resume, NULL)
>> You could use SYSTEM_SLEEP_PM_OPS and RUNTIME_PM_OPS and set
>>
>>                .pm = pm_ptr(&c3_mipi_csi_pm_ops),
>>
>> to avoid __maybe_unused in the functions, up to you
>>
>>> +};
>>> +
>>> +/* Probe/remove & platform driver */
>>> +
>>> +static int c3_mipi_csi_subdev_init(struct csi_device *csi)
>>> +{
>>> +   struct v4l2_subdev *sd = &csi->sd;
>>> +   int ret;
>>> +
>>> +   v4l2_subdev_init(sd, &c3_mipi_csi_subdev_ops);
>>> +   sd->owner = THIS_MODULE;
>>> +   sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>> +   sd->internal_ops = &c3_mipi_csi_internal_ops;
>>> +   snprintf(sd->name, sizeof(sd->name), "%s", MIPI_CSI2_SUBDEV_NAME);
>>> +
>>> +   sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>>> +   sd->entity.ops = &c3_mipi_csi_entity_ops;
>>> +
>>> +   sd->dev = csi->dev;
>>> +   v4l2_set_subdevdata(sd, csi);
>>> +
>>> +   csi->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>>> +   csi->pads[MIPI_CSI2_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
>>> +   ret = media_entity_pads_init(&sd->entity, MIPI_CSI2_PAD_MAX, csi->pads);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   ret = v4l2_subdev_init_finalize(sd);
>>> +   if (ret) {
>>> +           media_entity_cleanup(&sd->entity);
>>> +           return ret;
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static void c3_mipi_csi_subdev_deinit(struct csi_device *csi)
>>> +{
>>> +   v4l2_subdev_cleanup(&csi->sd);
>>> +   media_entity_cleanup(&csi->sd.entity);
>>> +}
>>> +
>>> +/* Subdev notifier register */
>>> +static int c3_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier,
>>> +                               struct v4l2_subdev *sd,
>>> +                               struct v4l2_async_connection *asc)
>>> +{
>>> +   struct csi_device *csi = v4l2_get_subdevdata(notifier->sd);
>>> +   struct media_pad *sink = &csi->sd.entity.pads[MIPI_CSI2_PAD_SINK];
>>> +   int ret;
>>> +
>>> +   ret = media_entity_get_fwnode_pad(&sd->entity,
>>> +                                     sd->fwnode, MEDIA_PAD_FL_SOURCE);
>>> +   if (ret < 0) {
>>> +           dev_err(csi->dev, "Failed to find pad for %s\n", sd->name);
>>> +           return ret;
>>> +   }
>>> +
>>> +   csi->src_sd = sd;
>>> +   csi->src_sd_pad = ret;
>>> +
>>> +   return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
>>> +                                          MEDIA_LNK_FL_IMMUTABLE);
>>> +}
>>> +
>>> +static const struct v4l2_async_notifier_operations c3_mipi_csi_notify_ops = {
>>> +   .bound = c3_mipi_csi_notify_bound,
>>> +};
>>> +
>>> +static int c3_mipi_csi_async_register(struct csi_device *csi)
>>> +{
>>> +   struct v4l2_fwnode_endpoint vep = {
>>> +           .bus_type = V4L2_MBUS_CSI2_DPHY,
>>> +   };
>>> +   struct v4l2_async_connection *asc;
>>> +   struct fwnode_handle *ep;
>>> +   int ret;
>>> +
>>> +   v4l2_async_subdev_nf_init(&csi->notifier, &csi->sd);
>>> +
>>> +   ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0,
>>> +                                        FWNODE_GRAPH_ENDPOINT_NEXT);
>>> +   if (!ep)
>>> +           return -ENOTCONN;
>>> +
>>> +   ret = v4l2_fwnode_endpoint_parse(ep, &vep);
>>> +   if (ret)
>>> +           goto err_put_handle;
>>> +
>>> +   csi->bus = vep.bus.mipi_csi2;
>>> +   if (csi->bus.num_data_lanes != 2 && csi->bus.num_data_lanes != 4)
>>> +           goto err_put_handle;
>> I would dev_err() here
>>
>> Thanks
>>     j
>>
>>> +
>>> +   asc = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep,
>>> +                                         struct v4l2_async_connection);
>>> +   if (IS_ERR(asc)) {
>>> +           ret = PTR_ERR(asc);
>>> +           goto err_put_handle;
>>> +   }
>>> +
>>> +   csi->notifier.ops = &c3_mipi_csi_notify_ops;
>>> +   ret = v4l2_async_nf_register(&csi->notifier);
>>> +   if (ret)
>>> +           goto err_cleanup_nf;
>>> +
>>> +   ret = v4l2_async_register_subdev(&csi->sd);
>>> +   if (ret)
>>> +           goto err_unregister_nf;
>>> +
>>> +   fwnode_handle_put(ep);
>>> +
>>> +   return 0;
>>> +
>>> +err_unregister_nf:
>>> +   v4l2_async_nf_unregister(&csi->notifier);
>>> +err_cleanup_nf:
>>> +   v4l2_async_nf_cleanup(&csi->notifier);
>>> +err_put_handle:
>>> +   fwnode_handle_put(ep);
>>> +   return ret;
>>> +}
>>> +
>>> +static void c3_mipi_csi_async_unregister(struct csi_device *csi)
>>> +{
>>> +   v4l2_async_unregister_subdev(&csi->sd);
>>> +   v4l2_async_nf_unregister(&csi->notifier);
>>> +   v4l2_async_nf_cleanup(&csi->notifier);
>>> +}
>>> +
>>> +static int c3_mipi_csi_ioremap_resource(struct csi_device *csi)
>>> +{
>>> +   struct device *dev = csi->dev;
>>> +   struct platform_device *pdev = to_platform_device(dev);
>>> +
>>> +   csi->aphy = devm_platform_ioremap_resource_byname(pdev, "aphy");
>>> +   if (IS_ERR(csi->aphy))
>>> +           return PTR_ERR(csi->aphy);
>>> +
>>> +   csi->dphy = devm_platform_ioremap_resource_byname(pdev, "dphy");
>>> +   if (IS_ERR(csi->dphy))
>>> +           return PTR_ERR(csi->dphy);
>>> +
>>> +   csi->host = devm_platform_ioremap_resource_byname(pdev, "host");
>>> +   if (IS_ERR(csi->host))
>>> +           return PTR_ERR(csi->host);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_mipi_csi_configure_clocks(struct csi_device *csi)
>>> +{
>>> +   const struct csi_info *info = csi->info;
>>> +   int ret;
>>> +   u32 i;
>>> +
>>> +   for (i = 0; i < info->clock_num; i++)
>>> +           csi->clks[i].id = info->clocks[i];
>>> +
>>> +   ret = devm_clk_bulk_get(csi->dev, info->clock_num, csi->clks);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   for (i = 0; i < info->clock_num; i++) {
>>> +           if (!info->clock_rates[i])
>>> +                   continue;
>>> +           ret = clk_set_rate(csi->clks[i].clk, info->clock_rates[i]);
>>> +           if (ret) {
>>> +                   dev_err(csi->dev, "Failed to set %s rate %u\n", info->clocks[i],
>>> +                           info->clock_rates[i]);
>>> +                   return ret;
>>> +           }
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_mipi_csi_probe(struct platform_device *pdev)
>>> +{
>>> +   struct device *dev = &pdev->dev;
>>> +   struct csi_device *csi;
>>> +   int ret;
>>> +
>>> +   csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
>>> +   if (!csi)
>>> +           return -ENOMEM;
>>> +
>>> +   csi->info = of_device_get_match_data(dev);
>>> +   csi->dev = dev;
>>> +
>>> +   ret = c3_mipi_csi_ioremap_resource(csi);
>>> +   if (ret)
>>> +           return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
>>> +
>>> +   ret = c3_mipi_csi_configure_clocks(csi);
>>> +   if (ret)
>>> +           return dev_err_probe(dev, ret, "Failed to configure clocks\n");
>>> +
>>> +   platform_set_drvdata(pdev, csi);
>>> +
>>> +   mutex_init(&csi->lock);
>>> +   pm_runtime_enable(dev);
>>> +
>>> +   ret = c3_mipi_csi_subdev_init(csi);
>>> +   if (ret)
>>> +           goto err_disable_runtime_pm;
>>> +
>>> +   ret = c3_mipi_csi_async_register(csi);
>>> +   if (ret)
>>> +           goto err_deinit_subdev;
>>> +
>>> +   return 0;
>>> +
>>> +err_deinit_subdev:
>>> +   c3_mipi_csi_subdev_deinit(csi);
>>> +err_disable_runtime_pm:
>>> +   pm_runtime_disable(dev);
>>> +   mutex_destroy(&csi->lock);
>>> +   return ret;
>>> +};
>>> +
>>> +static void c3_mipi_csi_remove(struct platform_device *pdev)
>>> +{
>>> +   struct csi_device *csi = platform_get_drvdata(pdev);
>>> +
>>> +   c3_mipi_csi_async_unregister(csi);
>>> +   c3_mipi_csi_subdev_deinit(csi);
>>> +
>>> +   pm_runtime_disable(&pdev->dev);
>>> +   mutex_destroy(&csi->lock);
>>> +};
>>> +
>>> +static const struct csi_info c3_mipi_csi_info = {
>>> +   .clocks = {"vapb", "phy0"},
>>> +   .clock_rates = {0, 200000000},
>>> +   .clock_num = 2
>>> +};
>>> +
>>> +static const struct of_device_id c3_mipi_csi_of_match[] = {
>>> +   { .compatible = "amlogic,c3-mipi-csi2",
>>> +     .data = &c3_mipi_csi_info,
>>> +   },
>>> +   { },
>>> +};
>>> +MODULE_DEVICE_TABLE(of, c3_mipi_csi_of_match);
>>> +
>>> +static struct platform_driver c3_mipi_csi_driver = {
>>> +   .probe = c3_mipi_csi_probe,
>>> +   .remove = c3_mipi_csi_remove,
>>> +   .driver = {
>>> +           .name = "c3-mipi-csi2",
>>> +           .of_match_table = c3_mipi_csi_of_match,
>>> +           .pm = &c3_mipi_csi_pm_ops,
>>> +   },
>>> +};
>>> +
>>> +module_platform_driver(c3_mipi_csi_driver);
>>> +
>>> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
>>> +MODULE_DESCRIPTION("Amlogic C3 MIPI CSI-2 receiver");
>>> +MODULE_LICENSE("GPL");
>>>
>>> --
>>> 2.46.1
>>>
>>>
>>>

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

* Re: [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver
  2024-11-05 11:39       ` Jacopo Mondi
@ 2024-11-05 11:58         ` Keke Li
  2024-11-05 12:08         ` Tomi Valkeinen
  1 sibling, 0 replies; 37+ messages in thread
From: Keke Li @ 2024-11-05 11:58 UTC (permalink / raw)
  To: Jacopo Mondi, Tomi Valkeinen
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally


On 2024/11/5 19:39, Jacopo Mondi wrote:
> [You don't often get email from jacopo.mondi@ideasonboard.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>
> [ EXTERNAL EMAIL ]
>
> Hi Keke
>
> On Tue, Nov 05, 2024 at 07:21:36PM +0800, Keke Li wrote:
>> Hi Jacopo
>>
>> Thanks very much for your reply.
>>
>> On 2024/11/5 01:50, Jacopo Mondi wrote:
>>> [You don't often get email from jacopo.mondi@ideasonboard.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>>>
>>> [ EXTERNAL EMAIL ]
>>>
>>> Hi Keke
>>>      sorry for the late feedback, hope you're still interested in
>>> upstreaming this driver
>>>
>>> On Wed, Sep 18, 2024 at 02:07:13PM +0800, Keke Li via B4 Relay wrote:
>>>> From: Keke Li <keke.li@amlogic.com>
>>>>
>>>> This driver is used to receive mipi data from image sensor.
>>>>
>>>> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
>>>> Signed-off-by: Keke Li <keke.li@amlogic.com>
>>>> ---
>>>>    MAINTAINERS                                        |   7 +
>>>>    drivers/media/platform/amlogic/Kconfig             |   1 +
>>>>    drivers/media/platform/amlogic/Makefile            |   2 +
>>>>    .../media/platform/amlogic/c3-mipi-csi2/Kconfig    |  16 +
>>>>    .../media/platform/amlogic/c3-mipi-csi2/Makefile   |   3 +
>>>>    .../platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c   | 910 +++++++++++++++++++++
>>>>    6 files changed, 939 insertions(+)
>>>>
>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>> index 2cdd7cacec86..9e75874a6e69 100644
>>>> --- a/MAINTAINERS
>>>> +++ b/MAINTAINERS
>>>> @@ -1209,6 +1209,13 @@ F:     Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
>>>>    F:   drivers/perf/amlogic/
>>>>    F:   include/soc/amlogic/
>>>>
>>>> +AMLOGIC MIPI CSI2 DRIVER
>>>> +M:   Keke Li <keke.li@amlogic.com>
>>>> +L:   linux-media@vger.kernel.org
>>>> +S:   Maintained
>>>> +F:   Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml
>>>> +F:   drivers/media/platform/amlogic/c3-mipi-csi2/
>>>> +
>>>>    AMPHENOL CHIPCAP 2 HUMIDITY-TEMPERATURE IIO DRIVER
>>>>    M:   Javier Carrasco <javier.carrasco.cruz@gmail.com>
>>>>    L:   linux-hwmon@vger.kernel.org
>>>> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
>>>> index 5014957404e9..b7c2de14848b 100644
>>>> --- a/drivers/media/platform/amlogic/Kconfig
>>>> +++ b/drivers/media/platform/amlogic/Kconfig
>>>> @@ -2,4 +2,5 @@
>>>>
>>>>    comment "Amlogic media platform drivers"
>>>>
>>>> +source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>>>>    source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
>>>> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
>>>> index d3cdb8fa4ddb..4f571ce5d13e 100644
>>>> --- a/drivers/media/platform/amlogic/Makefile
>>>> +++ b/drivers/media/platform/amlogic/Makefile
>>>> @@ -1,2 +1,4 @@
>>>>    # SPDX-License-Identifier: GPL-2.0-only
>>>> +
>>>> +obj-y += c3-mipi-csi2/
>>>>    obj-y += meson-ge2d/
>>>> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
>>>> new file mode 100644
>>>> index 000000000000..0d7b2e203273
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig
>>>> @@ -0,0 +1,16 @@
>>>> +# SPDX-License-Identifier: GPL-2.0-only
>>>> +
>>>> +config VIDEO_C3_MIPI_CSI2
>>>> +     tristate "Amlogic C3 MIPI CSI-2 receiver"
>>>> +     depends on ARCH_MESON || COMPILE_TEST
>>>> +     depends on VIDEO_DEV
>>>> +     depends on OF
>>>> +     select MEDIA_CONTROLLER
>>>> +     select V4L2_FWNODE
>>>> +     select VIDEO_V4L2_SUBDEV_API
>>>> +     help
>>>> +       Video4Linux2 driver for Amlogic C3 MIPI CSI-2 receiver.
>>>> +       C3 MIPI CSI-2 receiver is used to receive MIPI data from
>>>> +       image sensor.
>>>> +
>>>> +       To compile this driver as a module choose m here.
>>>> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
>>>> new file mode 100644
>>>> index 000000000000..cc08fc722bfd
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/Makefile
>>>> @@ -0,0 +1,3 @@
>>>> +# SPDX-License-Identifier: GPL-2.0-only
>>>> +
>>>> +obj-$(CONFIG_VIDEO_C3_MIPI_CSI2) += c3-mipi-csi2.o
>>>> diff --git a/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
>>>> new file mode 100644
>>>> index 000000000000..6ac60d5b26a8
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c
>>>> @@ -0,0 +1,910 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#include <linux/cleanup.h>
>>>> +#include <linux/clk.h>
>>>> +#include <linux/device.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/mutex.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +
>>>> +#include <media/v4l2-async.h>
>>>> +#include <media/v4l2-common.h>
>>>> +#include <media/v4l2-device.h>
>>>> +#include <media/v4l2-fwnode.h>
>>>> +#include <media/v4l2-mc.h>
>>>> +#include <media/v4l2-subdev.h>
>>>> +
>>>> +/* C3 CSI-2 submodule definition */
>>>> +enum {
>>>> +     SUBMD_APHY,
>>>> +     SUBMD_DPHY,
>>>> +     SUBMD_HOST,
>>>> +};
>>>> +
>>>> +#define CSI2_SUBMD_MASK             GENMASK(17, 16)
>>>> +#define CSI2_SUBMD_SHIFT            16
>>>> +#define CSI2_SUBMD(x)               (((x) & (CSI2_SUBMD_MASK)) >> (CSI2_SUBMD_SHIFT))
>>>> +#define CSI2_REG_ADDR_MASK          GENMASK(15, 0)
>>>> +#define CSI2_REG_ADDR(x)            ((x) & (CSI2_REG_ADDR_MASK))
>>>> +#define CSI2_REG_A(x)               ((SUBMD_APHY << CSI2_SUBMD_SHIFT) | (x))
>>>> +#define CSI2_REG_D(x)               ((SUBMD_DPHY << CSI2_SUBMD_SHIFT) | (x))
>>>> +#define CSI2_REG_H(x)               ((SUBMD_HOST << CSI2_SUBMD_SHIFT) | (x))
>>>> +
>>>> +#define MIPI_CSI2_CLOCK_NUM_MAX     3
>>>> +#define MIPI_CSI2_SUBDEV_NAME       "mipi-csi2"
>>> Isn't the name too generic ? Should it report at least "c3-mipi-csi2"
>>> ?
>> Will modify the name with  "C3_MIPI_CSI2".
>>>> +
>>>> +/* C3 CSI-2 APHY register */
>>>> +#define MIPI_CSI_2M_PHY2_CNTL1      CSI2_REG_A(0x44)
>>>> +#define MIPI_APHY_NORMAL_CNTL1      0x3f425C00
>>> All other hex addresses use small capitals for letters
>> OK, will use small capitals for letters.
>>>> +
>>>> +#define MIPI_CSI_2M_PHY2_CNTL2      CSI2_REG_A(0x48)
>>>> +#define MIPI_APHY_4LANES_CNTL2      0x033a0000
>>>> +#define MIPI_APHY_NORMAL_CNTL2      0x333a0000
>>>> +
>>>> +#define MIPI_CSI_2M_PHY2_CNTL3      CSI2_REG_A(0x4c)
>>>> +#define MIPI_APHY_2LANES_CNTL3      0x03800000
>>>> +
>>>> +/* C3 CSI-2 DPHY register */
>>>> +#define MIPI_PHY_CTRL                    CSI2_REG_D(0x00)
>>>> +#define MIPI_DPHY_LANES_ENABLE      0x0
>>>> +
>>>> +#define MIPI_PHY_CLK_LANE_CTRL           CSI2_REG_D(0x04)
>>>> +#define MIPI_DPHY_CLK_CONTINUE_MODE 0x3d8
>>>> +
>>>> +#define MIPI_PHY_DATA_LANE_CTRL     CSI2_REG_D(0x08)
>>>> +#define MIPI_DPHY_LANE_CTRL_DISABLE 0x0
>>>> +
>>>> +#define MIPI_PHY_DATA_LANE_CTRL1    CSI2_REG_D(0x0c)
>>>> +#define MIPI_DPHY_INSERT_ERRESC     BIT(0)
>>>> +#define MIPI_DPHY_HS_SYNC_CHECK     BIT(1)
>>>> +#define MIPI_DPHY_FIVE_HS_PIPE      GENMASK(6, 2)
>>>> +#define MIPI_DPHY_FIVE_HS_PIPE_SHIFT           2
>>>> +#define MIPI_DPHY_DATA_PIPE_SELECT  GENMASK(9, 7)
>>>> +#define MIPI_DPHY_DATA_PIPE_SELECT_SHIFT       7
>>>> +
>>>> +#define MIPI_PHY_TCLK_MISS       CSI2_REG_D(0x10)
>>>> +#define MIPI_DPHY_CLK_MISS          0x9
>>>> +
>>>> +#define MIPI_PHY_TCLK_SETTLE     CSI2_REG_D(0x14)
>>>> +#define MIPI_DPHY_CLK_SETTLE        0x1F
>>>> +
>>>> +#define MIPI_PHY_THS_EXIT        CSI2_REG_D(0x18)
>>>> +#define MIPI_DPHY_HS_EXIT           0x8
>>>> +
>>>> +#define MIPI_PHY_THS_SKIP        CSI2_REG_D(0x1c)
>>>> +#define MIPI_DPHY_HS_SKIP           0xa
>>>> +
>>>> +#define MIPI_PHY_THS_SETTLE      CSI2_REG_D(0x20)
>>>> +#define MIPI_PHY_TINIT                   CSI2_REG_D(0x24)
>>>> +#define MIPI_DPHY_INIT_CYCLES       0x4e20
>>>> +
>>>> +#define MIPI_PHY_TULPS_C         CSI2_REG_D(0x28)
>>>> +#define MIPI_DPHY_ULPS_CHECK_CYCLES 0x1000
>>>> +
>>>> +#define MIPI_PHY_TULPS_S         CSI2_REG_D(0x2c)
>>>> +#define MIPI_DPHY_ULPS_START_CYCLES 0x100
>>>> +
>>>> +#define MIPI_PHY_TMBIAS             CSI2_REG_D(0x30)
>>>> +#define MIPI_DPHY_MBIAS_CYCLES      0x100
>>>> +
>>>> +#define MIPI_PHY_TLP_EN_W           CSI2_REG_D(0x34)
>>>> +#define MIPI_DPHY_ULPS_STOP_CYCLES  0xC
>>>> +
>>>> +#define MIPI_PHY_TLPOK                   CSI2_REG_D(0x38)
>>>> +#define MIPI_DPHY_POWER_UP_CYCLES   0x100
>>>> +
>>>> +#define MIPI_PHY_TWD_INIT        CSI2_REG_D(0x3c)
>>>> +#define MIPI_DPHY_INIT_WATCH_DOG    0x400000
>>>> +
>>>> +#define MIPI_PHY_TWD_HS             CSI2_REG_D(0x40)
>>>> +#define MIPI_DPHY_HS_WATCH_DOG      0x400000
>>>> +
>>>> +#define MIPI_PHY_MUX_CTRL0       CSI2_REG_D(0x284)
>>>> +#define MIPI_DPHY_LANE3_SELECT      GENMASK(3, 0)
>>>> +#define MIPI_DPHY_LANE2_SELECT      GENMASK(7, 4)
>>>> +#define MIPI_DPHY_LANE2_SELECT_SHIFT           4
>>>> +#define MIPI_DPHY_LANE1_SELECT      GENMASK(11, 8)
>>>> +#define MIPI_DPHY_LANE1_SELECT_SHIFT            8
>>>> +#define MIPI_DPHY_LANE0_SELECT      GENMASK(14, 12)
>>>> +
>>>> +#define MIPI_PHY_MUX_CTRL1       CSI2_REG_D(0x288)
>>>> +#define MIPI_DPHY_LANE3_CTRL_SIGNAL GENMASK(3, 0)
>>>> +#define MIPI_DPHY_LANE2_CTRL_SIGNAL GENMASK(7, 4)
>>>> +#define MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT      4
>>>> +#define MIPI_DPHY_LANE1_CTRL_SIGNAL GENMASK(11, 8)
>>>> +#define MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT       8
>>>> +#define MIPI_DPHY_LANE0_CTRL_SIGNAL GENMASK(14, 12)
>>>> +#define MIPI_DPHY_CLK_SELECT        BIT(17)
>>>> +
>>>> +/* C3 CSI-2 HOST register */
>>>> +#define CSI2_HOST_N_LANES           CSI2_REG_H(0x04)
>>>> +#define CSI2_HOST_CSI2_RESETN       CSI2_REG_H(0x10)
>>>> +#define CSI2_HOST_RESETN_DEFAULT    0x0
>>>> +#define CSI2_HOST_RESETN_RST_VALUE  BIT(0)
>>>> +
>>>> +#define CSI2_HOST_MASK1             CSI2_REG_H(0x28)
>>>> +#define CSI2_HOST_ERROR_MASK1       GENMASK(28, 0)
>>>> +
>>>> +#define MIPI_CSI2_MAX_WIDTH         2888
>>>> +#define MIPI_CSI2_MIN_WIDTH         160
>>>> +#define MIPI_CSI2_MAX_HEIGHT        2240
>>>> +#define MIPI_CSI2_MIN_HEIGHT        120
>>>> +#define MIPI_CSI2_DEFAULT_WIDTH     1920
>>>> +#define MIPI_CSI2_DEFAULT_HEIGHT    1080
>>>> +#define MIPI_CSI2_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
>>>> +
>>>> +/* C3 CSI-2 pad list */
>>>> +enum {
>>>> +     MIPI_CSI2_PAD_SINK,
>>>> +     MIPI_CSI2_PAD_SRC,
>>>> +     MIPI_CSI2_PAD_MAX
>>>> +};
>>>> +
>>>> +/**
>>> You don't need to kernel-doc in-driver types and functions.
>>> Documentation is always good, but this won't be parsed by kernel-doc
>>> (afaiu) so you should drop one * from /**
>>>
>> Will drop one * from /**
>>>> + * struct csi_info - MIPI CSI2 information
>>>> + *
>>>> + * @clocks: array of MIPI CSI2 clock names
>>>> + * @clock_rates: array of MIPI CSI2 clock rate
>>>> + * @clock_num: actual clock number
>>>> + */
>>>> +struct csi_info {
>>>> +     char *clocks[MIPI_CSI2_CLOCK_NUM_MAX];
>>>> +     u32 clock_rates[MIPI_CSI2_CLOCK_NUM_MAX];
>>>> +     u32 clock_num;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct csi_device - MIPI CSI2 platform device
>>>> + *
>>>> + * @dev: pointer to the struct device
>>>> + * @aphy: MIPI CSI2 aphy register address
>>>> + * @dphy: MIPI CSI2 dphy register address
>>>> + * @host: MIPI CSI2 host register address
>>>> + * @clks: array of MIPI CSI2 clocks
>>>> + * @sd: MIPI CSI2 sub-device
>>>> + * @pads: MIPI CSI2 sub-device pads
>>>> + * @notifier: notifier to register on the v4l2-async API
>>>> + * @src_sd: source sub-device
>>>> + * @bus: MIPI CSI2 bus information
>>>> + * @src_sd_pad: source sub-device pad
>>>> + * @lock: protect MIPI CSI2 device
>>>> + * @info: version-specific MIPI CSI2 information
>>>> + */
>>>> +struct csi_device {
>>>> +     struct device *dev;
>>>> +     void __iomem *aphy;
>>>> +     void __iomem *dphy;
>>>> +     void __iomem *host;
>>>> +     struct clk_bulk_data clks[MIPI_CSI2_CLOCK_NUM_MAX];
>>>> +
>>>> +     struct v4l2_subdev sd;
>>>> +     struct media_pad pads[MIPI_CSI2_PAD_MAX];
>>>> +     struct v4l2_async_notifier notifier;
>>>> +     struct v4l2_subdev *src_sd;
>>>> +     struct v4l2_mbus_config_mipi_csi2 bus;
>>>> +
>>>> +     u16 src_sd_pad;
>>>> +     struct mutex lock; /* Protect csi device */
>>> All the operations which receive a subdev_state are guaranteed to be locked
>>> so you can avoid manually locking in enable/disable streams
>>> (and drop #include cleanup.h if you don't use guards in any other
>>> place)
>>>
>> OK, will remove this lock and drop "#include cleanup.h"
>>>> +     const struct csi_info *info;
>>>> +};
>>>> +
>>>> +static const u32 c3_mipi_csi_formats[] = {
>>>> +     MEDIA_BUS_FMT_SBGGR10_1X10,
>>>> +     MEDIA_BUS_FMT_SGBRG10_1X10,
>>>> +     MEDIA_BUS_FMT_SGRBG10_1X10,
>>>> +     MEDIA_BUS_FMT_SRGGB10_1X10,
>>>> +     MEDIA_BUS_FMT_SBGGR12_1X12,
>>>> +     MEDIA_BUS_FMT_SGBRG12_1X12,
>>>> +     MEDIA_BUS_FMT_SGRBG12_1X12,
>>>> +     MEDIA_BUS_FMT_SRGGB12_1X12,
>>>> +};
>>>> +
>>>> +/* Hardware configuration */
>>>> +
>>>> +static void c3_mipi_csi_write(struct csi_device *csi, u32 reg, u32 val)
>>>> +{
>>>> +     void __iomem *addr;
>>>> +
>>>> +     switch (CSI2_SUBMD(reg)) {
>>>> +     case SUBMD_APHY:
>>>> +             addr = csi->aphy + CSI2_REG_ADDR(reg);
>>>> +             break;
>>>> +     case SUBMD_DPHY:
>>>> +             addr = csi->dphy + CSI2_REG_ADDR(reg);
>>>> +             break;
>>>> +     case SUBMD_HOST:
>>>> +             addr = csi->host + CSI2_REG_ADDR(reg);
>>>> +             break;
>>>> +     default:
>>>> +             dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
>>>> +             return;
>>>> +     }
>>>> +
>>>> +     writel(val, addr);
>>>> +}
>>>> +
>>>> +static void c3_mipi_csi_update_bits(struct csi_device *csi, u32 reg,
>>>> +                                 u32 mask, u32 val)
>>>> +{
>>>> +     void __iomem *addr;
>>>> +     u32 orig, tmp;
>>>> +
>>>> +     switch (CSI2_SUBMD(reg)) {
>>>> +     case SUBMD_APHY:
>>>> +             addr = csi->aphy + CSI2_REG_ADDR(reg);
>>>> +             break;
>>>> +     case SUBMD_DPHY:
>>>> +             addr = csi->dphy + CSI2_REG_ADDR(reg);
>>>> +             break;
>>>> +     case SUBMD_HOST:
>>>> +             addr = csi->host + CSI2_REG_ADDR(reg);
>>>> +             break;
>>>> +     default:
>>>> +             dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg));
>>>> +             return;
>>>> +     }
>>> This is repeated in two functions and could be grouped to a common
>>> place. Up to you
>> Will use a common function to replace this part.
>>>> +
>>>> +     orig = readl(addr);
>>>> +     tmp = orig & ~mask;
>>>> +     tmp |= val & mask;
>>>> +
>>>> +     if (tmp != orig)
>>>> +             writel(tmp, addr);
>>>> +}
>>>> +
>>>> +static void c3_mipi_csi_cfg_aphy(struct csi_device *csi, u32 lanes)
>>>> +{
>>>> +     c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL1, MIPI_APHY_NORMAL_CNTL1);
>>>> +
>>>> +     if (lanes == 4)
>>>> +             c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_4LANES_CNTL2);
>>>> +     else
>>>> +             c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_NORMAL_CNTL2);
>>>> +
>>>> +     if (lanes == 2)
>>>> +             c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL3, MIPI_APHY_2LANES_CNTL3);
>>> The driver seems to only accept 2 or 4 lanes. What is
>>> MIPI_APHY_NORMAL_CNTL2 for ?
>> Will modify MIPI_APHY_NORMAL_CNTL2 to MIPI_APHY_2LANES_CNTL2.
>>
>> MIPI_APHY_2LANES_CNTL2 can indicate this 2 lanes setting.
>>
>>>> +}
>>>> +
>>>> +static void c3_mipi_csi_2lanes_setting(struct csi_device *csi)
>>>> +{
>>>> +     /* Disable lane 2 and lane 3 */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0xf);
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
>>>> +                             0xf << MIPI_DPHY_LANE2_SELECT_SHIFT);
>>>> +     /* Select analog data lane 1 */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
>>>> +                             0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
>>>> +     /* Select analog data lane 0 */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
>>>> +
>>>> +     /* Disable lane 2 and lane 3 control signal */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0xf);
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
>>>> +                             0xf << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
>>>> +     /* Select lane 1 signal */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
>>>> +                             0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
>>>> +     /* Select lane 0 signal */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
>>>> +     /* Select input 0 as clock */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
>>>> +                             MIPI_DPHY_CLK_SELECT);
>>>> +}
>>>> +
>>>> +static void c3_mipi_csi_4lanes_setting(struct csi_device *csi)
>>>> +{
>>>> +     /* Select analog data lane 3 */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0x3);
>>>> +     /* Select analog data lane 2 */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT,
>>>> +                             0x2 << MIPI_DPHY_LANE2_SELECT_SHIFT);
>>>> +     /* Select analog data lane 1 */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT,
>>>> +                             0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT);
>>>> +     /* Select analog data lane 0 */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0);
>>>> +
>>>> +     /* Select lane 3 signal */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0x3);
>>>> +     /* Select lane 2 signal */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL,
>>>> +                             0x2 << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT);
>>>> +     /* Select lane 1 signal */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL,
>>>> +                             0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT);
>>>> +     /* Select lane 0 signal */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0);
>>>> +     /* Select input 0 as clock */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT,
>>>> +                             MIPI_DPHY_CLK_SELECT);
>>>> +}
>>>> +
>>>> +static void c3_mipi_csi_cfg_dphy(struct csi_device *csi, u32 lanes, s64 rate)
>>>> +{
>>>> +     u32 val;
>>>> +     u32 settle;
>>>> +
>>>> +     /* Calculate the high speed settle */
>>>> +     val = DIV_ROUND_UP(1000000000, rate);
>>>> +     settle = (16 * val + 230) / 10;
>>>> +
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL, MIPI_DPHY_CLK_CONTINUE_MODE);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_TCLK_MISS, MIPI_DPHY_CLK_MISS);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_TCLK_SETTLE, MIPI_DPHY_CLK_SETTLE);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_THS_EXIT, MIPI_DPHY_HS_EXIT);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_THS_SKIP, MIPI_DPHY_HS_SKIP);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_THS_SETTLE, settle);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_TINIT, MIPI_DPHY_INIT_CYCLES);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_TMBIAS, MIPI_DPHY_MBIAS_CYCLES);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_TULPS_C, MIPI_DPHY_ULPS_CHECK_CYCLES);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_TULPS_S, MIPI_DPHY_ULPS_START_CYCLES);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_TLP_EN_W, MIPI_DPHY_ULPS_STOP_CYCLES);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_TLPOK, MIPI_DPHY_POWER_UP_CYCLES);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_TWD_INIT, MIPI_DPHY_INIT_WATCH_DOG);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_TWD_HS, MIPI_DPHY_HS_WATCH_DOG);
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_DATA_LANE_CTRL, MIPI_DPHY_LANE_CTRL_DISABLE);
>>>> +
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_INSERT_ERRESC,
>>>> +                             MIPI_DPHY_INSERT_ERRESC);
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_HS_SYNC_CHECK,
>>>> +                             MIPI_DPHY_HS_SYNC_CHECK);
>>>> +     /*
>>>> +      * Set 5 pipe lines to the same high speed.
>>>> +      * Each bit for one pipe line.
>>>> +      */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_FIVE_HS_PIPE,
>>>> +                             0x1f << MIPI_DPHY_FIVE_HS_PIPE_SHIFT);
>>>> +
>>>> +     /* Output data with pipe line data. */
>>>> +     c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_DATA_PIPE_SELECT,
>>>> +                             0x3 << MIPI_DPHY_DATA_PIPE_SELECT_SHIFT);
>>> Would it be possible to provide a definition for these 0x1f and 0x3
>>> values ?
>> OK, will provide two macros to represent  0x1f and 0x3.
>>>> +     if (lanes == 2)
>>>> +             c3_mipi_csi_2lanes_setting(csi);
>>>> +     else
>>>> +             c3_mipi_csi_4lanes_setting(csi);
>>>> +
>>>> +     /* Enable digital data and clock lanes */
>>>> +     c3_mipi_csi_write(csi, MIPI_PHY_CTRL, MIPI_DPHY_LANES_ENABLE);
>>>> +}
>>>> +
>>>> +static void c3_mipi_csi_cfg_host(struct csi_device *csi, u32 lanes)
>>>> +{
>>>> +     /* Reset CSI-2 controller output */
>>>> +     c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_DEFAULT);
>>>> +     c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_RST_VALUE);
>>>> +
>>>> +     /* Set data lane number */
>>>> +     c3_mipi_csi_write(csi, CSI2_HOST_N_LANES, lanes - 1);
>>>> +
>>>> +     /* Enable error mask */
>>>> +     c3_mipi_csi_write(csi, CSI2_HOST_MASK1, CSI2_HOST_ERROR_MASK1);
>>>> +}
>>>> +
>>>> +static int c3_mipi_csi_start_stream(struct csi_device *csi)
>>>> +{
>>>> +     s64 link_freq;
>>>> +     s64 lane_rate;
>>>> +
>>>> +     link_freq = v4l2_get_link_freq(csi->src_sd->ctrl_handler, 0, 0);
>>>> +     if (link_freq < 0) {
>>>> +             dev_err(csi->dev, "Unable to obtain link frequency: %lld\n", link_freq);
>>>> +             return link_freq;
>>>> +     }
>>>> +
>>>> +     lane_rate = link_freq * 2;
>>>> +     if (lane_rate > 1500000000)
>>> I would dev_err here too
>> Will add dev_err.
>>>> +             return -EINVAL;
>>>> +
>>>> +     c3_mipi_csi_cfg_aphy(csi, csi->bus.num_data_lanes);
>>>> +     c3_mipi_csi_cfg_dphy(csi, csi->bus.num_data_lanes, lane_rate);
>>>> +     c3_mipi_csi_cfg_host(csi, csi->bus.num_data_lanes);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_mipi_csi_enable_streams(struct v4l2_subdev *sd,
>>>> +                                   struct v4l2_subdev_state *state,
>>>> +                                   u32 pad, u64 streams_mask)
>>>> +{
>>>> +     struct csi_device *csi = v4l2_get_subdevdata(sd);
>>>> +     u64 sink_streams;
>>>> +     int ret;
>>>> +
>>>> +     guard(mutex)(&csi->lock);
>>>> +
>>>> +     pm_runtime_resume_and_get(csi->dev);
>>>> +
>>>> +     c3_mipi_csi_start_stream(csi);
>>>> +
>>>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>>> +                                                    MIPI_CSI2_PAD_SINK,
>>>> +                                                    &streams_mask);
>>>> +     ret = v4l2_subdev_enable_streams(csi->src_sd,
>>>> +                                      csi->src_sd_pad,
>>>> +                                      sink_streams);
>>>> +     if (ret) {
>>>> +             pm_runtime_put(csi->dev);
>>>> +             return ret;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_mipi_csi_disable_streams(struct v4l2_subdev *sd,
>>>> +                                    struct v4l2_subdev_state *state,
>>>> +                                    u32 pad, u64 streams_mask)
>>>> +{
>>>> +     struct csi_device *csi = v4l2_get_subdevdata(sd);
>>>> +     u64 sink_streams;
>>>> +     int ret;
>>>> +
>>>> +     guard(mutex)(&csi->lock);
>>>> +
>>>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>>> +                                                    MIPI_CSI2_PAD_SINK,
>>>> +                                                    &streams_mask);
>>>> +     ret = v4l2_subdev_disable_streams(csi->src_sd,
>>>> +                                       csi->src_sd_pad,
>>>> +                                       sink_streams);
>>>> +     if (ret)
>>>> +             dev_err(csi->dev, "Failed to disable %s\n", csi->src_sd->name);
>>>> +
>>>> +     pm_runtime_put(csi->dev);
>>>> +
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static int c3_mipi_csi_cfg_routing(struct v4l2_subdev *sd,
>>>> +                                struct v4l2_subdev_state *state,
>>>> +                                struct v4l2_subdev_krouting *routing)
>>>> +{
>>>> +     static const struct v4l2_mbus_framefmt format = {
>>>> +             .width = MIPI_CSI2_DEFAULT_WIDTH,
>>>> +             .height = MIPI_CSI2_DEFAULT_HEIGHT,
>>>> +             .code = MIPI_CSI2_DEFAULT_FMT,
>>>> +             .field = V4L2_FIELD_NONE,
>>>> +             .colorspace = V4L2_COLORSPACE_RAW,
>>>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>>>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>>> I presume for Raw Bayer data the quantization range is full ?
>> OK, will modify V4L2_QUANTIZATION_LIM_RANGE to V4L2_QUANTIZATION_FULL_RANGE.
>>>> +             .xfer_func = V4L2_XFER_FUNC_NONE,
>>>> +     };
>>>> +     int ret;
>>>> +
>>>> +     ret = v4l2_subdev_routing_validate(sd, routing,
>>>> +                                        V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>>>> +     if (ret)
>>>> +             return ret;
>>> You should validate that the provided routing table matches what the
>>> driver supports, so only [0/0]->[1/0]
>>>
>>> Now that I've said so, if the routing table is not modifiable I wonder
>>> if you should support set_routing() at all, or it could be left out
>>> until you don't add support for more streams to the driver.
>>>
>>> After all this driver implements support for routing but doesn't set
>>> the V4L2_SUBDEV_FL_STREAMS flag, so the operation is disallowed from
>>> userspace for now.
>> Will remove set_routing().
>>
>> Now the driver dosen't require  routing configuration.
>>
>>>> +
>>>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_mipi_csi_init_routing(struct v4l2_subdev *sd,
>>>> +                                 struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_subdev_route routes;
>>>> +     struct v4l2_subdev_krouting routing;
>>>> +
>>>> +     routes.sink_pad = MIPI_CSI2_PAD_SINK;
>>>> +     routes.sink_stream = 0;
>>>> +     routes.source_pad = MIPI_CSI2_PAD_SRC;
>>>> +     routes.source_stream = 0;
>>>> +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>>>> +
>>>> +     routing.num_routes = 1;
>>>> +     routing.routes = &routes;
>>>> +
>>>> +     return c3_mipi_csi_cfg_routing(sd, state, &routing);
>>>> +}
>>>> +
>>>> +static int c3_mipi_csi_set_routing(struct v4l2_subdev *sd,
>>>> +                                struct v4l2_subdev_state *state,
>>>> +                                enum v4l2_subdev_format_whence which,
>>>> +                                struct v4l2_subdev_krouting *routing)
>>>> +{
>>>> +     bool is_streaming = v4l2_subdev_is_streaming(sd);
>>>> +
>>>> +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>>>> +             return -EBUSY;
>>>> +
>>>> +     return c3_mipi_csi_cfg_routing(sd, state, routing);
>>>> +}
>>>> +
>>>> +static int c3_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
>>>> +                                   struct v4l2_subdev_state *state,
>>>> +                                   struct v4l2_subdev_mbus_code_enum *code)
>>>> +{
>>>> +     switch (code->pad) {
>>>> +     case MIPI_CSI2_PAD_SINK:
>>>> +             if (code->index >= ARRAY_SIZE(c3_mipi_csi_formats))
>>>> +                     return -EINVAL;
>>>> +
>>>> +             code->code = c3_mipi_csi_formats[code->index];
>>>> +             break;
>>>> +     case MIPI_CSI2_PAD_SRC:
>>>> +             struct v4l2_mbus_framefmt *fmt;
>>>> +
>>>> +             if (code->index > 0)
>>>> +                     return -EINVAL;
>>>> +
>>>> +             fmt = v4l2_subdev_state_get_format(state, code->pad);
>>>> +             code->code = fmt->code;
>>>> +             break;
>>> I'm not sure if the V4L2 API specify that the formats on a pad should
>>> be enumerated in full, regardless of the configuration, or like you're
>>> doing here reflect the subdev configuration. I like what you have here
>>> more, so unless someone screams I think it's fine.
>> OK, thanks.
>>
>> I will pay attention to the review of this function.
>>
>>>> +     default:
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_mipi_csi_set_fmt(struct v4l2_subdev *sd,
>>>> +                            struct v4l2_subdev_state *state,
>>>> +                            struct v4l2_subdev_format *format)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *fmt;
>>>> +     unsigned int i;
>>>> +
>>>> +     if (format->pad != MIPI_CSI2_PAD_SINK)
>>>> +             return v4l2_subdev_get_fmt(sd, state, format);
>>>> +
>>>> +     fmt = v4l2_subdev_state_get_format(state, format->pad);
>>> Could you clarify what other streams you plan to support ? As you
>>> support routing I presume you are preparing to capture
>>> multiple streams of data like image + embedded data, or to support
>>> sensors which sends data on different virtual channels ?
>>>
>>> How do you see this driver evolve ? Will it be augmented with an
>>> additional source pad directed to a video device where to capture
>>> embedded data from ?
>>>
>>> I'm wondering because if PAD_SINK become multiplexed, you won't be
>>> allowed to set a format there. It only works now because you have a
>>> single stream.
>>>
>>> Speaking of which, as you prepare to support multiple streams, I would
>>> specify the stream number when calling v4l2_subdev_state_get_format().
>>>
>>>           fmt = v4l2_subdev_state_get_format(state, format->pad, 0);
>>>
>> Thanks for your suggestion.
>>
>> But this MIPI CSI2 hardware module doesn't have the ability to separate data
>> , such as image + embedded data.
>>
>> So there are no plans to support other streams.
> I see. Now that I've reviewed the adapter subdevice path I realized
> that it's the adapter that can split data based on VC/DT to either the
> ISP direct path or to DDR.
>
> The CSI-2 RX then transports streams as received from the sensor
> directly to the adapter.
>
> In order to support capturing embedded data to DDR in the adapter the
> embedded data stream needs a representation in this subdevice as well,
> so that the source pad is multiplexed as well and the adapter receives
> two streams that it can eventually split.
>
>
>        Sensor         CSI-2Rx         Adapter
>     +-----------+     +------+       +--------+
>     0-- ED --\  |     |      |       |     /->0---> Embedded Data (DDR)
>     |         ->0====>0 ==== 0 ====> 0====|   |
>     0-- I ---/  |     |      |       |     \->0--->    Image (ISP)
>     +-----------+     +------+       +--------+
>
> When going to support embedded data capture this driver should create
> two routes and allow enabling/disabling the embedded data one.
>
> Tomi in cc for inputs.
>
> For now, if you don't support capturing embedded data, I would remove
> routing support from here and from the adapter.
>

Yes, you are right.

The adapter module can support capturing embedded data.

I will remove set_routing() interface.

>>>> +
>>>> +     for (i = 0; i < ARRAY_SIZE(c3_mipi_csi_formats); i++)
>>>> +             if (format->format.code == c3_mipi_csi_formats[i])
>>>> +                     break;
>>> nit: please use {} for the for loop
>> Will add { } for the for loop
>>>> +
>>>> +     if (i == ARRAY_SIZE(c3_mipi_csi_formats))
>>>> +             fmt->code = c3_mipi_csi_formats[0];
>>>> +     else
>>>> +             fmt->code = c3_mipi_csi_formats[i];
>>> You could set this in the for loop, before breaking.
>> Will put this in the for loop.
>>>> +
>>>> +     fmt->width = clamp_t(u32, format->format.width,
>>>> +                          MIPI_CSI2_MIN_WIDTH, MIPI_CSI2_MAX_WIDTH);
>>>> +     fmt->height = clamp_t(u32, format->format.height,
>>>> +                           MIPI_CSI2_MIN_HEIGHT, MIPI_CSI2_MAX_HEIGHT);
>>>> +
>>> You should set the colorspace related information too, as you
>>> initialize them, similar to what you do in init_state()
>> Will set the colorspace .
>>>> +     format->format = *fmt;
>>>> +
>>>> +     /* Synchronize the format to source pad */
>>>> +     fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
>>>> +     *fmt = format->format;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_mipi_csi_init_state(struct v4l2_subdev *sd,
>>>> +                               struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *sink_fmt;
>>>> +     struct v4l2_mbus_framefmt *src_fmt;
>>>> +
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SINK);
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC);
>>>> +
>>>> +     sink_fmt->width = MIPI_CSI2_DEFAULT_WIDTH;
>>>> +     sink_fmt->height = MIPI_CSI2_DEFAULT_HEIGHT;
>>>> +     sink_fmt->field = V4L2_FIELD_NONE;
>>>> +     sink_fmt->code = MIPI_CSI2_DEFAULT_FMT;
>>>> +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>>>> +     sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
>>>> +     sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
>>>> +     sink_fmt->quantization =
>>>> +             V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
>>> If you could initialize them like you do above, with specific values
>>> instead of using _DEFAULT() I think it's better.
>> Will use the specific values.
>>>> +                                           sink_fmt->ycbcr_enc);
>>>> +     *src_fmt = *sink_fmt;
>>>> +
>>>> +     return c3_mipi_csi_init_routing(sd, state);
>>>> +}
>>>> +
>>>> +static const struct v4l2_subdev_pad_ops c3_mipi_csi_pad_ops = {
>>>> +     .enum_mbus_code = c3_mipi_csi_enum_mbus_code,
>>>> +     .get_fmt = v4l2_subdev_get_fmt,
>>>> +     .set_fmt = c3_mipi_csi_set_fmt,
>>>> +     .set_routing = c3_mipi_csi_set_routing,
>>>> +     .enable_streams = c3_mipi_csi_enable_streams,
>>>> +     .disable_streams = c3_mipi_csi_disable_streams,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_ops c3_mipi_csi_subdev_ops = {
>>>> +     .pad = &c3_mipi_csi_pad_ops,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_internal_ops c3_mipi_csi_internal_ops = {
>>>> +     .init_state = c3_mipi_csi_init_state,
>>>> +};
>>>> +
>>>> +/* Media entity operations */
>>>> +static const struct media_entity_operations c3_mipi_csi_entity_ops = {
>>>> +     .link_validate = v4l2_subdev_link_validate,
>>>> +};
>>>> +
>>>> +/* PM runtime */
>>>> +
>>>> +static int __maybe_unused c3_mipi_csi_runtime_suspend(struct device *dev)
>>>> +{
>>>> +     struct csi_device *csi = dev_get_drvdata(dev);
>>>> +
>>>> +     clk_bulk_disable_unprepare(csi->info->clock_num, csi->clks);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int __maybe_unused c3_mipi_csi_runtime_resume(struct device *dev)
>>>> +{
>>>> +     struct csi_device *csi = dev_get_drvdata(dev);
>>>> +
>>>> +     return clk_bulk_prepare_enable(csi->info->clock_num, csi->clks);
>>>> +}
>>>> +
>>>> +static const struct dev_pm_ops c3_mipi_csi_pm_ops = {
>>>> +     SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>>>> +                             pm_runtime_force_resume)
>>>> +     SET_RUNTIME_PM_OPS(c3_mipi_csi_runtime_suspend,
>>>> +                        c3_mipi_csi_runtime_resume, NULL)
>>> You could use SYSTEM_SLEEP_PM_OPS and RUNTIME_PM_OPS and set
>>>
>>>                   .pm = pm_ptr(&c3_mipi_csi_pm_ops),
>>>
>>> to avoid __maybe_unused in the functions, up to you
>>>
>> OK, will use your method to set pm.
>>>> +};
>>>> +
>>>> +/* Probe/remove & platform driver */
>>>> +
>>>> +static int c3_mipi_csi_subdev_init(struct csi_device *csi)
>>>> +{
>>>> +     struct v4l2_subdev *sd = &csi->sd;
>>>> +     int ret;
>>>> +
>>>> +     v4l2_subdev_init(sd, &c3_mipi_csi_subdev_ops);
>>>> +     sd->owner = THIS_MODULE;
>>>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>>> +     sd->internal_ops = &c3_mipi_csi_internal_ops;
>>>> +     snprintf(sd->name, sizeof(sd->name), "%s", MIPI_CSI2_SUBDEV_NAME);
>>>> +
>>>> +     sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>>>> +     sd->entity.ops = &c3_mipi_csi_entity_ops;
>>>> +
>>>> +     sd->dev = csi->dev;
>>>> +     v4l2_set_subdevdata(sd, csi);
>>>> +
>>>> +     csi->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>>>> +     csi->pads[MIPI_CSI2_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
>>>> +     ret = media_entity_pads_init(&sd->entity, MIPI_CSI2_PAD_MAX, csi->pads);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = v4l2_subdev_init_finalize(sd);
>>>> +     if (ret) {
>>>> +             media_entity_cleanup(&sd->entity);
>>>> +             return ret;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static void c3_mipi_csi_subdev_deinit(struct csi_device *csi)
>>>> +{
>>>> +     v4l2_subdev_cleanup(&csi->sd);
>>>> +     media_entity_cleanup(&csi->sd.entity);
>>>> +}
>>>> +
>>>> +/* Subdev notifier register */
>>>> +static int c3_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier,
>>>> +                                 struct v4l2_subdev *sd,
>>>> +                                 struct v4l2_async_connection *asc)
>>>> +{
>>>> +     struct csi_device *csi = v4l2_get_subdevdata(notifier->sd);
>>>> +     struct media_pad *sink = &csi->sd.entity.pads[MIPI_CSI2_PAD_SINK];
>>>> +     int ret;
>>>> +
>>>> +     ret = media_entity_get_fwnode_pad(&sd->entity,
>>>> +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
>>>> +     if (ret < 0) {
>>>> +             dev_err(csi->dev, "Failed to find pad for %s\n", sd->name);
>>>> +             return ret;
>>>> +     }
>>>> +
>>>> +     csi->src_sd = sd;
>>>> +     csi->src_sd_pad = ret;
>>>> +
>>>> +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
>>>> +                                            MEDIA_LNK_FL_IMMUTABLE);
>>>> +}
>>>> +
>>>> +static const struct v4l2_async_notifier_operations c3_mipi_csi_notify_ops = {
>>>> +     .bound = c3_mipi_csi_notify_bound,
>>>> +};
>>>> +
>>>> +static int c3_mipi_csi_async_register(struct csi_device *csi)
>>>> +{
>>>> +     struct v4l2_fwnode_endpoint vep = {
>>>> +             .bus_type = V4L2_MBUS_CSI2_DPHY,
>>>> +     };
>>>> +     struct v4l2_async_connection *asc;
>>>> +     struct fwnode_handle *ep;
>>>> +     int ret;
>>>> +
>>>> +     v4l2_async_subdev_nf_init(&csi->notifier, &csi->sd);
>>>> +
>>>> +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0,
>>>> +                                          FWNODE_GRAPH_ENDPOINT_NEXT);
>>>> +     if (!ep)
>>>> +             return -ENOTCONN;
>>>> +
>>>> +     ret = v4l2_fwnode_endpoint_parse(ep, &vep);
>>>> +     if (ret)
>>>> +             goto err_put_handle;
>>>> +
>>>> +     csi->bus = vep.bus.mipi_csi2;
>>>> +     if (csi->bus.num_data_lanes != 2 && csi->bus.num_data_lanes != 4)
>>>> +             goto err_put_handle;
>>> I would dev_err() here
>>>
>>> Thanks
>>>      j
>> Wil add dev_err().
>>>> +
>>>> +     asc = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep,
>>>> +                                           struct v4l2_async_connection);
>>>> +     if (IS_ERR(asc)) {
>>>> +             ret = PTR_ERR(asc);
>>>> +             goto err_put_handle;
>>>> +     }
>>>> +
>>>> +     csi->notifier.ops = &c3_mipi_csi_notify_ops;
>>>> +     ret = v4l2_async_nf_register(&csi->notifier);
>>>> +     if (ret)
>>>> +             goto err_cleanup_nf;
>>>> +
>>>> +     ret = v4l2_async_register_subdev(&csi->sd);
>>>> +     if (ret)
>>>> +             goto err_unregister_nf;
>>>> +
>>>> +     fwnode_handle_put(ep);
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_unregister_nf:
>>>> +     v4l2_async_nf_unregister(&csi->notifier);
>>>> +err_cleanup_nf:
>>>> +     v4l2_async_nf_cleanup(&csi->notifier);
>>>> +err_put_handle:
>>>> +     fwnode_handle_put(ep);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_mipi_csi_async_unregister(struct csi_device *csi)
>>>> +{
>>>> +     v4l2_async_unregister_subdev(&csi->sd);
>>>> +     v4l2_async_nf_unregister(&csi->notifier);
>>>> +     v4l2_async_nf_cleanup(&csi->notifier);
>>>> +}
>>>> +
>>>> +static int c3_mipi_csi_ioremap_resource(struct csi_device *csi)
>>>> +{
>>>> +     struct device *dev = csi->dev;
>>>> +     struct platform_device *pdev = to_platform_device(dev);
>>>> +
>>>> +     csi->aphy = devm_platform_ioremap_resource_byname(pdev, "aphy");
>>>> +     if (IS_ERR(csi->aphy))
>>>> +             return PTR_ERR(csi->aphy);
>>>> +
>>>> +     csi->dphy = devm_platform_ioremap_resource_byname(pdev, "dphy");
>>>> +     if (IS_ERR(csi->dphy))
>>>> +             return PTR_ERR(csi->dphy);
>>>> +
>>>> +     csi->host = devm_platform_ioremap_resource_byname(pdev, "host");
>>>> +     if (IS_ERR(csi->host))
>>>> +             return PTR_ERR(csi->host);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_mipi_csi_configure_clocks(struct csi_device *csi)
>>>> +{
>>>> +     const struct csi_info *info = csi->info;
>>>> +     int ret;
>>>> +     u32 i;
>>>> +
>>>> +     for (i = 0; i < info->clock_num; i++)
>>>> +             csi->clks[i].id = info->clocks[i];
>>>> +
>>>> +     ret = devm_clk_bulk_get(csi->dev, info->clock_num, csi->clks);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     for (i = 0; i < info->clock_num; i++) {
>>>> +             if (!info->clock_rates[i])
>>>> +                     continue;
>>>> +             ret = clk_set_rate(csi->clks[i].clk, info->clock_rates[i]);
>>>> +             if (ret) {
>>>> +                     dev_err(csi->dev, "Failed to set %s rate %u\n", info->clocks[i],
>>>> +                             info->clock_rates[i]);
>>>> +                     return ret;
>>>> +             }
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_mipi_csi_probe(struct platform_device *pdev)
>>>> +{
>>>> +     struct device *dev = &pdev->dev;
>>>> +     struct csi_device *csi;
>>>> +     int ret;
>>>> +
>>>> +     csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
>>>> +     if (!csi)
>>>> +             return -ENOMEM;
>>>> +
>>>> +     csi->info = of_device_get_match_data(dev);
>>>> +     csi->dev = dev;
>>>> +
>>>> +     ret = c3_mipi_csi_ioremap_resource(csi);
>>>> +     if (ret)
>>>> +             return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
>>>> +
>>>> +     ret = c3_mipi_csi_configure_clocks(csi);
>>>> +     if (ret)
>>>> +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
>>>> +
>>>> +     platform_set_drvdata(pdev, csi);
>>>> +
>>>> +     mutex_init(&csi->lock);
>>>> +     pm_runtime_enable(dev);
>>>> +
>>>> +     ret = c3_mipi_csi_subdev_init(csi);
>>>> +     if (ret)
>>>> +             goto err_disable_runtime_pm;
>>>> +
>>>> +     ret = c3_mipi_csi_async_register(csi);
>>>> +     if (ret)
>>>> +             goto err_deinit_subdev;
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_deinit_subdev:
>>>> +     c3_mipi_csi_subdev_deinit(csi);
>>>> +err_disable_runtime_pm:
>>>> +     pm_runtime_disable(dev);
>>>> +     mutex_destroy(&csi->lock);
>>>> +     return ret;
>>>> +};
>>>> +
>>>> +static void c3_mipi_csi_remove(struct platform_device *pdev)
>>>> +{
>>>> +     struct csi_device *csi = platform_get_drvdata(pdev);
>>>> +
>>>> +     c3_mipi_csi_async_unregister(csi);
>>>> +     c3_mipi_csi_subdev_deinit(csi);
>>>> +
>>>> +     pm_runtime_disable(&pdev->dev);
>>>> +     mutex_destroy(&csi->lock);
>>>> +};
>>>> +
>>>> +static const struct csi_info c3_mipi_csi_info = {
>>>> +     .clocks = {"vapb", "phy0"},
>>>> +     .clock_rates = {0, 200000000},
>>>> +     .clock_num = 2
>>>> +};
>>>> +
>>>> +static const struct of_device_id c3_mipi_csi_of_match[] = {
>>>> +     { .compatible = "amlogic,c3-mipi-csi2",
>>>> +       .data = &c3_mipi_csi_info,
>>>> +     },
>>>> +     { },
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, c3_mipi_csi_of_match);
>>>> +
>>>> +static struct platform_driver c3_mipi_csi_driver = {
>>>> +     .probe = c3_mipi_csi_probe,
>>>> +     .remove = c3_mipi_csi_remove,
>>>> +     .driver = {
>>>> +             .name = "c3-mipi-csi2",
>>>> +             .of_match_table = c3_mipi_csi_of_match,
>>>> +             .pm = &c3_mipi_csi_pm_ops,
>>>> +     },
>>>> +};
>>>> +
>>>> +module_platform_driver(c3_mipi_csi_driver);
>>>> +
>>>> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
>>>> +MODULE_DESCRIPTION("Amlogic C3 MIPI CSI-2 receiver");
>>>> +MODULE_LICENSE("GPL");
>>>>
>>>> --
>>>> 2.46.1
>>>>
>>>>
>>>>

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

* Re: [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver
  2024-11-05 11:39       ` Jacopo Mondi
  2024-11-05 11:58         ` Keke Li
@ 2024-11-05 12:08         ` Tomi Valkeinen
  2024-11-06  2:10           ` Keke Li
  1 sibling, 1 reply; 37+ messages in thread
From: Tomi Valkeinen @ 2024-11-05 12:08 UTC (permalink / raw)
  To: Jacopo Mondi, Keke Li
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi,

On 05/11/2024 13:39, Jacopo Mondi wrote:

>>> Could you clarify what other streams you plan to support ? As you
>>> support routing I presume you are preparing to capture
>>> multiple streams of data like image + embedded data, or to support
>>> sensors which sends data on different virtual channels ?
>>>
>>> How do you see this driver evolve ? Will it be augmented with an
>>> additional source pad directed to a video device where to capture
>>> embedded data from ?
>>>
>>> I'm wondering because if PAD_SINK become multiplexed, you won't be
>>> allowed to set a format there. It only works now because you have a
>>> single stream.
>>>
>>> Speaking of which, as you prepare to support multiple streams, I would
>>> specify the stream number when calling v4l2_subdev_state_get_format().
>>>
>>>           fmt = v4l2_subdev_state_get_format(state, format->pad, 0);
>>>
>> Thanks for your suggestion.
>>
>> But this MIPI CSI2 hardware module doesn't have the ability to separate data
>> , such as image + embedded data.
>>
>> So there are no plans to support other streams.
> 
> I see. Now that I've reviewed the adapter subdevice path I realized
> that it's the adapter that can split data based on VC/DT to either the
> ISP direct path or to DDR.
> 
> The CSI-2 RX then transports streams as received from the sensor
> directly to the adapter.
> 
> In order to support capturing embedded data to DDR in the adapter the
> embedded data stream needs a representation in this subdevice as well,
> so that the source pad is multiplexed as well and the adapter receives
> two streams that it can eventually split.
> 
> 
>        Sensor         CSI-2Rx         Adapter
>     +-----------+     +------+       +--------+
>     0-- ED --\  |     |      |       |     /->0---> Embedded Data (DDR)
>     |         ->0====>0 ==== 0 ====> 0====|   |
>     0-- I ---/  |     |      |       |     \->0--->    Image (ISP)
>     +-----------+     +------+       +--------+
> 
> When going to support embedded data capture this driver should create
> two routes and allow enabling/disabling the embedded data one.

This depends on the hardware. What's the bus between the CSI-2 rx and 
the adapter? If it's a custom bus that basically allows passing forward 
anything received from the CSI-2 bus, then the user should be free to 
configure any routes in the CSI-2 RX subdevice.

The adapter subdev, however, as it is limited by the HW, should only 
support two streams if that is what the hardware supports.

> Tomi in cc for inputs.
> 
> For now, if you don't support capturing embedded data, I would remove
> routing support from here and from the adapter.

Right, if the drivers only support a single stream, just drop the 
routing support.

  Tomi


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

* Re: [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver
  2024-11-05 12:08         ` Tomi Valkeinen
@ 2024-11-06  2:10           ` Keke Li
  0 siblings, 0 replies; 37+ messages in thread
From: Keke Li @ 2024-11-06  2:10 UTC (permalink / raw)
  To: Tomi Valkeinen, Jacopo Mondi
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi Tomi

Thanks very much for your reply.

On 2024/11/5 20:08, Tomi Valkeinen wrote:
> [ EXTERNAL EMAIL ]
>
> Hi,
>
> On 05/11/2024 13:39, Jacopo Mondi wrote:
>
>>>> Could you clarify what other streams you plan to support ? As you
>>>> support routing I presume you are preparing to capture
>>>> multiple streams of data like image + embedded data, or to support
>>>> sensors which sends data on different virtual channels ?
>>>>
>>>> How do you see this driver evolve ? Will it be augmented with an
>>>> additional source pad directed to a video device where to capture
>>>> embedded data from ?
>>>>
>>>> I'm wondering because if PAD_SINK become multiplexed, you won't be
>>>> allowed to set a format there. It only works now because you have a
>>>> single stream.
>>>>
>>>> Speaking of which, as you prepare to support multiple streams, I would
>>>> specify the stream number when calling v4l2_subdev_state_get_format().
>>>>
>>>>           fmt = v4l2_subdev_state_get_format(state, format->pad, 0);
>>>>
>>> Thanks for your suggestion.
>>>
>>> But this MIPI CSI2 hardware module doesn't have the ability to 
>>> separate data
>>> , such as image + embedded data.
>>>
>>> So there are no plans to support other streams.
>>
>> I see. Now that I've reviewed the adapter subdevice path I realized
>> that it's the adapter that can split data based on VC/DT to either the
>> ISP direct path or to DDR.
>>
>> The CSI-2 RX then transports streams as received from the sensor
>> directly to the adapter.
>>
>> In order to support capturing embedded data to DDR in the adapter the
>> embedded data stream needs a representation in this subdevice as well,
>> so that the source pad is multiplexed as well and the adapter receives
>> two streams that it can eventually split.
>>
>>
>>        Sensor         CSI-2Rx         Adapter
>>     +-----------+     +------+       +--------+
>>     0-- ED --\  |     |      |       |     /->0---> Embedded Data (DDR)
>>     |         ->0====>0 ==== 0 ====> 0====|   |
>>     0-- I ---/  |     |      |       |     \->0---> Image (ISP)
>>     +-----------+     +------+       +--------+
>>
>> When going to support embedded data capture this driver should create
>> two routes and allow enabling/disabling the embedded data one.
>
> This depends on the hardware. What's the bus between the CSI-2 rx and
> the adapter? If it's a custom bus that basically allows passing forward
> anything received from the CSI-2 bus, then the user should be free to
> configure any routes in the CSI-2 RX subdevice.
>
The bus between CSI-2 rx and the adapter is not a custom bus and can't

be configured by user.

> The adapter subdev, however, as it is limited by the HW, should only
> support two streams if that is what the hardware supports.
>
Yes,  the adapter HW only supports embedded data and image.
>> Tomi in cc for inputs.
>>
>> For now, if you don't support capturing embedded data, I would remove
>> routing support from here and from the adapter.
>
> Right, if the drivers only support a single stream, just drop the
> routing support.
>
>  Tomi
>
Will drop the routing support due to the hardware limit.



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

* Re: [PATCH v3 4/9] media: platform: Add c3 mipi adapter driver
  2024-11-05 11:24   ` Jacopo Mondi
@ 2024-11-06  5:32     ` Keke Li
  2024-11-07  8:48       ` Jacopo Mondi
  0 siblings, 1 reply; 37+ messages in thread
From: Keke Li @ 2024-11-06  5:32 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi Jacopo

Thanks very much for your reply.

On 2024/11/5 19:24, Jacopo Mondi wrote:
> [You don't often get email from jacopo.mondi@ideasonboard.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>
> [ EXTERNAL EMAIL ]
>
> Hi Keke
>
> On Wed, Sep 18, 2024 at 02:07:15PM +0800, Keke Li via B4 Relay wrote:
>> From: Keke Li <keke.li@amlogic.com>
>>
>> This driver mainly responsible for organizing
>> MIPI data and sending raw data to ISP pipeline.
> A few questions on the design of this subdevice.
>
> In my understanding the adapter provides several functionalities:
>
> - Data alignement to match the ISP size constraints
> - Read from DDR memory or from the CSI-2 RX (direct path)
> - Write to DDR memory or to the ISP (direct path)
> - Split data streams to DDR or direct path based on CSI-2 Virtual
>    Channel and Data type.


Yes, you are right.

> As I understand the current implementation only supports direct-path
> to ISP of data sent on VC#0.


Read image from DDR and wirte image to DDR  are used in WDR mode.

Due to the lack of WDR requirement,  This driver only support direct-path.

> Do you plan to expand the adapter subdev with support for mulitple
> paths ? Is this why you are supporting already a routing table ?

If there is a demand in the future, I will support multiple paths.

But now I attend to only support one path and will drop the 
set_routing() interface.


>> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
>> Signed-off-by: Keke Li <keke.li@amlogic.com>
>> ---
>>   MAINTAINERS                                        |   7 +
>>   drivers/media/platform/amlogic/Kconfig             |   1 +
>>   drivers/media/platform/amlogic/Makefile            |   1 +
>>   .../media/platform/amlogic/c3-mipi-adapter/Kconfig |  16 +
>>   .../platform/amlogic/c3-mipi-adapter/Makefile      |   3 +
>>   .../amlogic/c3-mipi-adapter/c3-mipi-adap.c         | 913 +++++++++++++++++++++
>>   6 files changed, 941 insertions(+)
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 9e75874a6e69..31168c05f304 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -1209,6 +1209,13 @@ F:     Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
>>   F:   drivers/perf/amlogic/
>>   F:   include/soc/amlogic/
>>
>> +AMLOGIC MIPI ADAPTER DRIVER
>> +M:   Keke Li <keke.li@amlogic.com>
>> +L:   linux-media@vger.kernel.org
>> +S:   Maintained
>> +F:   Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml
>> +F:   drivers/media/platform/amlogic/c3-mipi-adapter/
>> +
>>   AMLOGIC MIPI CSI2 DRIVER
>>   M:   Keke Li <keke.li@amlogic.com>
>>   L:   linux-media@vger.kernel.org
>> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
>> index b7c2de14848b..df09717b28d0 100644
>> --- a/drivers/media/platform/amlogic/Kconfig
>> +++ b/drivers/media/platform/amlogic/Kconfig
>> @@ -2,5 +2,6 @@
>>
>>   comment "Amlogic media platform drivers"
>>
>> +source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
>>   source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>>   source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
>> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
>> index 4f571ce5d13e..b370154b090c 100644
>> --- a/drivers/media/platform/amlogic/Makefile
>> +++ b/drivers/media/platform/amlogic/Makefile
>> @@ -1,4 +1,5 @@
>>   # SPDX-License-Identifier: GPL-2.0-only
>>
>> +obj-y += c3-mipi-adapter/
>>   obj-y += c3-mipi-csi2/
>>   obj-y += meson-ge2d/
>> diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig b/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig
>> new file mode 100644
>> index 000000000000..bf19059b3543
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig
>> @@ -0,0 +1,16 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +config VIDEO_C3_MIPI_ADAPTER
>> +     tristate "Amlogic C3 MIPI adapter"
>> +     depends on ARCH_MESON || COMPILE_TEST
>> +     depends on VIDEO_DEV
>> +     depends on OF
>> +     select MEDIA_CONTROLLER
>> +     select V4L2_FWNODE
>> +     select VIDEO_V4L2_SUBDEV_API
>> +     help
>> +       Video4Linux2 driver for Amlogic C3 MIPI adapter.
>> +       C3 MIPI adapter mainly responsible for organizing
>> +       MIPI data and sending raw data to ISP pipeline.
>> +
>> +       To compile this driver as a module choose m here.
>> diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile b/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile
>> new file mode 100644
>> index 000000000000..216fc310c5b4
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile
>> @@ -0,0 +1,3 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +obj-$(CONFIG_VIDEO_C3_MIPI_ADAPTER) += c3-mipi-adap.o
>> diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c b/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c
>> new file mode 100644
>> index 000000000000..b64eb417c2e2
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c
>> @@ -0,0 +1,913 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/cleanup.h>
>> +#include <linux/clk.h>
>> +#include <linux/device.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-async.h>
>> +#include <media/v4l2-common.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-fwnode.h>
>> +#include <media/v4l2-mc.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +/* C3 adapter submodule definition */
>> +enum {
>> +     SUBMD_TOP,
>> +     SUBMD_FD,
>> +     SUBMD_RD,
>> +};
>> +
>> +#define ADAP_SUBMD_MASK             GENMASK(17, 16)
>> +#define ADAP_SUBMD_SHIFT            16
>> +#define ADAP_SUBMD(x)               (((x) & (ADAP_SUBMD_MASK)) >> (ADAP_SUBMD_SHIFT))
>> +#define ADAP_REG_ADDR_MASK          GENMASK(15, 0)
>> +#define ADAP_REG_ADDR(x)            ((x) & (ADAP_REG_ADDR_MASK))
>> +#define ADAP_REG_T(x)               ((SUBMD_TOP << ADAP_SUBMD_SHIFT) | (x))
>> +#define ADAP_REG_F(x)               ((SUBMD_FD << ADAP_SUBMD_SHIFT) | (x))
>> +#define ADAP_REG_R(x)               ((SUBMD_RD << ADAP_SUBMD_SHIFT) | (x))
>> +
>> +#define MIPI_ADAP_CLOCK_NUM_MAX     3
>> +#define MIPI_ADAP_SUBDEV_NAME       "mipi-adapter"
> I would add a "c3-" prefix
>

Will add "c3-" prefix

>> +
>> +/* C3 MIPI adapter TOP register */
>> +#define MIPI_ADAPT_DE_CTRL0         ADAP_REG_T(0x40)
>> +#define ADAP_DE_READ_BYPASS         BIT(3)
>> +#define ADAP_DE_WRITE_BYPASS        BIT(7)
>> +
>> +/* C3 MIPI adapter FRONTEND register */
>> +#define CSI2_CLK_RESET              ADAP_REG_F(0x00)
>> +#define ADAP_FD_APPLY_RESET         BIT(0)
>> +#define ADAP_FD_ENABLE              BIT(1)
>> +
>> +#define CSI2_GEN_CTRL0              ADAP_REG_F(0x04)
>> +#define ADAP_FD_VIRTUAL_CHN0_EN     BIT(0)
>> +#define ADAP_FD_VIRTUAL_CHN1_EN     BIT(1)
>> +#define ADAP_FD_VIRTUAL_CHN2_EN     BIT(2)
>> +#define ADAP_FD_VIRTUAL_CHN3_EN     BIT(3)
>> +#define ADAP_FD_ENABLE_PACKETS      GENMASK(20, 16)
>> +#define ADAP_FD_ENABLE_RAW          BIT(16)
>> +
>> +#define CSI2_X_START_END_ISP        ADAP_REG_F(0x0c)
>> +#define ADAP_FD_X_END_MASK          GENMASK(31, 16)
>> +#define ADAP_FD_X_END_SHIFT         16
>> +#define ADAP_FD_X_END(x)            ((x) - 1)
>> +
>> +#define CSI2_Y_START_END_ISP        ADAP_REG_F(0x10)
>> +#define ADAP_FD_Y_END_MASK          GENMASK(31, 16)
>> +#define ADAP_FD_Y_END_SHIFT         16
>> +#define ADAP_FD_Y_END(x)            ((x) - 1)
>> +
>> +#define CSI2_VC_MODE                ADAP_REG_F(0x1c)
>> +#define ADAP_FD_VS_SEL_VC_MASK      GENMASK(19, 16)
>> +#define ADAP_FD_VS_DIRECT_PATH      BIT(16)
>> +#define ADAP_FD_HS_SEL_VC_MASK      GENMASK(23, 20)
>> +#define ADAP_FD_HS_DIRECT_PATH      BIT(20)
>> +
>> +/* C3 MIPI adapter READER register */
>> +#define MIPI_ADAPT_DDR_RD0_CNTL0    ADAP_REG_R(0x00)
>> +#define ADAP_RD0_MODULE_ENABLE      BIT(0)
>> +#define ADAP_RD0_LINE_STRIDE_MASK   GENMASK(13, 4)
>> +#define ADAP_RD0_LINE_STRIDE_SHIFT  4
>> +#define ADAP_RD0_SAMPLE_SEL_MASK    GENMASK(27, 26)
>> +#define ADAP_RD0_DATA_IN_VSYNC      BIT(26)
>> +#define ADAP_RD0_BURST_TYPE_MASK    GENMASK(29, 28)
>> +#define ADAP_RD0_BURST_TYPE_SHIFT   28
>> +#define ADAP_RD0_BURST_TYPE_INRC8   3
>> +#define ADAP_RD0_FRAME_RD_START     BIT(31)
>> +
>> +#define MIPI_ADAPT_DDR_RD0_CNTL1    ADAP_REG_R(0x04)
>> +#define ADAP_RD0_LINE_SIZE_MASK     GENMASK(9, 0)
>> +#define ADAP_RD0_LINE_NUM_MASK      GENMASK(28, 16)
>> +#define ADAP_RD0_LINE_NUM_SHIFT     16
>> +
>> +#define MIPI_ADAPT_PIXEL0_CNTL0     ADAP_REG_R(0x80)
>> +#define ADAP_PIXEL0_WORK_MODE_MASK  GENMASK(17, 16)
>> +#define ADAP_PIXEL0_DIRECT_PATH     BIT(16)
>> +#define ADAP_PIXEL0_DATA_TYPE_MASK  GENMASK(25, 20)
>> +#define ADAP_PIXEL0_DATA_TYPE_SHIFT 20
>> +#define ADAP_PIXEL0_DATA_TYPE_10BITS 0x2b
>> +#define ADAP_PIXEL0_DATA_TYPE_12BITS 0x2c
>> +#define ADAP_PIXEL0_START_ENABLE    BIT(31)
>> +
>> +#define MIPI_ADAPT_PIXEL0_CNTL1     ADAP_REG_R(0x84)
>> +#define ADAP_PIXEL0_X_END_MASK      GENMASK(15, 0)
>> +#define ADAP_PIXEL0_X_END(x)        ((x) - 1)
>> +
>> +#define MIPI_ADAPT_PIXEL0_CNTL2     ADAP_REG_R(0x88)
>> +#define ADAP_PIXEL0_FIFO_SIZE_MASK  GENMASK(9, 0)
>> +#define ADAP_PIXEL0_PIXEL_NUM_MASK  GENMASK(27, 15)
>> +#define ADAP_PIXEL0_PIXEL_NUM_SHIFT 15
>> +
>> +#define MIPI_ADAPT_ALIG_CNTL0       ADAP_REG_R(0x100)
>> +#define ADAP_ALIG_V_TOTAL_NUM_MASK  GENMASK(15, 0)
>> +/* Need to add a default blank */
>> +#define ADAP_ALIG_V_TOTAL_NUM(x)    ((x) + 64)
>> +#define ADAP_ALIG_H_TOTAL_NUM_MASK  GENMASK(31, 16)
>> +#define ADAP_ALIG_H_TOTAL_NUM_SHIFT 16
>> +/* Need to add a default blank */
>> +#define ADAP_ALIG_H_TOTAL_NUM(x)    ((x) + 64)
>> +
>> +#define MIPI_ADAPT_ALIG_CNTL1       ADAP_REG_R(0x104)
>> +#define ADAP_ALIG_HPE_NUM_MASK      GENMASK(31, 16)
>> +#define ADAP_ALIG_HPE_NUM_SHIFT     16
>> +
>> +#define MIPI_ADAPT_ALIG_CNTL2       ADAP_REG_R(0x108)
>> +#define ADAP_ALIG_VPE_NUM_MASK      GENMASK(31, 16)
>> +#define ADAP_ALIG_VPE_NUM_SHIFT     16
>> +
>> +#define MIPI_ADAPT_ALIG_CNTL3       ADAP_REG_R(0x10c)
>> +#define ADAP_ALIG_FRM_ST_PIXEL_MASK GENMASK(15, 0)
>> +
>> +#define MIPI_ADAPT_ALIG_CNTL6       ADAP_REG_R(0x118)
>> +#define ADAP_ALIG_LANE0_ENABLE      BIT(0)
>> +#define ADAP_ALIG_DATA_MODE0_MASK   BIT(4)
>> +#define ADAP_ALIG_DIRECT_MODE       BIT(4)
>> +#define ADAP_ALIG_VDATA0_ENABLE     BIT(12)
>> +#define ADAP_ALIG_VDATA1_ENABLE     BIT(13)
>> +#define ADAP_ALIG_VDATA2_ENABLE     BIT(14)
>> +#define ADAP_ALIG_VDATA3_ENABLE     BIT(15)
>> +
>> +#define MIPI_ADAPT_ALIG_CNTL8       ADAP_REG_R(0x120)
>> +#define ADAP_ALIG_FRAME_CONTINUE    BIT(5)
>> +#define ADAP_ALIG_EXC_MASK_DIS      BIT(12)
>> +#define ADAP_ALIG_START_ENABLE      BIT(31)
>> +
>> +#define MIPI_ADAP_MAX_WIDTH         2888
>> +#define MIPI_ADAP_MIN_WIDTH         160
>> +#define MIPI_ADAP_MAX_HEIGHT        2240
>> +#define MIPI_ADAP_MIN_HEIGHT        120
>> +#define MIPI_ADAP_DEFAULT_WIDTH     1920
>> +#define MIPI_ADAP_DEFAULT_HEIGHT    1080
>> +#define MIPI_ADAP_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
>> +
>> +/* C3 MIPI adapter pad list */
>> +enum {
>> +     MIPI_ADAP_PAD_SINK,
>> +     MIPI_ADAP_PAD_SRC,
>> +     MIPI_ADAP_PAD_MAX
>> +};
>> +
>> +/*
>> + * struct adap_info - mipi adapter information
>> + *
>> + * @clocks: array of mipi adapter clock names
>> + * @clock_rates: array of mipi adapter clock rate
>> + * @clock_num: actual clock number
>> + */
>> +struct adap_info {
>> +     char *clocks[MIPI_ADAP_CLOCK_NUM_MAX];
>> +     u32 clock_rates[MIPI_ADAP_CLOCK_NUM_MAX];
>> +     u32 clock_num;
>> +};
>> +
>> +/**
> Here as well you can drop one * from /**


Will drop one  * from  /**

>> + * struct adap_device - mipi adapter platform device
>> + *
>> + * @dev: pointer to the struct device
>> + * @top: mipi adapter top register address
>> + * @fd: mipi adapter frontend register address
>> + * @rd: mipi adapter reader register address
>> + * @clks: array of MIPI adapter clocks
>> + * @sd: mipi adapter sub-device
>> + * @pads: mipi adapter sub-device pads
>> + * @notifier: notifier to register on the v4l2-async API
>> + * @format: save sub-device format
>> + * @src_sd: source sub-device
>> + * @src_sd_pad: source sub-device pad
>> + * @lock: protect mipi adapter device
>> + * @info: version-specific MIPI adapter information
>> + */
>> +struct adap_device {
>> +     struct device *dev;
>> +     void __iomem *top;
>> +     void __iomem *fd;
>> +     void __iomem *rd;
>> +     struct clk_bulk_data clks[MIPI_ADAP_CLOCK_NUM_MAX];
>> +
>> +     struct v4l2_subdev sd;
>> +     struct media_pad pads[MIPI_ADAP_PAD_MAX];
>> +     struct v4l2_async_notifier notifier;
>> +     struct v4l2_subdev_format format;
>> +     struct v4l2_subdev *src_sd;
>> +
>> +     u16 src_sd_pad;
>> +     struct mutex lock; /* Protect adapter device */
> Most comments on the csi2-rx patch apply to this one as well.
> In this case, you don't need locking for functions that receive a
> subdev_state
>
Will remove this lock.
>> +     const struct adap_info *info;
>> +};
>> +
>> +/* Format helpers */
>> +
>> +struct adap_pix_format {
>> +     u32 code;
>> +     u8 width;
> I would call it bpp
Will replace width with bpp
>> +};
>> +
>> +static const struct adap_pix_format c3_mipi_adap_formats[] = {
>> +     { MEDIA_BUS_FMT_SBGGR10_1X10, 10 },
>> +     { MEDIA_BUS_FMT_SGBRG10_1X10, 10 },
>> +     { MEDIA_BUS_FMT_SGRBG10_1X10, 10 },
>> +     { MEDIA_BUS_FMT_SRGGB10_1X10, 10 },
>> +     { MEDIA_BUS_FMT_SBGGR12_1X12, 12 },
>> +     { MEDIA_BUS_FMT_SGBRG12_1X12, 12 },
>> +     { MEDIA_BUS_FMT_SGRBG12_1X12, 12 },
>> +     { MEDIA_BUS_FMT_SRGGB12_1X12, 12 },
>> +};
>> +
>> +static const struct adap_pix_format
>> +*c3_mipi_adap_find_format(u32 code)
>> +{
>> +     unsigned int i;
>> +
>> +     for (i = 0; i < ARRAY_SIZE(c3_mipi_adap_formats); i++)
>> +             if (code == c3_mipi_adap_formats[i].code)
>> +                     return &c3_mipi_adap_formats[i];
>> +
>> +     return NULL;
>> +}
>> +
>> +/* Hardware configuration */
>> +
>> +static void c3_mipi_adap_update_bits(struct adap_device *adap, u32 reg,
>> +                                  u32 mask, u32 val)
>> +{
>> +     void __iomem *addr;
>> +     u32 orig, tmp;
>> +
>> +     switch (ADAP_SUBMD(reg)) {
>> +     case SUBMD_TOP:
>> +             addr = adap->top + ADAP_REG_ADDR(reg);
>> +             break;
>> +     case SUBMD_FD:
>> +             addr = adap->fd + ADAP_REG_ADDR(reg);
>> +             break;
>> +     case SUBMD_RD:
>> +             addr = adap->rd + ADAP_REG_ADDR(reg);
>> +             break;
>> +     default:
>> +             dev_err(adap->dev, "Invalid sub-module: %lu\n", ADAP_SUBMD(reg));
>> +             return;
>> +     }
>> +
>> +     orig = readl(addr);
>> +     tmp = orig & ~mask;
>> +     tmp |= val & mask;
>> +
>> +     if (tmp != orig)
>> +             writel(tmp, addr);
>> +}
>> +
>> +/* Configure adapter top sub module */
>> +static void c3_mipi_adap_cfg_top(struct adap_device *adap)
>> +{
>> +     /* Bypass decompress */
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0,
>> +                              ADAP_DE_READ_BYPASS, ADAP_DE_READ_BYPASS);
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0,
>> +                              ADAP_DE_WRITE_BYPASS, ADAP_DE_WRITE_BYPASS);
>> +}
>> +
>> +/* Configure adapter frontend sub module */
>> +static void c3_mipi_adap_cfg_frontend(struct adap_device *adap,
>> +                                   struct v4l2_mbus_framefmt *fmt)
>> +{
>> +     /* The default value of BIT_0 is 1, so need release reset firstly. */
>> +     c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET, ADAP_FD_APPLY_RESET, 0);
>> +
>> +     c3_mipi_adap_update_bits(adap, CSI2_X_START_END_ISP, ADAP_FD_X_END_MASK,
>> +                              ADAP_FD_X_END(fmt->width) << ADAP_FD_X_END_SHIFT);
>> +     c3_mipi_adap_update_bits(adap, CSI2_Y_START_END_ISP, ADAP_FD_Y_END_MASK,
>> +                              ADAP_FD_Y_END(fmt->height) << ADAP_FD_Y_END_SHIFT);
>> +
>> +     /* Select VS and HS signal to direct path */
>> +     c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, ADAP_FD_VS_SEL_VC_MASK,
>> +                              ADAP_FD_VS_DIRECT_PATH);
>> +     c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, ADAP_FD_HS_SEL_VC_MASK,
>> +                              ADAP_FD_HS_DIRECT_PATH);
>> +
>> +     /* Enable to receive RAW image */
>> +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_ENABLE_PACKETS,
>> +                              ADAP_FD_ENABLE_RAW);
>> +
>> +     /* Enable virtual channel 0~3 */
>> +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN0_EN,
>> +                              ADAP_FD_VIRTUAL_CHN0_EN);
>> +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN1_EN,
>> +                              ADAP_FD_VIRTUAL_CHN1_EN);
>> +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN2_EN,
>> +                              ADAP_FD_VIRTUAL_CHN2_EN);
>> +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN3_EN,
>> +                              ADAP_FD_VIRTUAL_CHN3_EN);
>> +}
>> +
>> +/* Configure adapter reader sub module */
>> +static void c3_mipi_adap_cfg_reader(struct adap_device *adap,
>> +                                 struct v4l2_mbus_framefmt *fmt)
>> +{
>> +     const struct adap_pix_format *pix_format;
>> +     u32 line_size;
>> +     u8 data_type;
>> +
>> +     /* Data size for a line, unit: 128 bits */
>> +     pix_format = c3_mipi_adap_find_format(fmt->code);
>> +     line_size = fmt->width * pix_format->width;
>> +     line_size = DIV_ROUND_UP(line_size, 128);
>> +
>> +     if (pix_format->width == 10) {
>> +             data_type = ADAP_PIXEL0_DATA_TYPE_10BITS;
>> +     } else if (pix_format->width == 12) {
>> +             data_type = ADAP_PIXEL0_DATA_TYPE_12BITS;
> You should use MIPI_CSI2_DT_RAW10 and MIPI_CSI2_DT_RAW12 from
> "include/media/mipi-csi2.h"
>
OK,  Will use MIPI_CSI2_DT_RAW10 and MIPI_CSI2_DT_RAW12.
>> +     } else {
>> +             dev_err(adap->dev, "Invalid raw format width: %u\n", pix_format->width);
>> +             return;
>> +     }
>> +
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL1,
>> +                              ADAP_RD0_LINE_SIZE_MASK, line_size);
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL1, ADAP_RD0_LINE_NUM_MASK,
>> +                              fmt->height << ADAP_RD0_LINE_NUM_SHIFT);
>> +
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_MODULE_ENABLE,
>> +                              ADAP_RD0_MODULE_ENABLE);
> I'm not sure I get this. I read this bit as "Enable reading from DDR"
> while I thought the adapter receives data directly from the CSI-2
> receiver

This place is a bit confusing.
I will check this issue  and add explanation.


>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_LINE_STRIDE_MASK,
>> +                              line_size << ADAP_RD0_LINE_STRIDE_SHIFT);
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_SAMPLE_SEL_MASK,
>> +                              ADAP_RD0_DATA_IN_VSYNC);
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_BURST_TYPE_MASK,
>> +                              ADAP_RD0_BURST_TYPE_INRC8 << ADAP_RD0_BURST_TYPE_SHIFT);
>> +
>> +     /* Set data type and work mode */
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0,
>> +                              ADAP_PIXEL0_WORK_MODE_MASK, ADAP_PIXEL0_DIRECT_PATH);
> Ah maybe this setting "RAW from direct path" enables direct path from
> CSI-2 receiver
>

PIXEL0 is a sub module in adapter and should be configured.

>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0, ADAP_PIXEL0_DATA_TYPE_MASK,
>> +                              data_type << ADAP_PIXEL0_DATA_TYPE_SHIFT);
>> +
>> +     /* Set pixel end number */
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL1, ADAP_PIXEL0_X_END_MASK,
>> +                              ADAP_PIXEL0_X_END(fmt->width));
>> +
>> +     /* Set line pixel number and reader fifo size */
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL2,
>> +                              ADAP_PIXEL0_FIFO_SIZE_MASK, line_size);
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL2, ADAP_PIXEL0_PIXEL_NUM_MASK,
>> +                              fmt->width << ADAP_PIXEL0_PIXEL_NUM_SHIFT);
>> +
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, ADAP_ALIG_V_TOTAL_NUM_MASK,
>> +                              ADAP_ALIG_V_TOTAL_NUM(fmt->width));
> What are the ISP alignment requirements ?
>
> #define ADAP_ALIG_V_TOTAL_NUM(x)    ((x) + 64)
>
> Are you aligning or just adding 64 here ?


ISP hardware requires at least 40 v-blanks,

so here add 64 to ensure ISP can function properly.

>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, ADAP_ALIG_H_TOTAL_NUM_MASK,
>> +                              ADAP_ALIG_H_TOTAL_NUM(fmt->height)
>> +                              << ADAP_ALIG_H_TOTAL_NUM_SHIFT);
>> +
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL1, ADAP_ALIG_HPE_NUM_MASK,
>> +                              fmt->width << ADAP_ALIG_HPE_NUM_SHIFT);
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL2, ADAP_ALIG_VPE_NUM_MASK,
>> +                              fmt->height << ADAP_ALIG_VPE_NUM_SHIFT);
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL3,
>> +                              ADAP_ALIG_FRM_ST_PIXEL_MASK, fmt->width);
>> +
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
>> +                              ADAP_ALIG_LANE0_ENABLE, ADAP_ALIG_LANE0_ENABLE);
>> +
>> +     /* Select direct mode */
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
>> +                              ADAP_ALIG_DATA_MODE0_MASK, ADAP_ALIG_DIRECT_MODE);
>> +
>> +     /* Enable vdata 0~3 */
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
>> +                              ADAP_ALIG_VDATA0_ENABLE, ADAP_ALIG_VDATA0_ENABLE);
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
>> +                              ADAP_ALIG_VDATA1_ENABLE, ADAP_ALIG_VDATA1_ENABLE);
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
>> +                              ADAP_ALIG_VDATA2_ENABLE, ADAP_ALIG_VDATA2_ENABLE);
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
>> +                              ADAP_ALIG_VDATA3_ENABLE, ADAP_ALIG_VDATA2_ENABLE);
> For my education could you tell what these bit do ?
Will add explanation.
>> +
>> +     /* continue mode and disable hold counter */
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
>> +                              ADAP_ALIG_FRAME_CONTINUE, ADAP_ALIG_FRAME_CONTINUE);
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
>> +                              ADAP_ALIG_EXC_MASK_DIS, ADAP_ALIG_EXC_MASK_DIS);
>> +}
>> +
>> +static void c3_mipi_adap_start_stream(struct adap_device *adap)
>> +{
>> +     /* Enable to start and will auto clear to 0 */
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
>> +                              ADAP_ALIG_START_ENABLE, ADAP_ALIG_START_ENABLE);
>> +
>> +     /* Enable to start and will auto clear to 0 */
>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0,
>> +                              ADAP_PIXEL0_START_ENABLE, ADAP_PIXEL0_START_ENABLE);
>> +
>> +     /* Enable frontend */
>> +     c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET,
>> +                              ADAP_FD_ENABLE, ADAP_FD_ENABLE);
>> +}
>> +
>> +static void c3_mipi_adap_cfg_format(struct adap_device *adap)
>> +{
>> +     struct v4l2_subdev_format *format = &adap->format;
>> +
>> +     c3_mipi_adap_cfg_top(adap);
>> +     c3_mipi_adap_cfg_frontend(adap, &format->format);
>> +     c3_mipi_adap_cfg_reader(adap, &format->format);
>> +}
>> +
>> +/* V4L2 subdev operations */
>> +
>> +static int c3_mipi_adap_enable_streams(struct v4l2_subdev *sd,
>> +                                    struct v4l2_subdev_state *state,
>> +                                    u32 pad, u64 streams_mask)
>> +{
>> +     struct adap_device *adap = v4l2_get_subdevdata(sd);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     guard(mutex)(&adap->lock);
>> +
>> +     pm_runtime_resume_and_get(adap->dev);
>> +
>> +     c3_mipi_adap_cfg_format(adap);
>> +     c3_mipi_adap_start_stream(adap);
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +                                                    MIPI_ADAP_PAD_SINK,
>> +                                                    &streams_mask);
>> +     ret = v4l2_subdev_enable_streams(adap->src_sd,
>> +                                      adap->src_sd_pad,
>> +                                      sink_streams);
>> +     if (ret) {
>> +             pm_runtime_put(adap->dev);
>> +             return ret;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_adap_disable_streams(struct v4l2_subdev *sd,
>> +                                     struct v4l2_subdev_state *state,
>> +                                     u32 pad, u64 streams_mask)
>> +{
>> +     struct adap_device *adap = v4l2_get_subdevdata(sd);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     guard(mutex)(&adap->lock);
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +                                                    MIPI_ADAP_PAD_SINK,
>> +                                                    &streams_mask);
>> +     ret = v4l2_subdev_disable_streams(adap->src_sd,
>> +                                       adap->src_sd_pad,
>> +                                       sink_streams);
>> +     if (ret)
>> +             dev_err(adap->dev, "Failed to disable %s\n", adap->src_sd->name);
>> +
>> +     pm_runtime_put(adap->dev);
>> +
>> +     return ret;
>> +}
>> +
>> +static int c3_mipi_adap_cfg_routing(struct v4l2_subdev *sd,
>> +                                 struct v4l2_subdev_state *state,
>> +                                 struct v4l2_subdev_krouting *routing)
>> +{
>> +     static const struct v4l2_mbus_framefmt format = {
>> +             .width = MIPI_ADAP_DEFAULT_WIDTH,
>> +             .height = MIPI_ADAP_DEFAULT_HEIGHT,
>> +             .code = MIPI_ADAP_DEFAULT_FMT,
>> +             .field = V4L2_FIELD_NONE,
>> +             .colorspace = V4L2_COLORSPACE_RAW,
>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>> +             .xfer_func = V4L2_XFER_FUNC_NONE,
>> +     };
>> +     int ret;
>> +
>> +     ret = v4l2_subdev_routing_validate(sd, routing,
>> +                                        V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_adap_init_routing(struct v4l2_subdev *sd,
>> +                                  struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_subdev_route routes;
>> +     struct v4l2_subdev_krouting routing;
>> +
>> +     routes.sink_pad = MIPI_ADAP_PAD_SINK;
>> +     routes.sink_stream = 0;
>> +     routes.source_pad = MIPI_ADAP_PAD_SRC;
>> +     routes.source_stream = 0;
>> +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>> +
>> +     routing.num_routes = 1;
>> +     routing.routes = &routes;
>> +
>> +     return c3_mipi_adap_cfg_routing(sd, state, &routing);
>> +}
> Same comments as per the CSI-2 Rx, do you need to allow set routing
> when a single route in enabled and valid ?
>

Will drop the set_routing() interface.

>> +
>> +static int c3_mipi_adap_set_routing(struct v4l2_subdev *sd,
>> +                                 struct v4l2_subdev_state *state,
>> +                                 enum v4l2_subdev_format_whence which,
>> +                                 struct v4l2_subdev_krouting *routing)
>> +{
>> +     bool is_streaming = v4l2_subdev_is_streaming(sd);
>> +
>> +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>> +             return -EBUSY;
>> +
>> +     return c3_mipi_adap_cfg_routing(sd, state, routing);
>> +}
>> +
>> +static int c3_mipi_adap_enum_mbus_code(struct v4l2_subdev *sd,
>> +                                    struct v4l2_subdev_state *state,
>> +                                    struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +     switch (code->pad) {
>> +     case MIPI_ADAP_PAD_SINK:
>> +             if (code->index >= ARRAY_SIZE(c3_mipi_adap_formats))
>> +                     return -EINVAL;
>> +
>> +             code->code = c3_mipi_adap_formats[code->index].code;
>> +             break;
>> +     case MIPI_ADAP_PAD_SRC:
>> +             struct v4l2_mbus_framefmt *fmt;
>> +
>> +             if (code->index > 0)
>> +                     return -EINVAL;
>> +
>> +             fmt = v4l2_subdev_state_get_format(state, code->pad);
>> +             code->code = fmt->code;
>> +             break;
>> +     default:
>> +             return -EINVAL;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_adap_set_fmt(struct v4l2_subdev *sd,
>> +                             struct v4l2_subdev_state *state,
>> +                             struct v4l2_subdev_format *format)
>> +{
>> +     struct adap_device *adap = v4l2_get_subdevdata(sd);
>> +     struct v4l2_mbus_framefmt *fmt;
>> +     const struct adap_pix_format *pix_format;
>> +
>> +     if (format->pad != MIPI_ADAP_PAD_SINK)
>> +             return v4l2_subdev_get_fmt(sd, state, format);
>> +
>> +     pix_format = c3_mipi_adap_find_format(format->format.code);
>> +     if (!pix_format)
>> +             pix_format = &c3_mipi_adap_formats[0];
>> +
>> +     fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +     fmt->code = pix_format->code;
>> +     fmt->width = clamp_t(u32, format->format.width,
>> +                          MIPI_ADAP_MIN_WIDTH, MIPI_ADAP_MAX_WIDTH);
>> +     fmt->height = clamp_t(u32, format->format.height,
>> +                           MIPI_ADAP_MIN_HEIGHT, MIPI_ADAP_MAX_HEIGHT);
>> +
>> +     format->format = *fmt;
>> +
>> +     /* Synchronize the format to source pad */
>> +     fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SRC);
>> +     *fmt = format->format;
>> +
>> +     adap->format = *format;
> There is not need to store the format in the driver-specific
> structure. You use it c3_mipi_adap_cfg_format() called by
> c3_mipi_adap_enable_streams() which receives a subdev_state where the format
> is stored already.
>
> Thanks
>    j


Will remove the adap->format

>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_adap_init_state(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SINK);
>> +     src_fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SRC);
>> +
>> +     sink_fmt->width = MIPI_ADAP_DEFAULT_WIDTH;
>> +     sink_fmt->height = MIPI_ADAP_DEFAULT_HEIGHT;
>> +     sink_fmt->field = V4L2_FIELD_NONE;
>> +     sink_fmt->code = MIPI_ADAP_DEFAULT_FMT;
>> +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>> +     sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
>> +     sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
>> +     sink_fmt->quantization =
>> +             V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
>> +                                           sink_fmt->ycbcr_enc);
>> +     *src_fmt = *sink_fmt;
>> +
>> +     return c3_mipi_adap_init_routing(sd, state);
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops c3_mipi_adap_pad_ops = {
>> +     .enum_mbus_code = c3_mipi_adap_enum_mbus_code,
>> +     .get_fmt = v4l2_subdev_get_fmt,
>> +     .set_fmt = c3_mipi_adap_set_fmt,
>> +     .set_routing = c3_mipi_adap_set_routing,
>> +     .enable_streams = c3_mipi_adap_enable_streams,
>> +     .disable_streams = c3_mipi_adap_disable_streams,
>> +};
>> +
>> +static const struct v4l2_subdev_ops c3_mipi_adap_subdev_ops = {
>> +     .pad = &c3_mipi_adap_pad_ops,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops c3_mipi_adap_internal_ops = {
>> +     .init_state = c3_mipi_adap_init_state,
>> +};
>> +
>> +/* Media entity operations */
>> +static const struct media_entity_operations c3_mipi_adap_entity_ops = {
>> +     .link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +/* PM runtime */
>> +
>> +static int __maybe_unused c3_mipi_adap_runtime_suspend(struct device *dev)
>> +{
>> +     struct adap_device *adap = dev_get_drvdata(dev);
>> +
>> +     clk_bulk_disable_unprepare(adap->info->clock_num, adap->clks);
>> +
>> +     return 0;
>> +}
>> +
>> +static int __maybe_unused c3_mipi_adap_runtime_resume(struct device *dev)
>> +{
>> +     struct adap_device *adap = dev_get_drvdata(dev);
>> +
>> +     return clk_bulk_prepare_enable(adap->info->clock_num, adap->clks);
>> +}
>> +
>> +static const struct dev_pm_ops c3_mipi_adap_pm_ops = {
>> +     SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>> +                             pm_runtime_force_resume)
>> +     SET_RUNTIME_PM_OPS(c3_mipi_adap_runtime_suspend,
>> +                        c3_mipi_adap_runtime_resume, NULL)
>> +};
>> +
>> +/* Probe/remove & platform driver */
>> +
>> +static int c3_mipi_adap_subdev_init(struct adap_device *adap)
>> +{
>> +     struct v4l2_subdev *sd = &adap->sd;
>> +     int ret;
>> +
>> +     v4l2_subdev_init(sd, &c3_mipi_adap_subdev_ops);
>> +     sd->owner = THIS_MODULE;
>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +     sd->internal_ops = &c3_mipi_adap_internal_ops;
>> +     snprintf(sd->name, sizeof(sd->name), "%s", MIPI_ADAP_SUBDEV_NAME);
>> +
>> +     sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>> +     sd->entity.ops = &c3_mipi_adap_entity_ops;
>> +
>> +     sd->dev = adap->dev;
>> +     v4l2_set_subdevdata(sd, adap);
>> +
>> +     adap->pads[MIPI_ADAP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>> +     adap->pads[MIPI_ADAP_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
>> +     ret = media_entity_pads_init(&sd->entity, MIPI_ADAP_PAD_MAX, adap->pads);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_init_finalize(sd);
>> +     if (ret) {
>> +             media_entity_cleanup(&sd->entity);
>> +             return ret;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_mipi_adap_subdev_deinit(struct adap_device *adap)
>> +{
>> +     v4l2_subdev_cleanup(&adap->sd);
>> +     media_entity_cleanup(&adap->sd.entity);
>> +}
>> +
>> +/* Subdev notifier register */
>> +static int c3_mipi_adap_notify_bound(struct v4l2_async_notifier *notifier,
>> +                                  struct v4l2_subdev *sd,
>> +                                  struct v4l2_async_connection *asc)
>> +{
>> +     struct adap_device *adap = v4l2_get_subdevdata(notifier->sd);
>> +     struct media_pad *sink = &adap->sd.entity.pads[MIPI_ADAP_PAD_SINK];
>> +     int ret;
>> +
>> +     ret = media_entity_get_fwnode_pad(&sd->entity,
>> +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
>> +     if (ret < 0) {
>> +             dev_err(adap->dev, "Failed to find pad for %s\n", sd->name);
>> +             return ret;
>> +     }
>> +
>> +     adap->src_sd = sd;
>> +     adap->src_sd_pad = ret;
>> +
>> +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
>> +                                            MEDIA_LNK_FL_IMMUTABLE);
>> +}
>> +
>> +static const struct v4l2_async_notifier_operations c3_mipi_adap_notify_ops = {
>> +     .bound = c3_mipi_adap_notify_bound,
>> +};
>> +
>> +static int c3_mipi_adap_async_register(struct adap_device *adap)
>> +{
>> +     struct v4l2_async_connection *asc;
>> +     struct fwnode_handle *ep;
>> +     int ret;
>> +
>> +     v4l2_async_subdev_nf_init(&adap->notifier, &adap->sd);
>> +
>> +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(adap->dev), 0, 0,
>> +                                          FWNODE_GRAPH_ENDPOINT_NEXT);
>> +     if (!ep)
>> +             return -ENOTCONN;
>> +
>> +     asc = v4l2_async_nf_add_fwnode_remote(&adap->notifier, ep,
>> +                                           struct v4l2_async_connection);
>> +     if (IS_ERR(asc)) {
>> +             ret = PTR_ERR(asc);
>> +             goto err_put_handle;
>> +     }
>> +
>> +     adap->notifier.ops = &c3_mipi_adap_notify_ops;
>> +     ret = v4l2_async_nf_register(&adap->notifier);
>> +     if (ret)
>> +             goto err_cleanup_nf;
>> +
>> +     ret = v4l2_async_register_subdev(&adap->sd);
>> +     if (ret)
>> +             goto err_unregister_nf;
>> +
>> +     fwnode_handle_put(ep);
>> +
>> +     return 0;
>> +
>> +err_unregister_nf:
>> +     v4l2_async_nf_unregister(&adap->notifier);
>> +err_cleanup_nf:
>> +     v4l2_async_nf_cleanup(&adap->notifier);
>> +err_put_handle:
>> +     fwnode_handle_put(ep);
>> +     return ret;
>> +}
>> +
>> +static void c3_mipi_adap_async_unregister(struct adap_device *adap)
>> +{
>> +     v4l2_async_unregister_subdev(&adap->sd);
>> +     v4l2_async_nf_unregister(&adap->notifier);
>> +     v4l2_async_nf_cleanup(&adap->notifier);
>> +}
>> +
>> +static int c3_mipi_adap_ioremap_resource(struct adap_device *adap)
>> +{
>> +     struct device *dev = adap->dev;
>> +     struct platform_device *pdev = to_platform_device(dev);
>> +
>> +     adap->top = devm_platform_ioremap_resource_byname(pdev, "top");
>> +     if (IS_ERR(adap->top))
>> +             return PTR_ERR(adap->top);
>> +
>> +     adap->fd = devm_platform_ioremap_resource_byname(pdev, "fd");
>> +     if (IS_ERR(adap->fd))
>> +             return PTR_ERR(adap->fd);
>> +
>> +     adap->rd = devm_platform_ioremap_resource_byname(pdev, "rd");
>> +     if (IS_ERR(adap->rd))
>> +             return PTR_ERR(adap->rd);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_adap_configure_clocks(struct adap_device *adap)
>> +{
>> +     const struct adap_info *info = adap->info;
>> +     int ret;
>> +     u32 i;
>> +
>> +     for (i = 0; i < info->clock_num; i++)
>> +             adap->clks[i].id = info->clocks[i];
>> +
>> +     ret = devm_clk_bulk_get(adap->dev, info->clock_num, adap->clks);
>> +     if (ret)
>> +             return ret;
>> +
>> +     for (i = 0; i < info->clock_num; i++) {
>> +             if (!info->clock_rates[i])
>> +                     continue;
>> +             ret = clk_set_rate(adap->clks[i].clk, info->clock_rates[i]);
>> +             if (ret) {
>> +                     dev_err(adap->dev, "Failed to set %s rate %u\n", info->clocks[i],
>> +                             info->clock_rates[i]);
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_mipi_adap_probe(struct platform_device *pdev)
>> +{
>> +     struct device *dev = &pdev->dev;
>> +     struct adap_device *adap;
>> +     int ret;
>> +
>> +     adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL);
>> +     if (!adap)
>> +             return -ENOMEM;
>> +
>> +     adap->info = of_device_get_match_data(dev);
>> +     adap->dev = dev;
>> +
>> +     ret = c3_mipi_adap_ioremap_resource(adap);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
>> +
>> +     ret = c3_mipi_adap_configure_clocks(adap);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
>> +
>> +     platform_set_drvdata(pdev, adap);
>> +
>> +     mutex_init(&adap->lock);
>> +     pm_runtime_enable(dev);
>> +
>> +     ret = c3_mipi_adap_subdev_init(adap);
>> +     if (ret < 0)
>> +             goto err_disable_runtime_pm;
>> +
>> +     ret = c3_mipi_adap_async_register(adap);
>> +     if (ret < 0)
>> +             goto err_deinit_subdev;
>> +
>> +     return 0;
>> +
>> +err_deinit_subdev:
>> +     c3_mipi_adap_subdev_deinit(adap);
>> +err_disable_runtime_pm:
>> +     pm_runtime_disable(dev);
>> +     mutex_destroy(&adap->lock);
>> +     return ret;
>> +};
>> +
>> +static void c3_mipi_adap_remove(struct platform_device *pdev)
>> +{
>> +     struct adap_device *adap = platform_get_drvdata(pdev);
>> +
>> +     c3_mipi_adap_async_unregister(adap);
>> +     c3_mipi_adap_subdev_deinit(adap);
>> +
>> +     pm_runtime_disable(&pdev->dev);
>> +     mutex_destroy(&adap->lock);
>> +};
>> +
>> +static const struct adap_info c3_mipi_adap_info = {
>> +     .clocks = {"vapb", "isp0"},
>> +     .clock_rates = {0, 400000000},
>> +     .clock_num = 2
>> +};
>> +
>> +static const struct of_device_id c3_mipi_adap_of_match[] = {
>> +     { .compatible = "amlogic,c3-mipi-adapter",
>> +       .data = &c3_mipi_adap_info },
>> +     { },
>> +};
>> +MODULE_DEVICE_TABLE(of, c3_mipi_adap_of_match);
>> +
>> +static struct platform_driver c3_mipi_adap_driver = {
>> +     .probe = c3_mipi_adap_probe,
>> +     .remove = c3_mipi_adap_remove,
>> +     .driver = {
>> +             .name = "c3-mipi-adapter",
>> +             .of_match_table = c3_mipi_adap_of_match,
>> +             .pm = &c3_mipi_adap_pm_ops,
>> +     },
>> +};
>> +
>> +module_platform_driver(c3_mipi_adap_driver);
>> +
>> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
>> +MODULE_DESCRIPTION("Amlogic C3 MIPI adapter");
>> +MODULE_LICENSE("GPL");
>>
>> --
>> 2.46.1
>>
>>
>>

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

* Re: [PATCH v3 4/9] media: platform: Add c3 mipi adapter driver
  2024-11-06  5:32     ` Keke Li
@ 2024-11-07  8:48       ` Jacopo Mondi
  2024-11-08  7:48         ` Keke Li
  0 siblings, 1 reply; 37+ messages in thread
From: Jacopo Mondi @ 2024-11-07  8:48 UTC (permalink / raw)
  To: Keke Li
  Cc: Jacopo Mondi, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-media, devicetree,
	linux-kernel, kieran.bingham, laurent.pinchart, dan.scally

Hi Keke

On Wed, Nov 06, 2024 at 01:32:58PM +0800, Keke Li wrote:
> Hi Jacopo
>
> Thanks very much for your reply.
>
> On 2024/11/5 19:24, Jacopo Mondi wrote:
> > [You don't often get email from jacopo.mondi@ideasonboard.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
> >
> > [ EXTERNAL EMAIL ]
> >
> > Hi Keke
> >
> > On Wed, Sep 18, 2024 at 02:07:15PM +0800, Keke Li via B4 Relay wrote:
> > > From: Keke Li <keke.li@amlogic.com>
> > >
> > > This driver mainly responsible for organizing
> > > MIPI data and sending raw data to ISP pipeline.
> > A few questions on the design of this subdevice.
> >
> > In my understanding the adapter provides several functionalities:
> >
> > - Data alignement to match the ISP size constraints
> > - Read from DDR memory or from the CSI-2 RX (direct path)
> > - Write to DDR memory or to the ISP (direct path)
> > - Split data streams to DDR or direct path based on CSI-2 Virtual
> >    Channel and Data type.
>
>
> Yes, you are right.
>
> > As I understand the current implementation only supports direct-path
> > to ISP of data sent on VC#0.
>
>
> Read image from DDR and wirte image to DDR  are used in WDR mode.
>
> Due to the lack of WDR requirement,  This driver only support direct-path.
>
> > Do you plan to expand the adapter subdev with support for mulitple
> > paths ? Is this why you are supporting already a routing table ?
>
> If there is a demand in the future, I will support multiple paths.
>
> But now I attend to only support one path and will drop the set_routing()
> interface.
>
>
> > > Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
> > > Signed-off-by: Keke Li <keke.li@amlogic.com>
> > > ---
> > >   MAINTAINERS                                        |   7 +
> > >   drivers/media/platform/amlogic/Kconfig             |   1 +
> > >   drivers/media/platform/amlogic/Makefile            |   1 +
> > >   .../media/platform/amlogic/c3-mipi-adapter/Kconfig |  16 +
> > >   .../platform/amlogic/c3-mipi-adapter/Makefile      |   3 +
> > >   .../amlogic/c3-mipi-adapter/c3-mipi-adap.c         | 913 +++++++++++++++++++++
> > >   6 files changed, 941 insertions(+)
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index 9e75874a6e69..31168c05f304 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -1209,6 +1209,13 @@ F:     Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
> > >   F:   drivers/perf/amlogic/
> > >   F:   include/soc/amlogic/
> > >
> > > +AMLOGIC MIPI ADAPTER DRIVER
> > > +M:   Keke Li <keke.li@amlogic.com>
> > > +L:   linux-media@vger.kernel.org
> > > +S:   Maintained
> > > +F:   Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml
> > > +F:   drivers/media/platform/amlogic/c3-mipi-adapter/
> > > +
> > >   AMLOGIC MIPI CSI2 DRIVER
> > >   M:   Keke Li <keke.li@amlogic.com>
> > >   L:   linux-media@vger.kernel.org
> > > diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
> > > index b7c2de14848b..df09717b28d0 100644
> > > --- a/drivers/media/platform/amlogic/Kconfig
> > > +++ b/drivers/media/platform/amlogic/Kconfig
> > > @@ -2,5 +2,6 @@
> > >
> > >   comment "Amlogic media platform drivers"
> > >
> > > +source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
> > >   source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
> > >   source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
> > > diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
> > > index 4f571ce5d13e..b370154b090c 100644
> > > --- a/drivers/media/platform/amlogic/Makefile
> > > +++ b/drivers/media/platform/amlogic/Makefile
> > > @@ -1,4 +1,5 @@
> > >   # SPDX-License-Identifier: GPL-2.0-only
> > >
> > > +obj-y += c3-mipi-adapter/
> > >   obj-y += c3-mipi-csi2/
> > >   obj-y += meson-ge2d/
> > > diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig b/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig
> > > new file mode 100644
> > > index 000000000000..bf19059b3543
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig
> > > @@ -0,0 +1,16 @@
> > > +# SPDX-License-Identifier: GPL-2.0-only
> > > +
> > > +config VIDEO_C3_MIPI_ADAPTER
> > > +     tristate "Amlogic C3 MIPI adapter"
> > > +     depends on ARCH_MESON || COMPILE_TEST
> > > +     depends on VIDEO_DEV
> > > +     depends on OF
> > > +     select MEDIA_CONTROLLER
> > > +     select V4L2_FWNODE
> > > +     select VIDEO_V4L2_SUBDEV_API
> > > +     help
> > > +       Video4Linux2 driver for Amlogic C3 MIPI adapter.
> > > +       C3 MIPI adapter mainly responsible for organizing
> > > +       MIPI data and sending raw data to ISP pipeline.
> > > +
> > > +       To compile this driver as a module choose m here.
> > > diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile b/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile
> > > new file mode 100644
> > > index 000000000000..216fc310c5b4
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile
> > > @@ -0,0 +1,3 @@
> > > +# SPDX-License-Identifier: GPL-2.0-only
> > > +
> > > +obj-$(CONFIG_VIDEO_C3_MIPI_ADAPTER) += c3-mipi-adap.o
> > > diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c b/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c
> > > new file mode 100644
> > > index 000000000000..b64eb417c2e2
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c
> > > @@ -0,0 +1,913 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#include <linux/cleanup.h>
> > > +#include <linux/clk.h>
> > > +#include <linux/device.h>
> > > +#include <linux/module.h>
> > > +#include <linux/mutex.h>
> > > +#include <linux/platform_device.h>
> > > +#include <linux/pm_runtime.h>
> > > +
> > > +#include <media/v4l2-async.h>
> > > +#include <media/v4l2-common.h>
> > > +#include <media/v4l2-device.h>
> > > +#include <media/v4l2-fwnode.h>
> > > +#include <media/v4l2-mc.h>
> > > +#include <media/v4l2-subdev.h>
> > > +
> > > +/* C3 adapter submodule definition */
> > > +enum {
> > > +     SUBMD_TOP,
> > > +     SUBMD_FD,
> > > +     SUBMD_RD,
> > > +};
> > > +
> > > +#define ADAP_SUBMD_MASK             GENMASK(17, 16)
> > > +#define ADAP_SUBMD_SHIFT            16
> > > +#define ADAP_SUBMD(x)               (((x) & (ADAP_SUBMD_MASK)) >> (ADAP_SUBMD_SHIFT))
> > > +#define ADAP_REG_ADDR_MASK          GENMASK(15, 0)
> > > +#define ADAP_REG_ADDR(x)            ((x) & (ADAP_REG_ADDR_MASK))
> > > +#define ADAP_REG_T(x)               ((SUBMD_TOP << ADAP_SUBMD_SHIFT) | (x))
> > > +#define ADAP_REG_F(x)               ((SUBMD_FD << ADAP_SUBMD_SHIFT) | (x))
> > > +#define ADAP_REG_R(x)               ((SUBMD_RD << ADAP_SUBMD_SHIFT) | (x))
> > > +
> > > +#define MIPI_ADAP_CLOCK_NUM_MAX     3
> > > +#define MIPI_ADAP_SUBDEV_NAME       "mipi-adapter"
> > I would add a "c3-" prefix
> >
>
> Will add "c3-" prefix
>
> > > +
> > > +/* C3 MIPI adapter TOP register */
> > > +#define MIPI_ADAPT_DE_CTRL0         ADAP_REG_T(0x40)
> > > +#define ADAP_DE_READ_BYPASS         BIT(3)
> > > +#define ADAP_DE_WRITE_BYPASS        BIT(7)
> > > +
> > > +/* C3 MIPI adapter FRONTEND register */
> > > +#define CSI2_CLK_RESET              ADAP_REG_F(0x00)
> > > +#define ADAP_FD_APPLY_RESET         BIT(0)
> > > +#define ADAP_FD_ENABLE              BIT(1)
> > > +
> > > +#define CSI2_GEN_CTRL0              ADAP_REG_F(0x04)
> > > +#define ADAP_FD_VIRTUAL_CHN0_EN     BIT(0)
> > > +#define ADAP_FD_VIRTUAL_CHN1_EN     BIT(1)
> > > +#define ADAP_FD_VIRTUAL_CHN2_EN     BIT(2)
> > > +#define ADAP_FD_VIRTUAL_CHN3_EN     BIT(3)
> > > +#define ADAP_FD_ENABLE_PACKETS      GENMASK(20, 16)
> > > +#define ADAP_FD_ENABLE_RAW          BIT(16)
> > > +
> > > +#define CSI2_X_START_END_ISP        ADAP_REG_F(0x0c)
> > > +#define ADAP_FD_X_END_MASK          GENMASK(31, 16)
> > > +#define ADAP_FD_X_END_SHIFT         16
> > > +#define ADAP_FD_X_END(x)            ((x) - 1)
> > > +
> > > +#define CSI2_Y_START_END_ISP        ADAP_REG_F(0x10)
> > > +#define ADAP_FD_Y_END_MASK          GENMASK(31, 16)
> > > +#define ADAP_FD_Y_END_SHIFT         16
> > > +#define ADAP_FD_Y_END(x)            ((x) - 1)
> > > +
> > > +#define CSI2_VC_MODE                ADAP_REG_F(0x1c)
> > > +#define ADAP_FD_VS_SEL_VC_MASK      GENMASK(19, 16)
> > > +#define ADAP_FD_VS_DIRECT_PATH      BIT(16)
> > > +#define ADAP_FD_HS_SEL_VC_MASK      GENMASK(23, 20)
> > > +#define ADAP_FD_HS_DIRECT_PATH      BIT(20)
> > > +
> > > +/* C3 MIPI adapter READER register */
> > > +#define MIPI_ADAPT_DDR_RD0_CNTL0    ADAP_REG_R(0x00)
> > > +#define ADAP_RD0_MODULE_ENABLE      BIT(0)
> > > +#define ADAP_RD0_LINE_STRIDE_MASK   GENMASK(13, 4)
> > > +#define ADAP_RD0_LINE_STRIDE_SHIFT  4
> > > +#define ADAP_RD0_SAMPLE_SEL_MASK    GENMASK(27, 26)
> > > +#define ADAP_RD0_DATA_IN_VSYNC      BIT(26)
> > > +#define ADAP_RD0_BURST_TYPE_MASK    GENMASK(29, 28)
> > > +#define ADAP_RD0_BURST_TYPE_SHIFT   28
> > > +#define ADAP_RD0_BURST_TYPE_INRC8   3
> > > +#define ADAP_RD0_FRAME_RD_START     BIT(31)
> > > +
> > > +#define MIPI_ADAPT_DDR_RD0_CNTL1    ADAP_REG_R(0x04)
> > > +#define ADAP_RD0_LINE_SIZE_MASK     GENMASK(9, 0)
> > > +#define ADAP_RD0_LINE_NUM_MASK      GENMASK(28, 16)
> > > +#define ADAP_RD0_LINE_NUM_SHIFT     16
> > > +
> > > +#define MIPI_ADAPT_PIXEL0_CNTL0     ADAP_REG_R(0x80)
> > > +#define ADAP_PIXEL0_WORK_MODE_MASK  GENMASK(17, 16)
> > > +#define ADAP_PIXEL0_DIRECT_PATH     BIT(16)
> > > +#define ADAP_PIXEL0_DATA_TYPE_MASK  GENMASK(25, 20)
> > > +#define ADAP_PIXEL0_DATA_TYPE_SHIFT 20
> > > +#define ADAP_PIXEL0_DATA_TYPE_10BITS 0x2b
> > > +#define ADAP_PIXEL0_DATA_TYPE_12BITS 0x2c
> > > +#define ADAP_PIXEL0_START_ENABLE    BIT(31)
> > > +
> > > +#define MIPI_ADAPT_PIXEL0_CNTL1     ADAP_REG_R(0x84)
> > > +#define ADAP_PIXEL0_X_END_MASK      GENMASK(15, 0)
> > > +#define ADAP_PIXEL0_X_END(x)        ((x) - 1)
> > > +
> > > +#define MIPI_ADAPT_PIXEL0_CNTL2     ADAP_REG_R(0x88)
> > > +#define ADAP_PIXEL0_FIFO_SIZE_MASK  GENMASK(9, 0)
> > > +#define ADAP_PIXEL0_PIXEL_NUM_MASK  GENMASK(27, 15)
> > > +#define ADAP_PIXEL0_PIXEL_NUM_SHIFT 15
> > > +
> > > +#define MIPI_ADAPT_ALIG_CNTL0       ADAP_REG_R(0x100)
> > > +#define ADAP_ALIG_V_TOTAL_NUM_MASK  GENMASK(15, 0)
> > > +/* Need to add a default blank */
> > > +#define ADAP_ALIG_V_TOTAL_NUM(x)    ((x) + 64)
> > > +#define ADAP_ALIG_H_TOTAL_NUM_MASK  GENMASK(31, 16)
> > > +#define ADAP_ALIG_H_TOTAL_NUM_SHIFT 16
> > > +/* Need to add a default blank */
> > > +#define ADAP_ALIG_H_TOTAL_NUM(x)    ((x) + 64)
> > > +
> > > +#define MIPI_ADAPT_ALIG_CNTL1       ADAP_REG_R(0x104)
> > > +#define ADAP_ALIG_HPE_NUM_MASK      GENMASK(31, 16)
> > > +#define ADAP_ALIG_HPE_NUM_SHIFT     16
> > > +
> > > +#define MIPI_ADAPT_ALIG_CNTL2       ADAP_REG_R(0x108)
> > > +#define ADAP_ALIG_VPE_NUM_MASK      GENMASK(31, 16)
> > > +#define ADAP_ALIG_VPE_NUM_SHIFT     16
> > > +
> > > +#define MIPI_ADAPT_ALIG_CNTL3       ADAP_REG_R(0x10c)
> > > +#define ADAP_ALIG_FRM_ST_PIXEL_MASK GENMASK(15, 0)
> > > +
> > > +#define MIPI_ADAPT_ALIG_CNTL6       ADAP_REG_R(0x118)
> > > +#define ADAP_ALIG_LANE0_ENABLE      BIT(0)
> > > +#define ADAP_ALIG_DATA_MODE0_MASK   BIT(4)
> > > +#define ADAP_ALIG_DIRECT_MODE       BIT(4)
> > > +#define ADAP_ALIG_VDATA0_ENABLE     BIT(12)
> > > +#define ADAP_ALIG_VDATA1_ENABLE     BIT(13)
> > > +#define ADAP_ALIG_VDATA2_ENABLE     BIT(14)
> > > +#define ADAP_ALIG_VDATA3_ENABLE     BIT(15)
> > > +
> > > +#define MIPI_ADAPT_ALIG_CNTL8       ADAP_REG_R(0x120)
> > > +#define ADAP_ALIG_FRAME_CONTINUE    BIT(5)
> > > +#define ADAP_ALIG_EXC_MASK_DIS      BIT(12)
> > > +#define ADAP_ALIG_START_ENABLE      BIT(31)
> > > +
> > > +#define MIPI_ADAP_MAX_WIDTH         2888
> > > +#define MIPI_ADAP_MIN_WIDTH         160
> > > +#define MIPI_ADAP_MAX_HEIGHT        2240
> > > +#define MIPI_ADAP_MIN_HEIGHT        120
> > > +#define MIPI_ADAP_DEFAULT_WIDTH     1920
> > > +#define MIPI_ADAP_DEFAULT_HEIGHT    1080
> > > +#define MIPI_ADAP_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
> > > +
> > > +/* C3 MIPI adapter pad list */
> > > +enum {
> > > +     MIPI_ADAP_PAD_SINK,
> > > +     MIPI_ADAP_PAD_SRC,
> > > +     MIPI_ADAP_PAD_MAX
> > > +};
> > > +
> > > +/*
> > > + * struct adap_info - mipi adapter information
> > > + *
> > > + * @clocks: array of mipi adapter clock names
> > > + * @clock_rates: array of mipi adapter clock rate
> > > + * @clock_num: actual clock number
> > > + */
> > > +struct adap_info {
> > > +     char *clocks[MIPI_ADAP_CLOCK_NUM_MAX];
> > > +     u32 clock_rates[MIPI_ADAP_CLOCK_NUM_MAX];
> > > +     u32 clock_num;
> > > +};
> > > +
> > > +/**
> > Here as well you can drop one * from /**
>
>
> Will drop one  * from  /**
>
> > > + * struct adap_device - mipi adapter platform device
> > > + *
> > > + * @dev: pointer to the struct device
> > > + * @top: mipi adapter top register address
> > > + * @fd: mipi adapter frontend register address
> > > + * @rd: mipi adapter reader register address
> > > + * @clks: array of MIPI adapter clocks
> > > + * @sd: mipi adapter sub-device
> > > + * @pads: mipi adapter sub-device pads
> > > + * @notifier: notifier to register on the v4l2-async API
> > > + * @format: save sub-device format
> > > + * @src_sd: source sub-device
> > > + * @src_sd_pad: source sub-device pad
> > > + * @lock: protect mipi adapter device
> > > + * @info: version-specific MIPI adapter information
> > > + */
> > > +struct adap_device {
> > > +     struct device *dev;
> > > +     void __iomem *top;
> > > +     void __iomem *fd;
> > > +     void __iomem *rd;
> > > +     struct clk_bulk_data clks[MIPI_ADAP_CLOCK_NUM_MAX];
> > > +
> > > +     struct v4l2_subdev sd;
> > > +     struct media_pad pads[MIPI_ADAP_PAD_MAX];
> > > +     struct v4l2_async_notifier notifier;
> > > +     struct v4l2_subdev_format format;
> > > +     struct v4l2_subdev *src_sd;
> > > +
> > > +     u16 src_sd_pad;
> > > +     struct mutex lock; /* Protect adapter device */
> > Most comments on the csi2-rx patch apply to this one as well.
> > In this case, you don't need locking for functions that receive a
> > subdev_state
> >
> Will remove this lock.
> > > +     const struct adap_info *info;
> > > +};
> > > +
> > > +/* Format helpers */
> > > +
> > > +struct adap_pix_format {
> > > +     u32 code;
> > > +     u8 width;
> > I would call it bpp
> Will replace width with bpp
> > > +};
> > > +
> > > +static const struct adap_pix_format c3_mipi_adap_formats[] = {
> > > +     { MEDIA_BUS_FMT_SBGGR10_1X10, 10 },
> > > +     { MEDIA_BUS_FMT_SGBRG10_1X10, 10 },
> > > +     { MEDIA_BUS_FMT_SGRBG10_1X10, 10 },
> > > +     { MEDIA_BUS_FMT_SRGGB10_1X10, 10 },
> > > +     { MEDIA_BUS_FMT_SBGGR12_1X12, 12 },
> > > +     { MEDIA_BUS_FMT_SGBRG12_1X12, 12 },
> > > +     { MEDIA_BUS_FMT_SGRBG12_1X12, 12 },
> > > +     { MEDIA_BUS_FMT_SRGGB12_1X12, 12 },
> > > +};
> > > +
> > > +static const struct adap_pix_format
> > > +*c3_mipi_adap_find_format(u32 code)
> > > +{
> > > +     unsigned int i;
> > > +
> > > +     for (i = 0; i < ARRAY_SIZE(c3_mipi_adap_formats); i++)
> > > +             if (code == c3_mipi_adap_formats[i].code)
> > > +                     return &c3_mipi_adap_formats[i];
> > > +
> > > +     return NULL;
> > > +}
> > > +
> > > +/* Hardware configuration */
> > > +
> > > +static void c3_mipi_adap_update_bits(struct adap_device *adap, u32 reg,
> > > +                                  u32 mask, u32 val)
> > > +{
> > > +     void __iomem *addr;
> > > +     u32 orig, tmp;
> > > +
> > > +     switch (ADAP_SUBMD(reg)) {
> > > +     case SUBMD_TOP:
> > > +             addr = adap->top + ADAP_REG_ADDR(reg);
> > > +             break;
> > > +     case SUBMD_FD:
> > > +             addr = adap->fd + ADAP_REG_ADDR(reg);
> > > +             break;
> > > +     case SUBMD_RD:
> > > +             addr = adap->rd + ADAP_REG_ADDR(reg);
> > > +             break;
> > > +     default:
> > > +             dev_err(adap->dev, "Invalid sub-module: %lu\n", ADAP_SUBMD(reg));
> > > +             return;
> > > +     }
> > > +
> > > +     orig = readl(addr);
> > > +     tmp = orig & ~mask;
> > > +     tmp |= val & mask;
> > > +
> > > +     if (tmp != orig)
> > > +             writel(tmp, addr);
> > > +}
> > > +
> > > +/* Configure adapter top sub module */
> > > +static void c3_mipi_adap_cfg_top(struct adap_device *adap)
> > > +{
> > > +     /* Bypass decompress */
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0,
> > > +                              ADAP_DE_READ_BYPASS, ADAP_DE_READ_BYPASS);
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0,
> > > +                              ADAP_DE_WRITE_BYPASS, ADAP_DE_WRITE_BYPASS);
> > > +}
> > > +
> > > +/* Configure adapter frontend sub module */
> > > +static void c3_mipi_adap_cfg_frontend(struct adap_device *adap,
> > > +                                   struct v4l2_mbus_framefmt *fmt)
> > > +{
> > > +     /* The default value of BIT_0 is 1, so need release reset firstly. */
> > > +     c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET, ADAP_FD_APPLY_RESET, 0);
> > > +
> > > +     c3_mipi_adap_update_bits(adap, CSI2_X_START_END_ISP, ADAP_FD_X_END_MASK,
> > > +                              ADAP_FD_X_END(fmt->width) << ADAP_FD_X_END_SHIFT);
> > > +     c3_mipi_adap_update_bits(adap, CSI2_Y_START_END_ISP, ADAP_FD_Y_END_MASK,
> > > +                              ADAP_FD_Y_END(fmt->height) << ADAP_FD_Y_END_SHIFT);
> > > +
> > > +     /* Select VS and HS signal to direct path */
> > > +     c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, ADAP_FD_VS_SEL_VC_MASK,
> > > +                              ADAP_FD_VS_DIRECT_PATH);
> > > +     c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, ADAP_FD_HS_SEL_VC_MASK,
> > > +                              ADAP_FD_HS_DIRECT_PATH);
> > > +
> > > +     /* Enable to receive RAW image */
> > > +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_ENABLE_PACKETS,
> > > +                              ADAP_FD_ENABLE_RAW);
> > > +
> > > +     /* Enable virtual channel 0~3 */
> > > +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN0_EN,
> > > +                              ADAP_FD_VIRTUAL_CHN0_EN);
> > > +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN1_EN,
> > > +                              ADAP_FD_VIRTUAL_CHN1_EN);
> > > +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN2_EN,
> > > +                              ADAP_FD_VIRTUAL_CHN2_EN);
> > > +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN3_EN,
> > > +                              ADAP_FD_VIRTUAL_CHN3_EN);
> > > +}
> > > +
> > > +/* Configure adapter reader sub module */
> > > +static void c3_mipi_adap_cfg_reader(struct adap_device *adap,
> > > +                                 struct v4l2_mbus_framefmt *fmt)
> > > +{
> > > +     const struct adap_pix_format *pix_format;
> > > +     u32 line_size;
> > > +     u8 data_type;
> > > +
> > > +     /* Data size for a line, unit: 128 bits */
> > > +     pix_format = c3_mipi_adap_find_format(fmt->code);
> > > +     line_size = fmt->width * pix_format->width;
> > > +     line_size = DIV_ROUND_UP(line_size, 128);
> > > +
> > > +     if (pix_format->width == 10) {
> > > +             data_type = ADAP_PIXEL0_DATA_TYPE_10BITS;
> > > +     } else if (pix_format->width == 12) {
> > > +             data_type = ADAP_PIXEL0_DATA_TYPE_12BITS;
> > You should use MIPI_CSI2_DT_RAW10 and MIPI_CSI2_DT_RAW12 from
> > "include/media/mipi-csi2.h"
> >
> OK,  Will use MIPI_CSI2_DT_RAW10 and MIPI_CSI2_DT_RAW12.
> > > +     } else {
> > > +             dev_err(adap->dev, "Invalid raw format width: %u\n", pix_format->width);
> > > +             return;
> > > +     }
> > > +
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL1,
> > > +                              ADAP_RD0_LINE_SIZE_MASK, line_size);
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL1, ADAP_RD0_LINE_NUM_MASK,
> > > +                              fmt->height << ADAP_RD0_LINE_NUM_SHIFT);
> > > +
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_MODULE_ENABLE,
> > > +                              ADAP_RD0_MODULE_ENABLE);
> > I'm not sure I get this. I read this bit as "Enable reading from DDR"
> > while I thought the adapter receives data directly from the CSI-2
> > receiver
>
> This place is a bit confusing.
> I will check this issue  and add explanation.
>
>
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_LINE_STRIDE_MASK,
> > > +                              line_size << ADAP_RD0_LINE_STRIDE_SHIFT);
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_SAMPLE_SEL_MASK,
> > > +                              ADAP_RD0_DATA_IN_VSYNC);
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_BURST_TYPE_MASK,
> > > +                              ADAP_RD0_BURST_TYPE_INRC8 << ADAP_RD0_BURST_TYPE_SHIFT);
> > > +
> > > +     /* Set data type and work mode */
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0,
> > > +                              ADAP_PIXEL0_WORK_MODE_MASK, ADAP_PIXEL0_DIRECT_PATH);
> > Ah maybe this setting "RAW from direct path" enables direct path from
> > CSI-2 receiver
> >
>
> PIXEL0 is a sub module in adapter and should be configured.
>
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0, ADAP_PIXEL0_DATA_TYPE_MASK,
> > > +                              data_type << ADAP_PIXEL0_DATA_TYPE_SHIFT);
> > > +
> > > +     /* Set pixel end number */
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL1, ADAP_PIXEL0_X_END_MASK,
> > > +                              ADAP_PIXEL0_X_END(fmt->width));
> > > +
> > > +     /* Set line pixel number and reader fifo size */
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL2,
> > > +                              ADAP_PIXEL0_FIFO_SIZE_MASK, line_size);
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL2, ADAP_PIXEL0_PIXEL_NUM_MASK,
> > > +                              fmt->width << ADAP_PIXEL0_PIXEL_NUM_SHIFT);
> > > +
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, ADAP_ALIG_V_TOTAL_NUM_MASK,
> > > +                              ADAP_ALIG_V_TOTAL_NUM(fmt->width));
> > What are the ISP alignment requirements ?
> >
> > #define ADAP_ALIG_V_TOTAL_NUM(x)    ((x) + 64)
> >
> > Are you aligning or just adding 64 here ?
>
>
> ISP hardware requires at least 40 v-blanks,

Ok, now I see the requirements: 40 v-blanks, 64 h-blanks.

>
> so here add 64 to ensure ISP can function properly.
>

However I read the register ALIGN_CNTL0 to be described as v_total_num
and h_total_num which makes me think the actual image data are
expanded to the desired values ?  Or maybe it's just
to add enough processing margins to the ISP ?

> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, ADAP_ALIG_H_TOTAL_NUM_MASK,
> > > +                              ADAP_ALIG_H_TOTAL_NUM(fmt->height)
> > > +                              << ADAP_ALIG_H_TOTAL_NUM_SHIFT);
> > > +
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL1, ADAP_ALIG_HPE_NUM_MASK,
> > > +                              fmt->width << ADAP_ALIG_HPE_NUM_SHIFT);
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL2, ADAP_ALIG_VPE_NUM_MASK,
> > > +                              fmt->height << ADAP_ALIG_VPE_NUM_SHIFT);
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL3,
> > > +                              ADAP_ALIG_FRM_ST_PIXEL_MASK, fmt->width);

This is also something I'm not sure I get. The registers seems to be
there to skip reading a number of lines and pixels at the beginning of
the frame. It however only take effect if frame_sync_en =1 something
the driver doesn't set ?

> > > +
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
> > > +                              ADAP_ALIG_LANE0_ENABLE, ADAP_ALIG_LANE0_ENABLE);
> > > +
> > > +     /* Select direct mode */
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
> > > +                              ADAP_ALIG_DATA_MODE0_MASK, ADAP_ALIG_DIRECT_MODE);
> > > +
> > > +     /* Enable vdata 0~3 */
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
> > > +                              ADAP_ALIG_VDATA0_ENABLE, ADAP_ALIG_VDATA0_ENABLE);
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
> > > +                              ADAP_ALIG_VDATA1_ENABLE, ADAP_ALIG_VDATA1_ENABLE);
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
> > > +                              ADAP_ALIG_VDATA2_ENABLE, ADAP_ALIG_VDATA2_ENABLE);
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
> > > +                              ADAP_ALIG_VDATA3_ENABLE, ADAP_ALIG_VDATA2_ENABLE);
> > For my education could you tell what these bit do ?
> Will add explanation.

Thanks, I presume it's about controlling the internl bus between the
adapter and the ISP. A comment on how it works would be apreciated.

Thanks
  j

> > > +
> > > +     /* continue mode and disable hold counter */
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
> > > +                              ADAP_ALIG_FRAME_CONTINUE, ADAP_ALIG_FRAME_CONTINUE);
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
> > > +                              ADAP_ALIG_EXC_MASK_DIS, ADAP_ALIG_EXC_MASK_DIS);
> > > +}
> > > +
> > > +static void c3_mipi_adap_start_stream(struct adap_device *adap)
> > > +{
> > > +     /* Enable to start and will auto clear to 0 */
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
> > > +                              ADAP_ALIG_START_ENABLE, ADAP_ALIG_START_ENABLE);
> > > +
> > > +     /* Enable to start and will auto clear to 0 */
> > > +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0,
> > > +                              ADAP_PIXEL0_START_ENABLE, ADAP_PIXEL0_START_ENABLE);
> > > +
> > > +     /* Enable frontend */
> > > +     c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET,
> > > +                              ADAP_FD_ENABLE, ADAP_FD_ENABLE);
> > > +}
> > > +
> > > +static void c3_mipi_adap_cfg_format(struct adap_device *adap)
> > > +{
> > > +     struct v4l2_subdev_format *format = &adap->format;
> > > +
> > > +     c3_mipi_adap_cfg_top(adap);
> > > +     c3_mipi_adap_cfg_frontend(adap, &format->format);
> > > +     c3_mipi_adap_cfg_reader(adap, &format->format);
> > > +}
> > > +
> > > +/* V4L2 subdev operations */
> > > +
> > > +static int c3_mipi_adap_enable_streams(struct v4l2_subdev *sd,
> > > +                                    struct v4l2_subdev_state *state,
> > > +                                    u32 pad, u64 streams_mask)
> > > +{
> > > +     struct adap_device *adap = v4l2_get_subdevdata(sd);
> > > +     u64 sink_streams;
> > > +     int ret;
> > > +
> > > +     guard(mutex)(&adap->lock);
> > > +
> > > +     pm_runtime_resume_and_get(adap->dev);
> > > +
> > > +     c3_mipi_adap_cfg_format(adap);
> > > +     c3_mipi_adap_start_stream(adap);
> > > +
> > > +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > > +                                                    MIPI_ADAP_PAD_SINK,
> > > +                                                    &streams_mask);
> > > +     ret = v4l2_subdev_enable_streams(adap->src_sd,
> > > +                                      adap->src_sd_pad,
> > > +                                      sink_streams);
> > > +     if (ret) {
> > > +             pm_runtime_put(adap->dev);
> > > +             return ret;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_mipi_adap_disable_streams(struct v4l2_subdev *sd,
> > > +                                     struct v4l2_subdev_state *state,
> > > +                                     u32 pad, u64 streams_mask)
> > > +{
> > > +     struct adap_device *adap = v4l2_get_subdevdata(sd);
> > > +     u64 sink_streams;
> > > +     int ret;
> > > +
> > > +     guard(mutex)(&adap->lock);
> > > +
> > > +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > > +                                                    MIPI_ADAP_PAD_SINK,
> > > +                                                    &streams_mask);
> > > +     ret = v4l2_subdev_disable_streams(adap->src_sd,
> > > +                                       adap->src_sd_pad,
> > > +                                       sink_streams);
> > > +     if (ret)
> > > +             dev_err(adap->dev, "Failed to disable %s\n", adap->src_sd->name);
> > > +
> > > +     pm_runtime_put(adap->dev);
> > > +
> > > +     return ret;
> > > +}
> > > +
> > > +static int c3_mipi_adap_cfg_routing(struct v4l2_subdev *sd,
> > > +                                 struct v4l2_subdev_state *state,
> > > +                                 struct v4l2_subdev_krouting *routing)
> > > +{
> > > +     static const struct v4l2_mbus_framefmt format = {
> > > +             .width = MIPI_ADAP_DEFAULT_WIDTH,
> > > +             .height = MIPI_ADAP_DEFAULT_HEIGHT,
> > > +             .code = MIPI_ADAP_DEFAULT_FMT,
> > > +             .field = V4L2_FIELD_NONE,
> > > +             .colorspace = V4L2_COLORSPACE_RAW,
> > > +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
> > > +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
> > > +             .xfer_func = V4L2_XFER_FUNC_NONE,
> > > +     };
> > > +     int ret;
> > > +
> > > +     ret = v4l2_subdev_routing_validate(sd, routing,
> > > +                                        V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_mipi_adap_init_routing(struct v4l2_subdev *sd,
> > > +                                  struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_subdev_route routes;
> > > +     struct v4l2_subdev_krouting routing;
> > > +
> > > +     routes.sink_pad = MIPI_ADAP_PAD_SINK;
> > > +     routes.sink_stream = 0;
> > > +     routes.source_pad = MIPI_ADAP_PAD_SRC;
> > > +     routes.source_stream = 0;
> > > +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> > > +
> > > +     routing.num_routes = 1;
> > > +     routing.routes = &routes;
> > > +
> > > +     return c3_mipi_adap_cfg_routing(sd, state, &routing);
> > > +}
> > Same comments as per the CSI-2 Rx, do you need to allow set routing
> > when a single route in enabled and valid ?
> >
>
> Will drop the set_routing() interface.
>
> > > +
> > > +static int c3_mipi_adap_set_routing(struct v4l2_subdev *sd,
> > > +                                 struct v4l2_subdev_state *state,
> > > +                                 enum v4l2_subdev_format_whence which,
> > > +                                 struct v4l2_subdev_krouting *routing)
> > > +{
> > > +     bool is_streaming = v4l2_subdev_is_streaming(sd);
> > > +
> > > +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> > > +             return -EBUSY;
> > > +
> > > +     return c3_mipi_adap_cfg_routing(sd, state, routing);
> > > +}
> > > +
> > > +static int c3_mipi_adap_enum_mbus_code(struct v4l2_subdev *sd,
> > > +                                    struct v4l2_subdev_state *state,
> > > +                                    struct v4l2_subdev_mbus_code_enum *code)
> > > +{
> > > +     switch (code->pad) {
> > > +     case MIPI_ADAP_PAD_SINK:
> > > +             if (code->index >= ARRAY_SIZE(c3_mipi_adap_formats))
> > > +                     return -EINVAL;
> > > +
> > > +             code->code = c3_mipi_adap_formats[code->index].code;
> > > +             break;
> > > +     case MIPI_ADAP_PAD_SRC:
> > > +             struct v4l2_mbus_framefmt *fmt;
> > > +
> > > +             if (code->index > 0)
> > > +                     return -EINVAL;
> > > +
> > > +             fmt = v4l2_subdev_state_get_format(state, code->pad);
> > > +             code->code = fmt->code;
> > > +             break;
> > > +     default:
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_mipi_adap_set_fmt(struct v4l2_subdev *sd,
> > > +                             struct v4l2_subdev_state *state,
> > > +                             struct v4l2_subdev_format *format)
> > > +{
> > > +     struct adap_device *adap = v4l2_get_subdevdata(sd);
> > > +     struct v4l2_mbus_framefmt *fmt;
> > > +     const struct adap_pix_format *pix_format;
> > > +
> > > +     if (format->pad != MIPI_ADAP_PAD_SINK)
> > > +             return v4l2_subdev_get_fmt(sd, state, format);
> > > +
> > > +     pix_format = c3_mipi_adap_find_format(format->format.code);
> > > +     if (!pix_format)
> > > +             pix_format = &c3_mipi_adap_formats[0];
> > > +
> > > +     fmt = v4l2_subdev_state_get_format(state, format->pad);
> > > +     fmt->code = pix_format->code;
> > > +     fmt->width = clamp_t(u32, format->format.width,
> > > +                          MIPI_ADAP_MIN_WIDTH, MIPI_ADAP_MAX_WIDTH);
> > > +     fmt->height = clamp_t(u32, format->format.height,
> > > +                           MIPI_ADAP_MIN_HEIGHT, MIPI_ADAP_MAX_HEIGHT);
> > > +
> > > +     format->format = *fmt;
> > > +
> > > +     /* Synchronize the format to source pad */
> > > +     fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SRC);
> > > +     *fmt = format->format;
> > > +
> > > +     adap->format = *format;
> > There is not need to store the format in the driver-specific
> > structure. You use it c3_mipi_adap_cfg_format() called by
> > c3_mipi_adap_enable_streams() which receives a subdev_state where the format
> > is stored already.
> >
> > Thanks
> >    j
>
>
> Will remove the adap->format
>
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_mipi_adap_init_state(struct v4l2_subdev *sd,
> > > +                                struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_mbus_framefmt *sink_fmt;
> > > +     struct v4l2_mbus_framefmt *src_fmt;
> > > +
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SINK);
> > > +     src_fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SRC);
> > > +
> > > +     sink_fmt->width = MIPI_ADAP_DEFAULT_WIDTH;
> > > +     sink_fmt->height = MIPI_ADAP_DEFAULT_HEIGHT;
> > > +     sink_fmt->field = V4L2_FIELD_NONE;
> > > +     sink_fmt->code = MIPI_ADAP_DEFAULT_FMT;
> > > +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> > > +     sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
> > > +     sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
> > > +     sink_fmt->quantization =
> > > +             V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
> > > +                                           sink_fmt->ycbcr_enc);
> > > +     *src_fmt = *sink_fmt;
> > > +
> > > +     return c3_mipi_adap_init_routing(sd, state);
> > > +}
> > > +
> > > +static const struct v4l2_subdev_pad_ops c3_mipi_adap_pad_ops = {
> > > +     .enum_mbus_code = c3_mipi_adap_enum_mbus_code,
> > > +     .get_fmt = v4l2_subdev_get_fmt,
> > > +     .set_fmt = c3_mipi_adap_set_fmt,
> > > +     .set_routing = c3_mipi_adap_set_routing,
> > > +     .enable_streams = c3_mipi_adap_enable_streams,
> > > +     .disable_streams = c3_mipi_adap_disable_streams,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_ops c3_mipi_adap_subdev_ops = {
> > > +     .pad = &c3_mipi_adap_pad_ops,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_internal_ops c3_mipi_adap_internal_ops = {
> > > +     .init_state = c3_mipi_adap_init_state,
> > > +};
> > > +
> > > +/* Media entity operations */
> > > +static const struct media_entity_operations c3_mipi_adap_entity_ops = {
> > > +     .link_validate = v4l2_subdev_link_validate,
> > > +};
> > > +
> > > +/* PM runtime */
> > > +
> > > +static int __maybe_unused c3_mipi_adap_runtime_suspend(struct device *dev)
> > > +{
> > > +     struct adap_device *adap = dev_get_drvdata(dev);
> > > +
> > > +     clk_bulk_disable_unprepare(adap->info->clock_num, adap->clks);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int __maybe_unused c3_mipi_adap_runtime_resume(struct device *dev)
> > > +{
> > > +     struct adap_device *adap = dev_get_drvdata(dev);
> > > +
> > > +     return clk_bulk_prepare_enable(adap->info->clock_num, adap->clks);
> > > +}
> > > +
> > > +static const struct dev_pm_ops c3_mipi_adap_pm_ops = {
> > > +     SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> > > +                             pm_runtime_force_resume)
> > > +     SET_RUNTIME_PM_OPS(c3_mipi_adap_runtime_suspend,
> > > +                        c3_mipi_adap_runtime_resume, NULL)
> > > +};
> > > +
> > > +/* Probe/remove & platform driver */
> > > +
> > > +static int c3_mipi_adap_subdev_init(struct adap_device *adap)
> > > +{
> > > +     struct v4l2_subdev *sd = &adap->sd;
> > > +     int ret;
> > > +
> > > +     v4l2_subdev_init(sd, &c3_mipi_adap_subdev_ops);
> > > +     sd->owner = THIS_MODULE;
> > > +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > > +     sd->internal_ops = &c3_mipi_adap_internal_ops;
> > > +     snprintf(sd->name, sizeof(sd->name), "%s", MIPI_ADAP_SUBDEV_NAME);
> > > +
> > > +     sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> > > +     sd->entity.ops = &c3_mipi_adap_entity_ops;
> > > +
> > > +     sd->dev = adap->dev;
> > > +     v4l2_set_subdevdata(sd, adap);
> > > +
> > > +     adap->pads[MIPI_ADAP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> > > +     adap->pads[MIPI_ADAP_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
> > > +     ret = media_entity_pads_init(&sd->entity, MIPI_ADAP_PAD_MAX, adap->pads);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = v4l2_subdev_init_finalize(sd);
> > > +     if (ret) {
> > > +             media_entity_cleanup(&sd->entity);
> > > +             return ret;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static void c3_mipi_adap_subdev_deinit(struct adap_device *adap)
> > > +{
> > > +     v4l2_subdev_cleanup(&adap->sd);
> > > +     media_entity_cleanup(&adap->sd.entity);
> > > +}
> > > +
> > > +/* Subdev notifier register */
> > > +static int c3_mipi_adap_notify_bound(struct v4l2_async_notifier *notifier,
> > > +                                  struct v4l2_subdev *sd,
> > > +                                  struct v4l2_async_connection *asc)
> > > +{
> > > +     struct adap_device *adap = v4l2_get_subdevdata(notifier->sd);
> > > +     struct media_pad *sink = &adap->sd.entity.pads[MIPI_ADAP_PAD_SINK];
> > > +     int ret;
> > > +
> > > +     ret = media_entity_get_fwnode_pad(&sd->entity,
> > > +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
> > > +     if (ret < 0) {
> > > +             dev_err(adap->dev, "Failed to find pad for %s\n", sd->name);
> > > +             return ret;
> > > +     }
> > > +
> > > +     adap->src_sd = sd;
> > > +     adap->src_sd_pad = ret;
> > > +
> > > +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
> > > +                                            MEDIA_LNK_FL_IMMUTABLE);
> > > +}
> > > +
> > > +static const struct v4l2_async_notifier_operations c3_mipi_adap_notify_ops = {
> > > +     .bound = c3_mipi_adap_notify_bound,
> > > +};
> > > +
> > > +static int c3_mipi_adap_async_register(struct adap_device *adap)
> > > +{
> > > +     struct v4l2_async_connection *asc;
> > > +     struct fwnode_handle *ep;
> > > +     int ret;
> > > +
> > > +     v4l2_async_subdev_nf_init(&adap->notifier, &adap->sd);
> > > +
> > > +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(adap->dev), 0, 0,
> > > +                                          FWNODE_GRAPH_ENDPOINT_NEXT);
> > > +     if (!ep)
> > > +             return -ENOTCONN;
> > > +
> > > +     asc = v4l2_async_nf_add_fwnode_remote(&adap->notifier, ep,
> > > +                                           struct v4l2_async_connection);
> > > +     if (IS_ERR(asc)) {
> > > +             ret = PTR_ERR(asc);
> > > +             goto err_put_handle;
> > > +     }
> > > +
> > > +     adap->notifier.ops = &c3_mipi_adap_notify_ops;
> > > +     ret = v4l2_async_nf_register(&adap->notifier);
> > > +     if (ret)
> > > +             goto err_cleanup_nf;
> > > +
> > > +     ret = v4l2_async_register_subdev(&adap->sd);
> > > +     if (ret)
> > > +             goto err_unregister_nf;
> > > +
> > > +     fwnode_handle_put(ep);
> > > +
> > > +     return 0;
> > > +
> > > +err_unregister_nf:
> > > +     v4l2_async_nf_unregister(&adap->notifier);
> > > +err_cleanup_nf:
> > > +     v4l2_async_nf_cleanup(&adap->notifier);
> > > +err_put_handle:
> > > +     fwnode_handle_put(ep);
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_mipi_adap_async_unregister(struct adap_device *adap)
> > > +{
> > > +     v4l2_async_unregister_subdev(&adap->sd);
> > > +     v4l2_async_nf_unregister(&adap->notifier);
> > > +     v4l2_async_nf_cleanup(&adap->notifier);
> > > +}
> > > +
> > > +static int c3_mipi_adap_ioremap_resource(struct adap_device *adap)
> > > +{
> > > +     struct device *dev = adap->dev;
> > > +     struct platform_device *pdev = to_platform_device(dev);
> > > +
> > > +     adap->top = devm_platform_ioremap_resource_byname(pdev, "top");
> > > +     if (IS_ERR(adap->top))
> > > +             return PTR_ERR(adap->top);
> > > +
> > > +     adap->fd = devm_platform_ioremap_resource_byname(pdev, "fd");
> > > +     if (IS_ERR(adap->fd))
> > > +             return PTR_ERR(adap->fd);
> > > +
> > > +     adap->rd = devm_platform_ioremap_resource_byname(pdev, "rd");
> > > +     if (IS_ERR(adap->rd))
> > > +             return PTR_ERR(adap->rd);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_mipi_adap_configure_clocks(struct adap_device *adap)
> > > +{
> > > +     const struct adap_info *info = adap->info;
> > > +     int ret;
> > > +     u32 i;
> > > +
> > > +     for (i = 0; i < info->clock_num; i++)
> > > +             adap->clks[i].id = info->clocks[i];
> > > +
> > > +     ret = devm_clk_bulk_get(adap->dev, info->clock_num, adap->clks);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     for (i = 0; i < info->clock_num; i++) {
> > > +             if (!info->clock_rates[i])
> > > +                     continue;
> > > +             ret = clk_set_rate(adap->clks[i].clk, info->clock_rates[i]);
> > > +             if (ret) {
> > > +                     dev_err(adap->dev, "Failed to set %s rate %u\n", info->clocks[i],
> > > +                             info->clock_rates[i]);
> > > +                     return ret;
> > > +             }
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_mipi_adap_probe(struct platform_device *pdev)
> > > +{
> > > +     struct device *dev = &pdev->dev;
> > > +     struct adap_device *adap;
> > > +     int ret;
> > > +
> > > +     adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL);
> > > +     if (!adap)
> > > +             return -ENOMEM;
> > > +
> > > +     adap->info = of_device_get_match_data(dev);
> > > +     adap->dev = dev;
> > > +
> > > +     ret = c3_mipi_adap_ioremap_resource(adap);
> > > +     if (ret)
> > > +             return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
> > > +
> > > +     ret = c3_mipi_adap_configure_clocks(adap);
> > > +     if (ret)
> > > +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
> > > +
> > > +     platform_set_drvdata(pdev, adap);
> > > +
> > > +     mutex_init(&adap->lock);
> > > +     pm_runtime_enable(dev);
> > > +
> > > +     ret = c3_mipi_adap_subdev_init(adap);
> > > +     if (ret < 0)
> > > +             goto err_disable_runtime_pm;
> > > +
> > > +     ret = c3_mipi_adap_async_register(adap);
> > > +     if (ret < 0)
> > > +             goto err_deinit_subdev;
> > > +
> > > +     return 0;
> > > +
> > > +err_deinit_subdev:
> > > +     c3_mipi_adap_subdev_deinit(adap);
> > > +err_disable_runtime_pm:
> > > +     pm_runtime_disable(dev);
> > > +     mutex_destroy(&adap->lock);
> > > +     return ret;
> > > +};
> > > +
> > > +static void c3_mipi_adap_remove(struct platform_device *pdev)
> > > +{
> > > +     struct adap_device *adap = platform_get_drvdata(pdev);
> > > +
> > > +     c3_mipi_adap_async_unregister(adap);
> > > +     c3_mipi_adap_subdev_deinit(adap);
> > > +
> > > +     pm_runtime_disable(&pdev->dev);
> > > +     mutex_destroy(&adap->lock);
> > > +};
> > > +
> > > +static const struct adap_info c3_mipi_adap_info = {
> > > +     .clocks = {"vapb", "isp0"},
> > > +     .clock_rates = {0, 400000000},
> > > +     .clock_num = 2
> > > +};
> > > +
> > > +static const struct of_device_id c3_mipi_adap_of_match[] = {
> > > +     { .compatible = "amlogic,c3-mipi-adapter",
> > > +       .data = &c3_mipi_adap_info },
> > > +     { },
> > > +};
> > > +MODULE_DEVICE_TABLE(of, c3_mipi_adap_of_match);
> > > +
> > > +static struct platform_driver c3_mipi_adap_driver = {
> > > +     .probe = c3_mipi_adap_probe,
> > > +     .remove = c3_mipi_adap_remove,
> > > +     .driver = {
> > > +             .name = "c3-mipi-adapter",
> > > +             .of_match_table = c3_mipi_adap_of_match,
> > > +             .pm = &c3_mipi_adap_pm_ops,
> > > +     },
> > > +};
> > > +
> > > +module_platform_driver(c3_mipi_adap_driver);
> > > +
> > > +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
> > > +MODULE_DESCRIPTION("Amlogic C3 MIPI adapter");
> > > +MODULE_LICENSE("GPL");
> > >
> > > --
> > > 2.46.1
> > >
> > >
> > >
>

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

* Re: [PATCH v3 9/9] Documentation: media: add documentation file c3-isp.rst
  2024-09-18  6:07 ` [PATCH v3 9/9] Documentation: media: add documentation file c3-isp.rst Keke Li via B4 Relay
@ 2024-11-07  9:07   ` Jacopo Mondi
  0 siblings, 0 replies; 37+ messages in thread
From: Jacopo Mondi @ 2024-11-07  9:07 UTC (permalink / raw)
  To: keke.li
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi Keke

On Wed, Sep 18, 2024 at 02:07:20PM +0800, Keke Li via B4 Relay wrote:
> From: Keke Li <keke.li@amlogic.com>
>
> Add the file 'c3-isp.rst' that documents the c3-isp driver.
>
> Signed-off-by: Keke Li <keke.li@amlogic.com>
> ---
>  Documentation/admin-guide/media/c3-isp.dot      | 26 +++++++
>  Documentation/admin-guide/media/c3-isp.rst      | 96 +++++++++++++++++++++++++
>  Documentation/admin-guide/media/v4l-drivers.rst |  1 +
>  MAINTAINERS                                     | 10 +++
>  4 files changed, 133 insertions(+)
>
> diff --git a/Documentation/admin-guide/media/c3-isp.dot b/Documentation/admin-guide/media/c3-isp.dot
> new file mode 100644
> index 000000000000..0cc1b8b96404
> --- /dev/null
> +++ b/Documentation/admin-guide/media/c3-isp.dot
> @@ -0,0 +1,26 @@
> +digraph board {
> +	rankdir=TB
> +	n00000001 [label="{{<port0> 0 | <port1> 1} | isp-core\n/dev/v4l-subdev0 | {<port2> 2 | <port3> 3}}", shape=Mrecord, style=filled, fillcolor=green]
> +	n00000001:port3 -> n00000006:port0 [style=bold]
> +	n00000001:port3 -> n00000009:port0 [style=bold]
> +	n00000001:port3 -> n0000000c:port0 [style=bold]
> +	n00000001:port2 -> n00000020 [style=bold]
> +	n00000006 [label="{{<port0> 0} | isp-resizer0\n/dev/v4l-subdev1 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
> +	n00000006:port1 -> n00000014 [style=bold]
> +	n00000009 [label="{{<port0> 0} | isp-resizer1\n/dev/v4l-subdev2 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
> +	n00000009:port1 -> n00000018 [style=bold]
> +	n0000000c [label="{{<port0> 0} | isp-resizer2\n/dev/v4l-subdev3 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
> +	n0000000c:port1 -> n0000001c [style=bold]
> +	n0000000f [label="{{<port0> 0} | mipi-adapter\n/dev/v4l-subdev4 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
> +	n0000000f:port1 -> n00000001:port0 [style=bold]
> +	n00000014 [label="isp-video0\n/dev/video0", shape=box, style=filled, fillcolor=yellow]
> +	n00000018 [label="isp-video1\n/dev/video1", shape=box, style=filled, fillcolor=yellow]
> +	n0000001c [label="isp-video2\n/dev/video2", shape=box, style=filled, fillcolor=yellow]
> +	n00000020 [label="isp-stats\n/dev/video3", shape=box, style=filled, fillcolor=yellow]
> +	n00000024 [label="isp-params\n/dev/video4", shape=box, style=filled, fillcolor=yellow]
> +	n00000024 -> n00000001:port1 [style=bold]
> +	n00000038 [label="{{<port0> 0} | mipi-csi2\n/dev/v4l-subdev5 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
> +	n00000038:port1 -> n0000000f:port0 [style=bold]
> +	n0000003d [label="{{} | imx290 2-001a\n/dev/v4l-subdev6 | {<port0> 0}}", shape=Mrecord, style=filled, fillcolor=green]
> +	n0000003d:port0 -> n00000038:port0 [style=bold]
> +}
> diff --git a/Documentation/admin-guide/media/c3-isp.rst b/Documentation/admin-guide/media/c3-isp.rst
> new file mode 100644
> index 000000000000..fab10c962465
> --- /dev/null
> +++ b/Documentation/admin-guide/media/c3-isp.rst
> @@ -0,0 +1,96 @@
> +.. SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +
> +.. include:: <isonum.txt>
> +
> +=================================================
> +Amlogic C3 Image Signal Processing (C3ISP) driver
> +=================================================
> +
> +Introduction
> +============
> +
> +This file documents the Amlogic C3ISP driver located under
> +drivers/media/platform/amlogic/c3-isp.
> +
> +The current version of the driver supports the C3ISP found on
> +Amlogic C308L processor.
> +
> +The driver implements V4L2, Media controller and V4L2 subdev interfaces.
> +Camera sensor using V4L2 subdev interface in the kernel is supported.

I would drop this last statement

> +
> +The driver has been tested on AW419-C308L-Socket platform.
> +
> +Anlogic Camera hardware

Amlogic

> +=======================
> +
> +The Camera hardware found on C308L processors and supported by
> +the driver consists of:
> +
> +- 1 MIPI-CSI2 module. It handle the Physical layer of the CSI2 receivers and
> +  receive MIPI data.

Do not break lines if you do not break it to new paragraphs

     What I mean is not to.
     Do this but instead:

     Either do this on multiple lines if you go beyond 80 columns in
     writing your text without breaking earlier.

     Or break it.

     To separate paragraphs.

> +  A separate camera sensor can be connected to MIPI-CSi2 module.

I would drop "separate" and mention that both RAW and YUV/RGB sensors
are supported

> +- 1 MIPI-ADAPTER module. Organize MIPI data to meet ISP input requirements and
> +  send MIPI data to ISP
> +- 1 ISP (Image Signal Processing) module. Contain a pipeline of image processing
> +  hardware blocks.
> +  The ISP pipeline contains three scalers at the end.
> +  The ISP also contains the DMA interface which writes the output data to memory.
> +
> +Supported functionality
> +=======================
> +
> +The current version of the driver supports:
> +
> +- Input from camera sensor via MIPI-CSI2;
> +
> +- Pixel output interface of ISP
> +
> +  - Scaling support. Configuration of the scaler module
> +    for downscalling with ratio up to 8x.

No need to break line before 80-cols

> +
> +Driver Architecture and Design
> +==============================
> +
> +The driver implements the V4L2 subdev interface. With the goal to model the
> +hardware links between the modules and to expose a clean, logical and usable
> +interface, the driver is split into V4L2 sub-devices as follows:
> +
> +- 1 mipi-csi2 sub-device - mipi-csi2 is represented by a single sub-device.
> +- 1 mipi-adapter sub-device - mipi-adapter is represented by a single sub-devices.
> +- 1 isp-core sub-device - isp-core is represented by a single sub-devices.
> +- 3 isp-resizer sub-devices - isp-resizer is represented by a number of sub-devices
> +  equal to the number of capture device.
> +
> +isp-core sub-device is linked to 2 separate video device nodes and
> +3 isp-resizer sub-devices nodes.
> +
> +- 1 capture statistics video device node.
> +- 1 output parameters video device node.
> +- 3 isp-resizer sub-device nodes.
> +
> +isp-resizer sub-device is linked to capture video device node.
> +
> +- isp-resizer0 is linked to isp-cap0
> +- isp-resizer1 is linked to isp-cap1
> +- isp-resizer2 is linked to isp-cap2
> +
> +The media controller pipeline graph is as follows (with connected a
> +IMX290 camera sensor):
> +
> +.. _isp_topology_graph:
> +
> +.. kernel-figure:: c3-isp.dot
> +    :alt:   c3-isp.dot
> +    :align: center
> +
> +    Media pipeline topology
> +
> +Implementation
> +==============
> +
> +Runtime configuration of the hardware via 'isp-params' video device node.
> +Acquiring statistics of ISP hardware via 'isp-stats' video device node.
> +Acquiring output image of ISP hardware via 'isp-video[0, 2]' video device node.

The text above names them 'isp-cap[0, 2]'

> +
> +The output size of the scaler module in the ISP is configured with
> +the pixel format of 'isp-video[0, 2]' video device node.

I haven't looked yet at how this is implemented, but I presume the
correct sizes should be configured on the isp-resizerX sub-device as
well. I would drop this last pharse as it might be misleading ?

> diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst
> index b6af448b9fe9..be0a8a860f39 100644
> --- a/Documentation/admin-guide/media/v4l-drivers.rst
> +++ b/Documentation/admin-guide/media/v4l-drivers.rst
> @@ -10,6 +10,7 @@ Video4Linux (V4L) driver-specific documentation
>  	:maxdepth: 2
>
>  	bttv
> +	c3-isp
>  	cafe_ccic
>  	cx88
>  	fimc
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 31168c05f304..954dd9bdf77e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1209,6 +1209,16 @@ F:	Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
>  F:	drivers/perf/amlogic/
>  F:	include/soc/amlogic/
>
> +AMLOGIC ISP DRIVER
> +M:	Keke Li <keke.li@amlogic.com>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained

I would add the entry to MAINTAINERS when introducing the ISP driver
and only add

> +F:	Documentation/admin-guide/media/c3-isp.dot
> +F:	Documentation/admin-guide/media/c3-isp.rst

These two files in this patch

> +F:	Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml
> +F:	Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst
> +F:	drivers/media/platform/amlogic/c3-isp/

And also the uAPI header is missing from the files list it seems

Thanks
  j

> +
>  AMLOGIC MIPI ADAPTER DRIVER
>  M:	Keke Li <keke.li@amlogic.com>
>  L:	linux-media@vger.kernel.org
>
> --
> 2.46.1
>
>
>

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

* Re: [PATCH v3 8/9] Documentation: media: add documentation file metafmt-c3-isp.rst
  2024-09-18  6:07 ` [PATCH v3 8/9] Documentation: media: add documentation file metafmt-c3-isp.rst Keke Li via B4 Relay
@ 2024-11-07 10:38   ` Jacopo Mondi
  0 siblings, 0 replies; 37+ messages in thread
From: Jacopo Mondi @ 2024-11-07 10:38 UTC (permalink / raw)
  To: keke.li
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi Keke

On Wed, Sep 18, 2024 at 02:07:19PM +0800, Keke Li via B4 Relay wrote:
> From: Keke Li <keke.li@amlogic.com>
>
> Add the file 'metafmt-c3-isp.rst' that documents
> the meta format of c3-isp.

Do not break lines too early!

>
> Signed-off-by: Keke Li <keke.li@amlogic.com>
> ---
>  .../userspace-api/media/v4l/meta-formats.rst       |  1 +
>  .../userspace-api/media/v4l/metafmt-c3-isp.rst     | 39 ++++++++++++++++++++++
>  2 files changed, 40 insertions(+)
>
> diff --git a/Documentation/userspace-api/media/v4l/meta-formats.rst b/Documentation/userspace-api/media/v4l/meta-formats.rst
> index c6e56b5888bc..3a420747f6e7 100644
> --- a/Documentation/userspace-api/media/v4l/meta-formats.rst
> +++ b/Documentation/userspace-api/media/v4l/meta-formats.rst
> @@ -12,6 +12,7 @@ These formats are used for the :ref:`metadata` interface only.
>  .. toctree::
>      :maxdepth: 1
>
> +    metafmt-c3-isp
>      metafmt-d4xx
>      metafmt-generic
>      metafmt-intel-ipu3
> diff --git a/Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst b/Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst
> new file mode 100644
> index 000000000000..bbaf3542c1ca
> --- /dev/null
> +++ b/Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst
> @@ -0,0 +1,39 @@
> +.. SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +
> +.. _v4l2-meta-fmt-c3isp-stats:
> +.. _v4l2-meta-fmt-c3isp-params:
> +
> +***********************************************************************
> +V4L2_META_FMT_C3ISP_STATS ('CSTS'), V4L2_META_FMT_C3ISP_PARAMS ('CPRM')
> +***********************************************************************
> +
> +.. c3_isp_stats_info

This does not seem to be used

> +
> +3A statistics
> +=============
> +
> +The C3 ISP can collect different statistics over an input Bayer frame.
> +Those statistics are obtained from the "isp-stats" metadata capture video nodes,
> +using the :c:type:`v4l2_meta_format` interface.
> +They are formatted as described by the :c:type:`c3_isp_stats_info` structure.
> +
> +The statistics collected are  Auto-white balance,
> +Auto-exposure and Auto-focus information.

Do not break lines too early

> +
> +.. c3_isp_params_buffer

Unsued as well ?

> +
> +Pipeline parameters
> +===================
> +
> +The pipeline parameters are passed to the "isp-params" metadata
> +output video nodes, using the :c:type:`v4l2_meta_format` interface. They are
> +formatted as described by the :c:type:`c3_isp_params_buffer` structure.
> +
> +The structure c3_isp_params_buffer includes the necessary parameters
> +required by the ISP.
> +These parameters are written to the ISP hardware.
> +
> +Amlogic C3 ISP uAPI data types
> +===============================

Additional = at the end

> +
> +.. kernel-doc:: drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
>
> --
> 2.46.1
>
>
>

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

* Re: [PATCH v3 7/9] media: platform: Add c3 ISP driver
  2024-09-18  6:07 ` [PATCH v3 7/9] media: platform: Add c3 ISP driver Keke Li via B4 Relay
@ 2024-11-07 16:03   ` Jacopo Mondi
  2024-11-07 16:15     ` Jacopo Mondi
  2024-11-08 12:34     ` Keke Li
  2024-11-08 10:47   ` Dan Scally
  1 sibling, 2 replies; 37+ messages in thread
From: Jacopo Mondi @ 2024-11-07 16:03 UTC (permalink / raw)
  To: keke.li
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi Keke

   a first pass of review without going into details about the
ISP parameters and stats but mostly on architecture.

On Wed, Sep 18, 2024 at 02:07:18PM +0800, Keke Li via B4 Relay wrote:
> From: Keke Li <keke.li@amlogic.com>
>
> The C3 ISP supports multi-camera and muti-exposure
> high dynamic range (HDR). It brings together some
> advanced imaging technologies to provide good image quality.
> This driver mainly responsible for driving ISP pipeline
> to process raw image.
>
> Signed-off-by: Keke Li <keke.li@amlogic.com>
> ---
>  drivers/media/platform/amlogic/Kconfig             |   1 +
>  drivers/media/platform/amlogic/Makefile            |   1 +
>  drivers/media/platform/amlogic/c3-isp/Kconfig      |  17 +
>  drivers/media/platform/amlogic/c3-isp/Makefile     |  10 +
>  .../media/platform/amlogic/c3-isp/c3-isp-capture.c | 759 ++++++++++++++++++
>  .../media/platform/amlogic/c3-isp/c3-isp-common.h  | 327 ++++++++
>  .../media/platform/amlogic/c3-isp/c3-isp-core.c    | 675 ++++++++++++++++
>  drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c | 486 ++++++++++++
>  .../media/platform/amlogic/c3-isp/c3-isp-params.c  | 857 +++++++++++++++++++++
>  .../media/platform/amlogic/c3-isp/c3-isp-regs.h    | 683 ++++++++++++++++
>  .../media/platform/amlogic/c3-isp/c3-isp-resizer.c | 768 ++++++++++++++++++
>  .../media/platform/amlogic/c3-isp/c3-isp-stats.c   | 488 ++++++++++++
>  .../amlogic/c3-isp/include/uapi/c3-isp-config.h    | 537 +++++++++++++

This really should be in include/uapi/linux/media/amlogic
so that it is made available to userspace.

>  13 files changed, 5609 insertions(+)
>
> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
> index df09717b28d0..ebda6b7edc2d 100644
> --- a/drivers/media/platform/amlogic/Kconfig
> +++ b/drivers/media/platform/amlogic/Kconfig
> @@ -2,6 +2,7 @@
>
>  comment "Amlogic media platform drivers"
>
> +source "drivers/media/platform/amlogic/c3-isp/Kconfig"
>  source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
>  source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"

I start wondering if this shouldn't rather be organized in an
drivers/media/platform/amlogic/c3/ folder with an
drivers/media/platform/amlogic/c3/isp/ subfolder

Do you know if any of the csi-2-rx, adapter or ISP can
be used in other SoCs ?

>  source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
> index b370154b090c..d0d9363d4d8d 100644
> --- a/drivers/media/platform/amlogic/Makefile
> +++ b/drivers/media/platform/amlogic/Makefile
> @@ -1,5 +1,6 @@
>  # SPDX-License-Identifier: GPL-2.0-only
>
> +obj-y += c3-isp/
>  obj-y += c3-mipi-adapter/
>  obj-y += c3-mipi-csi2/
>  obj-y += meson-ge2d/
> diff --git a/drivers/media/platform/amlogic/c3-isp/Kconfig b/drivers/media/platform/amlogic/c3-isp/Kconfig
> new file mode 100644
> index 000000000000..e317c1e81750
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/Kconfig
> @@ -0,0 +1,17 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config VIDEO_C3_ISP
> +	tristate "Amlogic C3 Image Signal Processor (ISP) driver"
> +	depends on ARCH_MESON || COMPILE_TEST
> +	depends on VIDEO_DEV
> +	depends on OF
> +	select MEDIA_CONTROLLER
> +	select V4L2_FWNODE
> +	select VIDEO_V4L2_SUBDEV_API
> +	select VIDEOBUF2_DMA_CONTIG
> +	help
> +	  Video4Linux2 driver for Amlogic C3 ISP pipeline.
> +	  C3 ISP pipeline mainly for processing raw image

          The C3 ISP is used for processing raw images
> +	  and output result to memory.

Or something similar, but "C3 ISP pipeline mainly for processing"
doesn't sound right to be

> +
> +	  To compile this driver as a module choose m here.
> diff --git a/drivers/media/platform/amlogic/c3-isp/Makefile b/drivers/media/platform/amlogic/c3-isp/Makefile
> new file mode 100644
> index 000000000000..b1b064170b57
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/Makefile
> @@ -0,0 +1,10 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +c3-isp-objs := c3-isp-dev.o \
> +	       c3-isp-params.o \
> +	       c3-isp-stats.o \
> +	       c3-isp-capture.o \
> +	       c3-isp-core.o \
> +	       c3-isp-resizer.o
> +
> +obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
> new file mode 100644
> index 000000000000..ee9a7a17a203
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
> @@ -0,0 +1,759 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +
> +static const struct c3_isp_capture_format cap_formats[] = {
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_Y8_1X8,
> +		.fourcc = V4L2_PIX_FMT_GREY,
> +		.depth = 8,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,

Does the 5X8 version represents the format on the internal bus between
the resizers and the capture device ?

How does format propagation work from the ISP to the resizers and the
capture devices ? I mean, is there an internal bus where the number of
samples (5X8 vs 2X8) changes depending on the output format ?

> +		.fourcc = V4L2_PIX_FMT_NV12,
> +		.depth = 12,
> +	}, {
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
> +		.fourcc = V4L2_PIX_FMT_NV21,
> +		.depth = 12,
> +	}, {
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> +		.fourcc = V4L2_PIX_FMT_NV16,
> +		.depth = 16,
> +	}, {
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> +		.fourcc = V4L2_PIX_FMT_NV61,
> +		.depth = 16,
> +	},
> +};
> +
> +/* Hardware configuration */
> +
> +/* Set the address of wrmifx3(write memory interface) */
> +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
> +{
> +	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> +	struct c3_isp_vb2_buffer *buff = cap->buff;
> +	u32 offset;
> +
> +	c3_isp_write(cap->isp,
> +		     C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
> +		     WRMIFX3_CH0_BADDR(buff->paddr));
> +
> +	if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
> +	    pix->pixelformat == V4L2_PIX_FMT_NV21 ||
> +	    pix->pixelformat == V4L2_PIX_FMT_NV16 ||
> +	    pix->pixelformat == V4L2_PIX_FMT_NV61) {
> +		offset = pix->width * pix->height;
> +		c3_isp_write(cap->isp,
> +			     C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
> +			     WRMIFX3_CH1_BADDR(buff->paddr + offset));
> +	}
> +}
> +
> +static void c3_isp_cap_output_size(struct c3_isp_capture *cap)
> +{
> +	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> +
> +	c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
> +			   DISP_OUT_VSIZE_MASK, pix->height);
> +	c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
> +			   DISP_OUT_HSIZE_MASK, pix->width << DISP_OUT_HSIZE_SHIFT);
> +}
> +
> +static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
> +{
> +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> +	u32 stride;
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
> +
> +	/* Grey has 1 plane*/
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_PLANE_MASK,
> +			   WRMIFX3_FMT_MTX_PLANE_X1 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
> +
> +	/* Set Y only as output format */
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MODE_OUT_MASK,
> +			   WRMIFX3_FMT_MODE_OUT_Y_ONLY << WRMIFX3_FMT_MODE_OUT_SHIFT);
> +
> +	/* The unit of stride is 128 bits */
> +	stride = DIV_ROUND_UP(fmt->width * 8, 128);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> +			   WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> +			   WRMIFX3_CH0_PIX_BITS_MODE_MASK,
> +			   WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
> +}
> +
> +static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, u32 swap_uv)
> +{
> +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> +	u32 stride;
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_UV_SWAP_MASK,
> +			   swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
> +
> +	/* NV12 or NV21 has 2 planes*/
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_PLANE_MASK,
> +			   WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
> +
> +	/* Set YUV420 as output format */
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MODE_OUT_MASK,
> +			   WRMIFX3_FMT_MODE_OUT_YUV420 << WRMIFX3_FMT_MODE_OUT_SHIFT);
> +
> +	/* The unit of stride is 128 bits */
> +	stride = DIV_ROUND_UP(fmt->width * 8, 128);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> +			   WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
> +			   WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> +			   WRMIFX3_CH0_PIX_BITS_MODE_MASK,
> +			   WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
> +			   WRMIFX3_CH1_PIX_BITS_MODE_MASK,
> +			   WRMIFX3_CH1_PIX_BITS_16BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
> +}
> +
> +static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, u32 swap_uv)
> +{
> +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> +	u32 stride;
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_16BIT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_UV_SWAP_MASK,
> +			   swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
> +
> +	/* NV16 or NV61 has 2 planes*/
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_PLANE_MASK,
> +			   WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
> +
> +	/* Set YUV422 as output format */
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MODE_OUT_MASK,
> +			   WRMIFX3_FMT_MODE_OUT_YUV422 << WRMIFX3_FMT_MODE_OUT_SHIFT);
> +
> +	/* The unit of stride is 128 bits */
> +	stride = DIV_ROUND_UP(fmt->width * 16, 128);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> +			   WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
> +			   WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> +			   WRMIFX3_CH0_PIX_BITS_MODE_MASK,
> +			   WRMIFX3_CH0_PIX_BITS_16BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
> +			   WRMIFX3_CH1_PIX_BITS_MODE_MASK,
> +			   WRMIFX3_CH1_PIX_BITS_32BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
> +}
> +
> +static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
> +{
> +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> +
> +	if (fmt->pixelformat == V4L2_PIX_FMT_GREY) {
> +		c3_isp_cap_wrmifx3_grey(cap);
> +	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV12) {
> +		c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
> +	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV21) {
> +		c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
> +	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV16) {
> +		c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
> +	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV61) {
> +		c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
> +	} else {
> +		dev_err(cap->isp->dev, "Invalid pixel format: 0x%x\n", fmt->pixelformat);
> +		return;
> +	}
> +
> +	c3_isp_write(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id),
> +		     WRMIFX3_FMT_HSIZE(fmt->width) | WRMIFX3_FMT_VSIZE(fmt->height));
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
> +			   WRMIFX3_WIN_LUMA_HEND_MASK,
> +			   WRMIFX3_WIN_LUMA_HEND(fmt->width) << WRMIFX3_WIN_LUMA_HEND_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
> +			   WRMIFX3_WIN_LUMA_VEND_MASK,
> +			   WRMIFX3_WIN_LUMA_VEND(fmt->height) << WRMIFX3_WIN_LUMA_VEND_SHIFT);
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_HSIZE, cap->id),
> +			   WRMIFX3_CROP_HEND_MASK,
> +			   WRMIFX3_CROP_HEND(fmt->width) << WRMIFX3_CROP_HEND_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_VSIZE, cap->id),
> +			   WRMIFX3_CROP_VEND_MASK,
> +			   WRMIFX3_CROP_VEND(fmt->height) << WRMIFX3_CROP_VEND_SHIFT);
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
> +			   WRMIFX3_WIN_CHROM_HEND_MASK,
> +			   WRMIFX3_WIN_CHROM_HEND(fmt->width) << WRMIFX3_WIN_CHROM_HEND_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
> +			   WRMIFX3_WIN_CHROM_VEND_MASK,
> +			   WRMIFX3_WIN_CHROM_VEND(fmt->height) << WRMIFX3_WIN_CHROM_VEND_SHIFT);
> +}
> +
> +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)

This function is called from two locations, one with the buff_lock
held, the other one without, if I'm not mistaken.

> +{
> +	cap->buff = list_first_entry_or_null(&cap->pending,
> +					     struct c3_isp_vb2_buffer, list);
> +	if (cap->buff) {
> +		c3_isp_cap_wrmifx3_buff(cap);
> +		list_del(&cap->buff->list);
> +	}
> +}
> +
> +static void c3_isp_cap_start(struct c3_isp_capture *cap)
> +{
> +	c3_isp_cap_cfg_buff(cap);
> +
> +	c3_isp_cap_output_size(cap);
> +	c3_isp_cap_wrmifx3_size(cap);
> +
> +	c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN,
> +			   TOP_WRMIF_EN(cap->id), TOP_WRMIF_EN(cap->id));
> +
> +	cap->is_streaming = true;
> +}
> +
> +static void c3_isp_cap_stop(struct c3_isp_capture *cap)
> +{
> +	cap->is_streaming = false;
> +
> +	c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, TOP_WRMIF_EN(cap->id), 0);
> +}
> +
> +static int c3_isp_cap_done(struct c3_isp_capture *cap)
> +{
> +	struct c3_isp_vb2_buffer *buff = cap->buff;
> +	unsigned long flags;
> +
> +	if (!cap->is_streaming)
> +		return -EINVAL;

Is this an error condition or 0 should be returnes ?

> +
> +	spin_lock_irqsave(&cap->buff_lock, flags);
> +
> +	if (buff) {
> +		buff->vb.sequence = cap->isp->frm_sequence;
> +		buff->vb.vb2_buf.timestamp = ktime_get();
> +		buff->vb.field = V4L2_FIELD_NONE;
> +		vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +	}
> +
> +	c3_isp_cap_cfg_buff(cap);
> +
> +	spin_unlock_irqrestore(&cap->buff_lock, flags);
> +
> +	return 0;
> +}
> +
> +/* V4L2 video operations */
> +
> +static const struct c3_isp_capture_format
> +*c3_cap_find_fmt(u32 fourcc)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
> +		if (cap_formats[i].fourcc == fourcc)
> +			return &cap_formats[i];
> +	}
> +
> +	return NULL;
> +}
> +
> +static void c3_cap_try_fmt(struct c3_isp_capture *cap,
> +			   struct v4l2_pix_format *pix)
> +{
> +	const struct c3_isp_capture_format *fmt;
> +
> +	fmt = c3_cap_find_fmt(pix->pixelformat);
> +	if (!fmt)
> +		fmt = &cap_formats[0];
> +
> +	pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> +	pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> +	pix->pixelformat = fmt->fourcc;
> +	pix->field = V4L2_FIELD_NONE;
> +	pix->colorspace = V4L2_COLORSPACE_SRGB;
> +	pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
> +	pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> +
> +	/* ISP hardware requires 16 bytes alignment */
> +	pix->bytesperline = ALIGN(pix->width, 16);
> +	pix->sizeimage = pix->bytesperline * pix->height * fmt->depth / 8;
> +}
> +
> +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
> +				      enum vb2_buffer_state state)
> +{
> +	unsigned long flags;
> +	struct c3_isp_vb2_buffer *buff;
> +
> +	spin_lock_irqsave(&cap->buff_lock, flags);
> +
> +	if (cap->buff) {
> +		vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
> +		cap->buff = NULL;
> +	}
> +
> +	while (!list_empty(&cap->pending)) {
> +		buff = list_first_entry(&cap->pending,
> +					struct c3_isp_vb2_buffer, list);
> +		list_del(&buff->list);
> +		vb2_buffer_done(&buff->vb.vb2_buf, state);
> +	}
> +
> +	spin_unlock_irqrestore(&cap->buff_lock, flags);
> +}
> +
> +static int c3_isp_cap_querycap(struct file *file, void *fh,
> +			       struct v4l2_capability *cap)
> +{
> +	strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
> +	strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
> +
> +	return 0;
> +}
> +
> +static int c3_isp_cap_enum_fmt(struct file *file, void *fh,
> +			       struct v4l2_fmtdesc *f)
> +{
> +	const struct c3_isp_capture_format *fmt;
> +	unsigned int index = 0;
> +	unsigned int i;
> +
> +	if (!f->mbus_code) {
> +		if (f->index >= ARRAY_SIZE(cap_formats))
> +			return -EINVAL;
> +
> +		fmt = &cap_formats[f->index];
> +		f->pixelformat = fmt->fourcc;
> +		return 0;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
> +		fmt = &cap_formats[i];
> +		if (f->mbus_code != fmt->mbus_code)
> +			continue;
> +
> +		if (index++ == f->index) {
> +			f->pixelformat = cap_formats[i].fourcc;
> +			return 0;
> +		}
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int c3_isp_cap_g_fmt(struct file *file, void *fh,
> +			    struct v4l2_format *f)
> +{
> +	struct c3_isp_capture *cap = video_drvdata(file);
> +
> +	f->fmt.pix = cap->vfmt.fmt.pix;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_cap_s_fmt(struct file *file, void *fh,
> +			    struct v4l2_format *f)
> +{
> +	struct c3_isp_capture *cap = video_drvdata(file);
> +
> +	c3_cap_try_fmt(cap, &f->fmt.pix);
> +	cap->vfmt = *f;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_cap_try_fmt(struct file *file, void *fh,
> +			      struct v4l2_format *f)
> +{
> +	struct c3_isp_capture *cap = video_drvdata(file);
> +
> +	c3_cap_try_fmt(cap, &f->fmt.pix);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
> +				   struct v4l2_frmsizeenum *fsize)
> +{
> +	const struct c3_isp_capture_format *fmt;
> +
> +	if (fsize->index)
> +		return -EINVAL;
> +
> +	fmt = c3_cap_find_fmt(fsize->pixel_format);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> +	fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
> +	fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
> +	fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
> +	fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
> +	fsize->stepwise.step_width = 2;
> +	fsize->stepwise.step_height = 2;
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
> +	.vidioc_querycap		= c3_isp_cap_querycap,
> +	.vidioc_enum_fmt_vid_cap	= c3_isp_cap_enum_fmt,
> +	.vidioc_g_fmt_vid_cap		= c3_isp_cap_g_fmt,
> +	.vidioc_s_fmt_vid_cap		= c3_isp_cap_s_fmt,
> +	.vidioc_try_fmt_vid_cap		= c3_isp_cap_try_fmt,
> +	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
> +	.vidioc_querybuf		= vb2_ioctl_querybuf,
> +	.vidioc_qbuf			= vb2_ioctl_qbuf,
> +	.vidioc_expbuf			= vb2_ioctl_expbuf,
> +	.vidioc_dqbuf			= vb2_ioctl_dqbuf,
> +	.vidioc_prepare_buf		= vb2_ioctl_prepare_buf,
> +	.vidioc_create_bufs		= vb2_ioctl_create_bufs,
> +	.vidioc_streamon		= vb2_ioctl_streamon,
> +	.vidioc_streamoff		= vb2_ioctl_streamoff,
> +	.vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
> +	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
> +	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
> +};
> +
> +static const struct v4l2_file_operations isp_cap_v4l2_fops = {
> +	.open = v4l2_fh_open,
> +	.release = vb2_fop_release,
> +	.poll = vb2_fop_poll,
> +	.unlocked_ioctl = video_ioctl2,
> +	.mmap = vb2_fop_mmap,
> +};
> +
> +static int c3_isp_cap_link_validate(struct media_link *link)
> +{
> +	struct video_device *vdev =
> +		media_entity_to_video_device(link->sink->entity);
> +	struct v4l2_subdev *sd =
> +		media_entity_to_v4l2_subdev(link->source->entity);
> +	struct c3_isp_capture *cap = video_get_drvdata(vdev);
> +	struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
> +	struct v4l2_subdev_format src_fmt = {
> +		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
> +		.pad = link->source->index,
> +	};
> +	const struct c3_isp_capture_format *cap_fmt =
> +				c3_cap_find_fmt(pix_fmt->pixelformat);
> +	int ret;
> +
> +	ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
> +	if (ret)
> +		return ret;
> +
> +	if (src_fmt.format.width != pix_fmt->width ||
> +	    src_fmt.format.height != pix_fmt->height ||
> +	    src_fmt.format.code != cap_fmt->mbus_code) {
> +		dev_err(cap->isp->dev,
> +			"link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n",
> +			link->source->entity->name, link->source->index,
> +			link->sink->entity->name, link->sink->index,
> +			src_fmt.format.code, src_fmt.format.width,
> +			src_fmt.format.height, cap_fmt->mbus_code,
> +			pix_fmt->width, pix_fmt->height);
> +
> +		return -EPIPE;
> +	};
> +
> +	return 0;
> +}
> +
> +static const struct media_entity_operations isp_cap_entity_ops = {
> +	.link_validate = c3_isp_cap_link_validate,
> +};
> +
> +static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
> +				  unsigned int *num_buffers,
> +				  unsigned int *num_planes,
> +				  unsigned int sizes[],
> +				  struct device *alloc_devs[])
> +{
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> +	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> +
> +	if (*num_planes) {
> +		if (*num_planes != 1)
> +			return -EINVAL;
> +
> +		if (sizes[0] < pix->sizeimage)
> +			return -EINVAL;
> +	} else {
> +		*num_planes = 1;
> +		sizes[0] = pix->sizeimage;
> +	}
> +
> +	return 0;
> +}
> +
> +static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_vb2_buffer *buf =
> +			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&cap->buff_lock, flags);
> +
> +	list_add_tail(&buf->list, &cap->pending);
> +
> +	spin_unlock_irqrestore(&cap->buff_lock, flags);
> +}
> +
> +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
> +{
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned int size = cap->vfmt.fmt.pix.sizeimage;
> +
> +	if (vb2_plane_size(vb, 0) < size) {
> +		dev_err(cap->isp->dev,
> +			"User buffer too small (%ld < %u)\n",
> +			vb2_plane_size(vb, 0), size);
> +		return -EINVAL;
> +	}
> +
> +	vb2_set_plane_payload(vb, 0, size);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_vb2_buffer *buf =
> +		container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> +
> +	buf->vaddr = vb2_plane_vaddr(vb, 0);
> +	buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
> +
> +	memset(buf->vaddr, 0, cap->vfmt.fmt.pix.sizeimage);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
> +				      unsigned int count)
> +{
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> +	int ret;
> +
> +	guard(mutex)(&cap->isp->lock);
> +
> +	ret = pm_runtime_resume_and_get(cap->isp->dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
> +	if (ret) {
> +		dev_err(cap->isp->dev,
> +			"Failed to start cap%u pipeline: %d\n", cap->id, ret);
> +		goto err_pm_put;
> +	}
> +
> +	if (c3_isp_pipeline_ready(cap->isp)) {
> +		ret = v4l2_subdev_enable_streams(&cap->isp->core.sd,
> +						 C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +						 BIT(0));
> +		if (ret)
> +			goto err_pipeline_stop;
> +	}
> +
> +	c3_isp_rsz_start(cap->rsz);
> +	c3_isp_cap_start(cap);
> +
> +	return 0;
> +
> +err_pipeline_stop:
> +	video_device_pipeline_stop(&cap->vdev);
> +err_pm_put:
> +	pm_runtime_put(cap->isp->dev);
> +	c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
> +	return ret;
> +}
> +
> +static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
> +{
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> +
> +	guard(mutex)(&cap->isp->lock);
> +
> +	c3_isp_cap_stop(cap);
> +	c3_isp_rsz_stop(cap->rsz);
> +	c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
> +
> +	if (cap->isp->pipe.start_count == 1)
> +		v4l2_subdev_disable_streams(&cap->isp->core.sd,
> +					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +					    BIT(0));
> +
> +	video_device_pipeline_stop(&cap->vdev);
> +	pm_runtime_put(cap->isp->dev);
> +}
> +
> +static const struct vb2_ops isp_video_vb2_ops = {
> +	.queue_setup = c3_isp_vb2_queue_setup,
> +	.buf_queue = c3_isp_vb2_buf_queue,
> +	.buf_prepare = c3_isp_vb2_buf_prepare,
> +	.buf_init = c3_isp_vb2_buf_init,
> +	.wait_prepare = vb2_ops_wait_prepare,
> +	.wait_finish = vb2_ops_wait_finish,
> +	.start_streaming = c3_isp_vb2_start_streaming,
> +	.stop_streaming = c3_isp_vb2_stop_streaming,
> +};
> +
> +static int c3_isp_register_capture(struct c3_isp_capture *cap)
> +{
> +	struct video_device *vdev = &cap->vdev;
> +	struct vb2_queue *vb2_q = &cap->vb2_q;
> +	int ret;
> +
> +	snprintf(vdev->name, sizeof(vdev->name), "isp-cap%u", cap->id);
> +	vdev->fops = &isp_cap_v4l2_fops;
> +	vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
> +	vdev->v4l2_dev = &cap->isp->v4l2_dev;
> +	vdev->entity.ops = &isp_cap_entity_ops;
> +	vdev->lock = &cap->lock;
> +	vdev->minor = -1;
> +	vdev->queue = vb2_q;
> +	vdev->release = video_device_release_empty;
> +	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> +	vdev->vfl_dir = VFL_DIR_RX;
> +	video_set_drvdata(vdev, cap);
> +
> +	vb2_q->drv_priv = cap;
> +	vb2_q->mem_ops = &vb2_dma_contig_memops;
> +	vb2_q->ops = &isp_video_vb2_ops;
> +	vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +	vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
> +	vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
> +	vb2_q->dev = cap->isp->dev;
> +	vb2_q->lock = &cap->lock;
> +	vb2_q->min_queued_buffers = 2;

I'm not sure what your plans regarding libcamera are, but be aware
we're going to get stricter about this value. Ideally, we aim to have
ISP driver set this value to 0 so that the ISP operates (produce
statistics) even if no buffer is queued to the capture devices. The
reason is that we want algorithms to run even if there are no capture
buffers provided.

In order to allow the driver to operate with no buffers, you could
probably allocate a scratch buffer in the driver and use it whenever
you receive a frame completed IRQ and have no buffers available in the
cap->provided queue.

See rkisp1_dummy_buf_create() in the RkISP1 driver and how dummy_buf
is used there.

> +
> +	ret = vb2_queue_init(vb2_q);
> +	if (ret < 0)
> +		goto err_destroy;
> +
> +	cap->pad.flags = MEDIA_PAD_FL_SINK;
> +	ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
> +	if (ret < 0)
> +		goto err_queue_release;
> +
> +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +	if (ret < 0) {
> +		dev_err(cap->isp->dev,
> +			"Failed to register %s: %d\n", vdev->name, ret);
> +		goto err_entity_cleanup;
> +	}
> +
> +	return 0;
> +
> +err_entity_cleanup:
> +	media_entity_cleanup(&vdev->entity);
> +err_queue_release:
> +	vb2_queue_release(vb2_q);
> +err_destroy:
> +	mutex_destroy(&cap->lock);
> +	return ret;
> +}
> +
> +int c3_isp_captures_register(struct c3_isp_device *isp)
> +{
> +	int ret;
> +	unsigned int i;
> +	struct c3_isp_capture *cap;
> +
> +	for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
> +		cap = &isp->caps[i];
> +		memset(cap, 0, sizeof(*cap));
> +
> +		cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
> +		cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
> +		cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
> +		cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
> +		cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
> +
> +		c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
> +
> +		cap->id = i;
> +		if (cap->id == C3_ISP_CAP_DEV_0)
> +			cap->rsz = &isp->resizers[C3_ISP_RSZ_0];
> +		else if (cap->id == C3_ISP_CAP_DEV_1)
> +			cap->rsz = &isp->resizers[C3_ISP_RSZ_1];
> +		else
> +			cap->rsz = &isp->resizers[C3_ISP_RSZ_2];
> +
> +		cap->isp = isp;
> +		INIT_LIST_HEAD(&cap->pending);
> +		spin_lock_init(&cap->buff_lock);
> +		mutex_init(&cap->lock);
> +
> +		ret = c3_isp_register_capture(cap);
> +		if (ret) {
> +			cap->isp = NULL;
> +			mutex_destroy(&cap->lock);
> +			c3_isp_captures_unregister(isp);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +void c3_isp_captures_unregister(struct c3_isp_device *isp)
> +{
> +	unsigned int i;
> +	struct c3_isp_capture *cap;
> +
> +	for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
> +		cap = &isp->caps[i];
> +
> +		if (!cap->isp)
> +			continue;
> +		vb2_queue_release(&cap->vb2_q);
> +		media_entity_cleanup(&cap->vdev.entity);
> +		video_unregister_device(&cap->vdev);
> +		mutex_destroy(&cap->lock);
> +	}
> +}
> +
> +void c3_isp_captures_done(struct c3_isp_device *isp)
> +{
> +	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
> +	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
> +	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
> +}
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
> new file mode 100644
> index 000000000000..19f2a3bc29c9
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
> @@ -0,0 +1,327 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#ifndef __C3_ISP_COMMON_H__
> +#define __C3_ISP_COMMON_H__
> +
> +#include <linux/clk.h>
> +
> +#include <media/media-device.h>
> +#include <media/videobuf2-core.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#define C3_ISP_DRIVER_NAME            "c3-isp"
> +#define C3_ISP_CLOCK_NUM_MAX          3
> +
> +#define C3_ISP_CORE_DEF_SINK_PAD_FMT  MEDIA_BUS_FMT_SRGGB10_1X10
> +#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUYV8_1_5X8
> +#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUYV8_1_5X8
> +#define C3_ISP_DEFAULT_WIDTH          1920
> +#define C3_ISP_DEFAULT_HEIGHT         1080
> +#define C3_ISP_MAX_WIDTH              2888
> +#define C3_ISP_MAX_HEIGHT             2240
> +#define C3_ISP_MIN_WIDTH              160
> +#define C3_ISP_MIN_HEIGHT             120
> +
> +#define C3_DISP_INTER                 0x400
> +#define C3_DISP_REG(base, id)         ((base) + (id) * C3_DISP_INTER)
> +#define C3_WRMIFX3_INTER              0x100
> +#define C3_WRMIFX3_REG(base, id)      ((base) + (id) * C3_WRMIFX3_INTER)
> +#define C3_PPS_TAP4_S11_H_NUM         33
> +#define C3_PPS_LUT_CTYPE_0            0
> +#define C3_PPS_LUT_CTYPE_2            2
> +#define C3_SCALE_EN                   1
> +#define C3_SCALE_DIS                  0
> +
> +#define C3_ISP_PHASE_OFFSET_0         0
> +#define C3_ISP_PHASE_OFFSET_1         1
> +#define C3_ISP_PHASE_OFFSET_NONE      0xff
> +
> +enum c3_isp_core_pads {
> +	C3_ISP_CORE_PAD_SINK_VIDEO,
> +	C3_ISP_CORE_PAD_SINK_PARAMS,
> +	C3_ISP_CORE_PAD_SOURCE_STATS,
> +	C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +	C3_ISP_CORE_PAD_MAX
> +};
> +
> +enum c3_isp_resizer_ids {
> +	C3_ISP_RSZ_0,
> +	C3_ISP_RSZ_1,
> +	C3_ISP_RSZ_2,
> +	C3_ISP_NUM_RSZ
> +};
> +
> +enum c3_isp_resizer_pads {
> +	C3_ISP_RESIZER_PAD_SINK,
> +	C3_ISP_RESIZER_PAD_SOURCE,
> +	C3_ISP_RESIZER_PAD_MAX
> +};
> +
> +enum c3_isp_cap_devs {
> +	C3_ISP_CAP_DEV_0,
> +	C3_ISP_CAP_DEV_1,
> +	C3_ISP_CAP_DEV_2,
> +	C3_ISP_NUM_CAP_DEVS
> +};
> +
> +/**
> + * struct c3_isp_pps_io_size - isp scaler input and output size
> + *
> + * @thsize: input horizontal size of after preprocessing
> + * @tvsize: input vertical size of after preprocessing
> + * @ohsize: output horizontal size
> + * @ovsize: output vertical size
> + * @ihsize: input horizontal size
> + * @max_hsize: maximum horizontal size
> + */
> +struct c3_isp_pps_io_size {
> +	u32 thsize;
> +	u32 tvsize;
> +	u32 ohsize;
> +	u32 ovsize;
> +	u32 ihsize;
> +	u32 max_hsize;
> +};
> +
> +/**
> + * @mbus_code: the mbus code
> + * @pads: save the pad flag of this mbus_code
> + * @xofst: horizontal phase offset of hardware
> + * @yofst: vertical phase offset of hardware
> + */
> +struct c3_isp_mbus_format_info {
> +	u32 mbus_code;
> +	u32 pads;
> +	u8 xofst;
> +	u8 yofst;
> +};
> +
> +/**
> + * @mbus_code: the mbus code
> + * @fourcc: pixel format
> + * @depth: pixel width
> + */
> +struct c3_isp_capture_format {
> +	u32 mbus_code;
> +	u32 fourcc;
> +	u8 depth;
> +};
> +
> +/**
> + * struct c3_isp_vb2_buffer - A container of vb2 buffer
> + *
> + * @vb: vb2 buffer
> + * @vaddr: buffer virtual address
> + * @paddr: buffer physical address
> + * @list: entry of the buffer in the queue
> + */
> +struct c3_isp_vb2_buffer {
> +	struct vb2_v4l2_buffer vb;
> +	void *vaddr;
> +	dma_addr_t paddr;
> +	struct list_head list;
> +};
> +
> +/**
> + * struct c3_isp_core - ISP core subdev
> + *
> + * @sd: ISP sub-device
> + * @pads: ISP sub-device pads
> + * @src_sd: source sub-device
> + * @isp: pointer to c3_isp_device
> + * @src_sd_pad: source sub-device pad
> + */
> +struct c3_isp_core {
> +	struct v4l2_subdev sd;
> +	struct media_pad pads[C3_ISP_CORE_PAD_MAX];
> +	struct v4l2_subdev *src_sd;
> +	u16 src_sd_pad;
> +	struct c3_isp_device *isp;
> +};
> +
> +/**
> + * struct c3_isp_resizer - ISP resizer subdev
> + *
> + * @id: resizer id
> + * @sd: resizer sub-device
> + * @pads: resizer sub-device pads
> + * @isp: pointer to c3_isp_device
> + * @cap: pointer to c3_isp_capture
> + */
> +struct c3_isp_resizer {
> +	enum c3_isp_resizer_ids id;
> +	struct v4l2_subdev sd;
> +	struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
> +	struct c3_isp_device *isp;
> +	struct c3_isp_capture *cap;
> +};
> +
> +/**
> + * struct c3_isp_stats - ISP statistics device
> + *
> + * @vb2_q: vb2 buffer queue
> + * @vdev: video node
> + * @vfmt: v4l2_format of the metadata format
> + * @pad: media pad
> + * @lock: protects vb2_q, vdev
> + * @is_streaming: stats status
> + * @isp: pointer to c3_isp_device
> + * @buff: in use buffer
> + * @buff_lock: protects stats buffer
> + * @pending: stats buffer list head
> + */
> +struct c3_isp_stats {
> +	struct vb2_queue vb2_q;
> +	struct video_device vdev;
> +	struct v4l2_format vfmt;
> +	struct media_pad pad;
> +
> +	struct mutex lock; /* Protects vb2_q, vdev */
> +	bool is_streaming;
> +	struct c3_isp_device *isp;
> +
> +	struct c3_isp_vb2_buffer *buff;
> +	spinlock_t buff_lock; /* Protects stream buffer */
> +	struct list_head pending;
> +};
> +
> +/**
> + * struct c3_isp_params - ISP parameters device
> + *
> + * @vb2_q: vb2 buffer queue
> + * @vdev: video node
> + * @vfmt: v4l2_format of the metadata format
> + * @pad: media pad
> + * @lock: protects vb2_q, vdev
> + * @isp: pointer to c3_isp_device
> + * @buff: in use buffer
> + * @buff_lock: protects stats buffer
> + * @pending: stats buffer list head
> + */
> +struct c3_isp_params {
> +	struct vb2_queue vb2_q;
> +	struct video_device vdev;
> +	struct v4l2_format vfmt;
> +	struct media_pad pad;
> +
> +	struct mutex lock; /* Protects vb2_q, vdev */
> +	struct c3_isp_device *isp;
> +
> +	struct c3_isp_vb2_buffer *buff;
> +	spinlock_t buff_lock; /* Protects stream buffer */
> +	struct list_head pending;
> +};
> +
> +/**
> + * struct c3_isp_capture - ISP capture device
> + *
> + * @id: capture device ID
> + * @vb2_q: vb2 buffer queue
> + * @vdev: video node
> + * @vfmt: v4l2_format of the capture format
> + * @pad: media pad
> + * @lock: protects vb2_q, vdev
> + * @is_streaming: capture device status
> + * @isp: pointer to c3_isp_device
> + * @rsz: pointer to c3_isp_resizer
> + * @buff: in use buffer
> + * @buff_lock: protects capture buffer
> + * @pending: capture buffer list head
> + */
> +struct c3_isp_capture {
> +	enum c3_isp_cap_devs id;
> +	struct vb2_queue vb2_q;
> +	struct video_device vdev;
> +	struct v4l2_format vfmt;
> +	struct media_pad pad;
> +
> +	struct mutex lock; /* Protects vb2_q, vdev */
> +	bool is_streaming;
> +	struct c3_isp_device *isp;
> +	struct c3_isp_resizer *rsz;
> +
> +	struct c3_isp_vb2_buffer *buff;
> +	spinlock_t buff_lock; /* Protects stream buffer */
> +	struct list_head pending;
> +};
> +
> +/**
> + * struct c3_isp_info - ISP information
> + *
> + * @clocks: array of ISP clock names
> + * @clock_rates: array of ISP clock rate
> + * @clock_num: actual clock number
> + */
> +struct c3_isp_info {
> +	char *clocks[C3_ISP_CLOCK_NUM_MAX];
> +	u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
> +	u32 clock_num;
> +};
> +
> +/**
> + * struct c3_isp_device - ISP platform device
> + *
> + * @dev: pointer to the struct device
> + * @base: base register address
> + * @clks: array of clocks
> + * @notifier: notifier to register on the v4l2-async API
> + * @v4l2_dev: v4l2_device variable
> + * @media_dev: media device variable
> + * @pipe: media pipeline
> + * @core: ISP core subdev
> + * @resizer: ISP resizer subdev
> + * @stats: ISP stats device
> + * @params: ISP params device
> + * @caps: array of ISP capture device
> + * @frm_sequence: used to record frame id
> + * @lock: protect ISP device
> + * @info: version-specific ISP information
> + */
> +struct c3_isp_device {
> +	struct device *dev;
> +	void __iomem *base;
> +	struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
> +
> +	struct v4l2_async_notifier notifier;
> +	struct v4l2_device v4l2_dev;
> +	struct media_device media_dev;
> +	struct media_pipeline pipe;
> +
> +	struct c3_isp_core core;
> +	struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
> +	struct c3_isp_stats stats;
> +	struct c3_isp_params params;
> +	struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
> +
> +	u32 frm_sequence;
> +	struct mutex lock; /* Protect ISP device */
> +	const struct c3_isp_info *info;
> +};
> +
> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp);
> +
> +int c3_isp_core_register(struct c3_isp_device *isp);
> +void c3_isp_core_unregister(struct c3_isp_device *isp);
> +int c3_isp_resizers_register(struct c3_isp_device *isp);
> +void c3_isp_resizers_unregister(struct c3_isp_device *isp);
> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz);
> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz);
> +int c3_isp_captures_register(struct c3_isp_device *isp);
> +void c3_isp_captures_unregister(struct c3_isp_device *isp);
> +void c3_isp_captures_done(struct c3_isp_device *isp);
> +int c3_isp_stats_register(struct c3_isp_device *isp);
> +void c3_isp_stats_unregister(struct c3_isp_device *isp);
> +int c3_isp_stats_done(struct c3_isp_device *isp);
> +int c3_isp_params_register(struct c3_isp_device *isp);
> +void c3_isp_params_unregister(struct c3_isp_device *isp);
> +int c3_isp_params_done(struct c3_isp_device *isp);
> +
> +#endif
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
> new file mode 100644
> index 000000000000..d3672aff9fd2
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
> @@ -0,0 +1,675 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/pm_runtime.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +#include "include/uapi/c3-isp-config.h"
> +
> +#define C3_ISP_CORE_SUBDEV_NAME        "isp-core"
> +
> +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
> +	/* RAW formats */
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SGBRG10_1X10,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SBGGR12_1X12,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SGBRG12_1X12,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SGRBG12_1X12,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SRGGB12_1X12,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> +	},
> +	/* YUV formats */
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_Y8_1X8,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_YUYV8_1_5X8,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_YUYV8_2X8,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> +	},
> +};
> +
> +static const struct c3_isp_mbus_format_info
> +*core_find_format_by_code(u32 code, u32 pad)
> +{
> +	int i;

unsigned

> +
> +	for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
> +		const struct c3_isp_mbus_format_info *info =
> +			&c3_isp_core_mbus_formats[i];
> +
> +		if (info->mbus_code == code && info->pads & BIT(pad))
> +			return info;
> +	}
> +
> +	return NULL;
> +}
> +
> +static const struct c3_isp_mbus_format_info
> +*core_find_format_by_index(u32 index, u32 pad)
> +{
> +	int i;

unsigned

> +
> +	for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
> +		const struct c3_isp_mbus_format_info *info =
> +			&c3_isp_core_mbus_formats[i];
> +
> +		if (!(info->pads & BIT(pad)))
> +			continue;
> +
> +		if (!index)
> +			return info;
> +
> +		index--;
> +	}
> +
> +	return NULL;
> +}
> +
> +static void c3_isp_core_enable(struct c3_isp_device *isp)
> +{
> +	/* Select the line sync signal */
> +	c3_isp_update_bits(isp, ISP_TOP_IRQ_LINE_THRD,
> +			   TOP_IRQ_DIN_HSYNC, TOP_IRQ_DIN_HSYNC);
> +
> +	/* Enable frame done and stats error irq */
> +	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
> +			   TOP_IRQ_FRAME_DONE, TOP_IRQ_FRAME_DONE);
> +	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
> +			   TOP_IRQ_STATS_ERR, TOP_IRQ_STATS_ERR);
> +
> +	/* Enable image data to ISP core */
> +	c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
> +			   TOP_DATA_PATH_MASK, TOP_PATH_MIPI_TO_CORE);
> +}
> +
> +static void c3_isp_core_disable(struct c3_isp_device *isp)
> +{
> +	/* Disable image data to ISP core */
> +	c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
> +			   TOP_DATA_PATH_MASK, 0x0);
> +
> +	/* Disable all irq */
> +	c3_isp_write(isp, ISP_TOP_IRQ_EN, 0x0);
> +}
> +
> +/* Set the phase offset of blc, wb and lns */
> +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
> +				  u8 xofst, u8 yofst)
> +{
> +	c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
> +			   LSWB_BLC_XPHS_OFST_MASK,
> +			   xofst << LSWB_BLC_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
> +			   LSWB_BLC_YPHS_OFST_MASK, yofst);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
> +			   LSWB_WB_XPHS_OFST_MASK,
> +			   xofst << LSWB_WB_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
> +			   LSWB_WB_YPHS_OFST_MASK, yofst);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
> +			   LSWB_LNS_XPHS_OFST_MASK,
> +			   xofst << LSWB_LNS_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
> +			   LSWB_LNS_YPHS_OFST_MASK, yofst);
> +}
> +
> +/* Set the phase offset of af, ae and awb */
> +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
> +				u8 xofst, u8 yofst)
> +{
> +	c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_XPHS_OFST_MASK,
> +			   xofst << AF_CTRL_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_YPHS_OFST_MASK,
> +			   yofst << AF_CTRL_YPHS_OFST_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_XPHS_OFST_MASK,
> +			   xofst << AE_CTRL_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_YPHS_OFST_MASK,
> +			   yofst << AE_CTRL_YPHS_OFST_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_XPHS_OFST_MASK,
> +			   xofst << AWB_CTRL_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_YPHS_OFST_MASK, yofst);
> +}
> +
> +/* Set the phase offset of demosaic */
> +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
> +				 u8 xofst, u8 yofst)
> +{
> +	c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_XPHS_OFST_MASK,
> +			   xofst << DMS_COMMON_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_YPHS_OFST_MASK, yofst);
> +}
> +
> +static void c3_isp_core_cfg_ofst(struct c3_isp_device *isp,
> +				 struct v4l2_mbus_framefmt *fmt)
> +{
> +	const struct c3_isp_mbus_format_info *isp_fmt =
> +			core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
> +
> +	c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> +	c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> +	c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> +}
> +
> +/* Set format of the hardware control module */
> +static void c3_isp_core_top_fmt(struct c3_isp_device *isp,
> +				struct v4l2_mbus_framefmt *fmt)
> +{
> +	c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
> +		     TOP_INPUT_HSIZE(fmt->width) | TOP_INPUT_VSIZE(fmt->height));
> +
> +	c3_isp_write(isp, ISP_TOP_FRM_SIZE,
> +		     TOP_FRM_CORE_IHSIZE(fmt->width) | TOP_FRM_CORE_IVSIZE(fmt->height));
> +
> +	c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE, TOP_HOLD_HSIZE_MASK,
> +			   fmt->width << TOP_HOLD_HSIZE_SHIFT);
> +}
> +
> +static void c3_isp_core_af_fmt(struct c3_isp_device *isp,
> +			       struct v4l2_mbus_framefmt *fmt)
> +{
> +	u32 hidx;
> +	u32 vidx;
> +	int i;
> +
> +	c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, AF_HV_STAT_HBLK_NUM_MASK,
> +			   AF_STAT_BLKH_NUM << AF_HV_STAT_HBLK_NUM_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM,
> +			   AF_HV_STAT_VBLK_NUM_MASK, AF_STAT_BLKV_NUM);
> +
> +	c3_isp_write(isp, ISP_AF_HV_SIZE,
> +		     AF_HV_STAT_HSIZE(fmt->width) | AF_HV_STAT_VSIZE(fmt->height));
> +
> +	/* Set the index address to 0 position */
> +	c3_isp_write(isp, ISP_AF_IDX_ADDR, 0);
> +	/*
> +	 * Calculate and set the coordinates of points in the grid.
> +	 * hidx and vidx need to be aligned with 2.
> +	 */
> +	for (i = 0; i <= AF_STAT_BLKH_NUM; i++) {
> +		hidx = i * fmt->width / AF_STAT_BLKH_NUM;
> +		hidx = ALIGN_DOWN(hidx, 2);
> +
> +		vidx = i * fmt->height / AF_STAT_BLKV_NUM;
> +		vidx = min(vidx, fmt->height);
> +		vidx = ALIGN_DOWN(vidx, 2);
> +		c3_isp_write(isp, ISP_AF_IDX_DATA,
> +			     AF_IDX_HIDX_DATA(hidx) | AF_IDX_VIDX_DATA(vidx));
> +	}
> +}
> +
> +static void c3_isp_core_ae_fmt(struct c3_isp_device *isp,
> +			       struct v4l2_mbus_framefmt *fmt)
> +{
> +	u32 hidx;
> +	u32 vidx;
> +	int i;
> +
> +	c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, AE_HV_STAT_HBLK_NUM_MASK,
> +			   AE_STAT_BLKH_NUM << AE_HV_STAT_HBLK_NUM_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM,
> +			   AE_HV_STAT_VBLK_NUM_MASK, AE_STAT_BLKV_NUM);
> +
> +	c3_isp_write(isp, ISP_AE_HV_SIZE,
> +		     AE_HV_STAT_HSIZE(fmt->width) | AE_HV_STAT_VSIZE(fmt->height));
> +
> +	/* Set the index address to 0 position */
> +	c3_isp_write(isp, ISP_AE_IDX_ADDR, 0);
> +	/*
> +	 * Calculate and set the coordinates of points in the grid.
> +	 * hidx and vidx need to be aligned with 2.
> +	 */
> +	for (i = 0; i <= AE_STAT_BLKH_NUM; i++) {
> +		hidx = i * fmt->width / AE_STAT_BLKH_NUM;
> +		hidx = ALIGN_DOWN(hidx, 2);
> +
> +		vidx = i * fmt->height / AE_STAT_BLKV_NUM;
> +		vidx = min(vidx, fmt->height);
> +		vidx = ALIGN_DOWN(vidx, 2);
> +
> +		c3_isp_write(isp, ISP_AE_IDX_DATA,
> +			     AE_IDX_HIDX_DATA(hidx) | AE_IDX_VIDX_DATA(vidx));
> +	}
> +}
> +
> +static void c3_isp_core_awb_fmt(struct c3_isp_device *isp,
> +				struct v4l2_mbus_framefmt *fmt)
> +{
> +	u32 hidx;
> +	u32 vidx;
> +	int i;
> +
> +	c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, AWB_HV_STAT_HBLK_NUM_MASK,
> +			   AWB_STAT_BLKH_NUM << AWB_HV_STAT_HBLK_NUM_SHIFT);
> +	c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM,
> +			   AWB_HV_STAT_VBLK_NUM_MASK, AWB_STAT_BLKV_NUM);
> +
> +	c3_isp_write(isp, ISP_AWB_HV_SIZE,
> +		     AWB_HV_STAT_HSIZE(fmt->width) | AWB_HV_STAT_VSIZE(fmt->height));
> +
> +	/* Set the index address to 0 position */
> +	c3_isp_write(isp, ISP_AWB_IDX_ADDR, 0);
> +	/*
> +	 * Calculate and set the coordinates of points in the grid.
> +	 * hidx and vidx need to be aligned with 2.
> +	 */
> +	for (i = 0; i <= AWB_STAT_BLKH_NUM; i++) {
> +		hidx = i * fmt->width / AWB_STAT_BLKH_NUM;
> +		hidx = ALIGN_DOWN(hidx, 2);
> +
> +		vidx = i * fmt->height / AWB_STAT_BLKV_NUM;
> +		vidx = min(vidx, fmt->height);
> +		vidx = ALIGN_DOWN(vidx, 2);
> +
> +		c3_isp_write(isp, ISP_AWB_IDX_DATA,
> +			     AWB_IDX_HIDX_DATA(hidx) | AWB_IDX_VIDX_DATA(vidx));
> +	}
> +}
> +
> +static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
> +				   struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> +
> +	c3_isp_core_cfg_ofst(isp, fmt);
> +	c3_isp_core_top_fmt(isp, fmt);
> +	c3_isp_core_af_fmt(isp, fmt);
> +	c3_isp_core_ae_fmt(isp, fmt);
> +	c3_isp_core_awb_fmt(isp, fmt);
> +}
> +
> +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
> +				      struct v4l2_subdev_state *state,
> +				      u32 pad, u64 streams_mask)
> +{
> +	struct c3_isp_core *core = v4l2_get_subdevdata(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	core->isp->frm_sequence = 0;
> +	c3_isp_core_cfg_format(core->isp, state);
> +	c3_isp_core_enable(core->isp);
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       C3_ISP_CORE_PAD_SINK_VIDEO,
> +						       &streams_mask);
> +	ret = v4l2_subdev_enable_streams(core->src_sd,
> +					 core->src_sd_pad, sink_streams);
> +	if (ret) {
> +		c3_isp_core_disable(core->isp);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
> +				       struct v4l2_subdev_state *state,
> +				       u32 pad, u64 streams_mask)
> +{
> +	struct c3_isp_core *core = v4l2_get_subdevdata(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       C3_ISP_CORE_PAD_SINK_VIDEO,
> +						       &streams_mask);
> +	ret = v4l2_subdev_disable_streams(core->src_sd,
> +					  core->src_sd_pad, sink_streams);
> +	if (ret)
> +		return ret;
> +
> +	c3_isp_core_disable(core->isp);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   struct v4l2_subdev_krouting *routing)
> +{
> +	static const struct v4l2_mbus_framefmt format = {
> +		.width = C3_ISP_DEFAULT_WIDTH,
> +		.height = C3_ISP_DEFAULT_HEIGHT,
> +		.code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
> +		.field = V4L2_FIELD_NONE,
> +		.colorspace = V4L2_COLORSPACE_SRGB,
> +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
> +		.xfer_func = V4L2_XFER_FUNC_SRGB,
> +	};
> +	int ret;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_NO_SOURCE_STREAM_MIX);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes[2];
> +	struct v4l2_subdev_krouting routing;
> +
> +	routes[0].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
> +	routes[0].sink_stream = 0;
> +	routes[0].source_pad = C3_ISP_CORE_PAD_SOURCE_STATS;
> +	routes[0].source_stream = 0;
> +	routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> +
> +	routes[1].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
> +	routes[1].sink_stream = 0;
> +	routes[1].source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
> +	routes[1].source_stream = 0;
> +	routes[1].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> +
> +	routing.num_routes = ARRAY_SIZE(routes);
> +	routing.routes = routes;
> +
> +	return c3_isp_core_cfg_routing(sd, state, &routing);
> +}
> +
> +static int c3_isp_core_set_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   enum v4l2_subdev_format_whence which,
> +				   struct v4l2_subdev_krouting *routing)

I'm not sure I see a reason for the ISP subdev to implement routing.
In my understanding it will only receive a single image stream and
processes it to 3 DMA output nodes, and to control which output is
enabled you use media links.

> +{
> +	bool is_streaming = v4l2_subdev_is_streaming(sd);
> +
> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> +		return -EBUSY;
> +
> +	return c3_isp_core_cfg_routing(sd, state, routing);
> +}
> +
> +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
> +				      struct v4l2_subdev_state *state,
> +				      struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	const struct c3_isp_mbus_format_info *info;
> +	int ret = 0;
> +
> +	switch (code->pad) {
> +	case C3_ISP_CORE_PAD_SINK_VIDEO:
> +	case C3_ISP_CORE_PAD_SOURCE_VIDEO:
> +		info = core_find_format_by_index(code->index, code->pad);
> +		if (!info)
> +			ret = -EINVAL;
> +		else
> +			code->code = info->mbus_code;
> +
> +		break;
> +	case C3_ISP_CORE_PAD_SINK_PARAMS:
> +	case C3_ISP_CORE_PAD_SOURCE_STATS:
> +		if (code->index)
> +			ret = -EINVAL;
> +		else
> +			code->code = MEDIA_BUS_FMT_METADATA_FIXED;
> +
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static void c3_isp_core_set_sink_fmt(struct v4l2_subdev_state *state,
> +				     struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	const struct c3_isp_mbus_format_info *isp_fmt;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
> +
> +	isp_fmt = core_find_format_by_code(format->format.code, format->pad);
> +	if (!isp_fmt)
> +		sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
> +	else
> +		sink_fmt->code = format->format.code;
> +
> +	sink_fmt->width = clamp_t(u32, format->format.width,
> +				  C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> +	sink_fmt->height = clamp_t(u32, format->format.height,
> +				   C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> +
> +	format->format = *sink_fmt;
> +}
> +
> +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
> +				       struct v4l2_subdev_format *format)
> +{
> +	const struct c3_isp_mbus_format_info *isp_fmt;
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> +	src_fmt = v4l2_subdev_state_get_format(state, format->pad);
> +
> +	isp_fmt = core_find_format_by_code(format->format.code, format->pad);
> +	if (!isp_fmt)
> +		src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
> +	else
> +		src_fmt->code = format->format.code;
> +
> +	/* The source size must be same with the sink size. */
> +	src_fmt->width  = sink_fmt->width;
> +	src_fmt->height = sink_fmt->height;
> +
> +	format->format = *src_fmt;
> +}
> +
> +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
> +			       struct v4l2_subdev_state *state,
> +			       struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	switch (format->pad) {
> +	case C3_ISP_CORE_PAD_SINK_VIDEO:
> +		c3_isp_core_set_sink_fmt(state, format);
> +		break;
> +	case C3_ISP_CORE_PAD_SINK_PARAMS:
> +	case C3_ISP_CORE_PAD_SOURCE_STATS:
> +		fmt = v4l2_subdev_state_get_format(state, format->pad);
> +		format->format = *fmt;
> +		break;
> +	case C3_ISP_CORE_PAD_SOURCE_VIDEO:
> +		c3_isp_core_set_source_fmt(state, format);
> +		break;
> +	default:

I don't think this can happen. The core validates that format->pad is
correct.

> +		dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
> +		return -ENOTTY;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_core_init_state(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;
> +
> +	/* Video sink pad */
> +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> +	sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
> +	sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> +	sink_fmt->field = V4L2_FIELD_NONE;
> +	sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
> +	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> +	sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
> +	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> +	sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
> +
> +	/* Video source pad */
> +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
> +	src_fmt->width = C3_ISP_DEFAULT_WIDTH;
> +	src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> +	src_fmt->field = V4L2_FIELD_NONE;
> +	src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
> +	src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> +	src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
> +	src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> +	src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> +
> +	/* Parameters pad */
> +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
> +	sink_fmt->width = 0;
> +	sink_fmt->height = 0;
> +	sink_fmt->field = V4L2_FIELD_NONE;
> +	sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
> +
> +	/* Statistics pad */
> +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
> +	src_fmt->width = 0;
> +	src_fmt->height = 0;
> +	src_fmt->field = V4L2_FIELD_NONE;
> +	src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
> +
> +	return c3_isp_core_init_routing(sd, state);
> +}
> +
> +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
> +	.enum_mbus_code = c3_isp_core_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = c3_isp_core_set_fmt,
> +	.enable_streams = c3_isp_core_enable_streams,
> +	.disable_streams = c3_isp_core_disable_streams,
> +	.set_routing = c3_isp_core_set_routing,
> +};
> +
> +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
> +	.pad = &c3_isp_core_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
> +	.init_state = c3_isp_core_init_state,
> +};
> +
> +static int c3_isp_core_link_validate(struct media_link *link)
> +{
> +	if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
> +		return 0;
> +
> +	return v4l2_subdev_link_validate(link);
> +}
> +
> +/* Media entity operations */
> +static const struct media_entity_operations c3_isp_core_entity_ops = {
> +	.link_validate = c3_isp_core_link_validate,
> +};
> +
> +int c3_isp_core_register(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_core *core = &isp->core;
> +	struct v4l2_subdev *sd = &core->sd;
> +	int ret;
> +
> +	v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
> +	sd->owner = THIS_MODULE;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	sd->internal_ops = &c3_isp_core_internal_ops;
> +	snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
> +
> +	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
> +	sd->entity.ops = &c3_isp_core_entity_ops;
> +
> +	core->isp = isp;
> +	sd->dev = isp->dev;
> +	v4l2_set_subdevdata(sd, core);
> +
> +	core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
> +	core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
> +	core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
> +	core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret)
> +		goto err_entity_cleanup;
> +
> +	ret = v4l2_device_register_subdev(&isp->v4l2_dev, sd);
> +	if (ret)
> +		goto err_subdev_cleanup;
> +
> +	return 0;
> +
> +err_subdev_cleanup:
> +	v4l2_subdev_cleanup(sd);
> +err_entity_cleanup:
> +	media_entity_cleanup(&sd->entity);
> +	return ret;
> +}
> +
> +void c3_isp_core_unregister(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_core *core = &isp->core;
> +	struct v4l2_subdev *sd = &core->sd;
> +
> +	v4l2_device_unregister_subdev(sd);
> +	v4l2_subdev_cleanup(sd);
> +	media_entity_cleanup(&sd->entity);
> +}
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
> new file mode 100644
> index 000000000000..a57b9f8dbc3c
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
> @@ -0,0 +1,486 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +
> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
> +{
> +	return readl(isp->base + reg);
> +}
> +
> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
> +{
> +	writel(val, isp->base + reg);
> +}
> +
> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
> +{
> +	u32 orig, tmp;
> +
> +	orig = c3_isp_read(isp, reg);
> +
> +	tmp = orig & ~mask;
> +	tmp |= val & mask;
> +
> +	if (tmp != orig)
> +		c3_isp_write(isp, reg, tmp);
> +}
> +
> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp)
> +{
> +	struct media_pipeline_entity_iter iter;
> +	unsigned int n_video_devices = 0;
> +	struct media_entity *entity;
> +	int ret;
> +
> +	ret = media_pipeline_entity_iter_init(&isp->pipe, &iter);
> +	if (ret)
> +		return ret;
> +
> +	media_pipeline_for_each_entity(&isp->pipe, &iter, entity) {
> +		if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
> +			n_video_devices++;
> +	}
> +
> +	media_pipeline_entity_iter_cleanup(&iter);
> +
> +	return n_video_devices == isp->pipe.start_count;
> +}

As suggested in the review of the -params.c module, I suggest to
implement start_streaming on the capture nodes only. From there you
can call enable_streams on the ISP subdevice. The ISP subdevice can
count how many links to resizers are enabled, and actually start its
operation when all the enabled ones have been started.

The idea is to use enabled media links to identify how many capture video
devices are expected to be used, and only start the ISP (and the
downstream subdevices like the CSI-2 Adap and RX and the sensor) when
all of linked ones have been started.

> +
> +/* PM runtime suspend */
> +static int __maybe_unused c3_isp_runtime_suspend(struct device *dev)
> +{
> +	struct c3_isp_device *isp = dev_get_drvdata(dev);
> +
> +	clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
> +
> +	return 0;
> +}
> +
> +/* PM runtime resume */
> +static int __maybe_unused c3_isp_runtime_resume(struct device *dev)
> +{
> +	struct c3_isp_device *isp = dev_get_drvdata(dev);
> +
> +	return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
> +}
> +
> +static const struct dev_pm_ops c3_isp_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> +				pm_runtime_force_resume)
> +	SET_RUNTIME_PM_OPS(c3_isp_runtime_suspend,
> +			   c3_isp_runtime_resume, NULL)
> +};
> +
> +/* IRQ handling */
> +static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
> +{
> +	struct c3_isp_device *isp = dev;
> +	u32 status;
> +
> +	/* Get irq status and clear irq status */
> +	status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
> +	c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
> +
> +	if (status & TOP_IRQ_FRAME_DONE) {
> +		c3_isp_stats_done(isp);
> +		c3_isp_params_done(isp);
> +		c3_isp_captures_done(isp);
> +		isp->frm_sequence++;
> +	}
> +
> +	if (status & TOP_IRQ_STATS_ERR)
> +		dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
> +
> +	return IRQ_HANDLED;
> +}
> +
> +/* Subdev notifier register */
> +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
> +			       struct v4l2_subdev *sd,
> +			       struct v4l2_async_connection *asc)
> +{
> +	struct c3_isp_device *isp =
> +		container_of(notifier, struct c3_isp_device, notifier);
> +	struct c3_isp_core *core = &isp->core;
> +	struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
> +	int ret;
> +
> +	ret = media_entity_get_fwnode_pad(&sd->entity,
> +					  sd->fwnode, MEDIA_PAD_FL_SOURCE);
> +	if (ret < 0) {
> +		dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
> +		return ret;
> +	}
> +
> +	core->src_sd = sd;
> +	core->src_sd_pad = ret;
> +
> +	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
> +					       MEDIA_LNK_FL_IMMUTABLE);
> +}
> +
> +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
> +{
> +	struct c3_isp_device *isp =
> +		container_of(notifier, struct c3_isp_device, notifier);
> +	int ret;
> +
> +	ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
> +	if (ret < 0) {
> +		dev_err(isp->dev,
> +			"Failed to register subdev nodes: %d\n", ret);
> +		return ret;
> +	}
> +
> +	dev_info(isp->dev, "notify complete\n");
> +
> +	return media_device_register(&isp->media_dev);
> +}
> +
> +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
> +{
> +	struct c3_isp_device *isp =
> +		container_of(asc->notifier, struct c3_isp_device, notifier);
> +
> +	media_device_unregister(&isp->media_dev);
> +}
> +
> +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
> +	.bound = c3_isp_notify_bound,
> +	.complete = c3_isp_notify_complete,
> +	.destroy = c3_isp_notify_destroy,
> +};
> +
> +static int c3_isp_async_nf_register(struct c3_isp_device *isp)
> +{
> +	struct v4l2_async_connection *asc;
> +	struct fwnode_handle *ep;
> +	int ret;
> +
> +	v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
> +
> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
> +					     FWNODE_GRAPH_ENDPOINT_NEXT);
> +	if (!ep)
> +		return -ENOTCONN;
> +
> +	asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
> +					      struct v4l2_async_connection);
> +	if (IS_ERR(asc)) {
> +		fwnode_handle_put(ep);
> +		return PTR_ERR(asc);
> +	}
> +
> +	fwnode_handle_put(ep);
> +
> +	isp->notifier.ops = &c3_isp_notify_ops;
> +	ret = v4l2_async_nf_register(&isp->notifier);
> +	if (ret)
> +		v4l2_async_nf_cleanup(&isp->notifier);
> +
> +	return ret;
> +}
> +
> +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
> +{
> +	v4l2_async_nf_unregister(&isp->notifier);
> +	v4l2_async_nf_cleanup(&isp->notifier);
> +}
> +
> +static int c3_isp_v4l2_register(struct c3_isp_device *isp)
> +{
> +	struct media_device *media_dev = &isp->media_dev;
> +	struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
> +	int ret;
> +
> +	/* Initialize media device */
> +	strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
> +		sizeof(media_dev->model));
> +	media_dev->dev = isp->dev;
> +
> +	media_device_init(media_dev);
> +
> +	/* Initialize v4l2 device */
> +	v4l2_dev->mdev = media_dev;
> +	strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
> +		sizeof(v4l2_dev->name));
> +
> +	ret = v4l2_device_register(isp->dev, v4l2_dev);
> +	if (ret) {
> +		media_device_cleanup(media_dev);
> +		dev_err(isp->dev,
> +			"Failed to register V4L2 device: %d\n", ret);
> +	}
> +
> +	return ret;
> +}
> +
> +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
> +{
> +	v4l2_device_unregister(&isp->v4l2_dev);
> +	media_device_cleanup(&isp->media_dev);
> +}
> +
> +static void c3_isp_remove_links(struct c3_isp_device *isp)
> +{
> +	unsigned int i;
> +
> +	media_entity_remove_links(&isp->core.sd.entity);
> +
> +	for (i = 0; i < C3_ISP_NUM_RSZ; i++)
> +		media_entity_remove_links(&isp->resizers[i].sd.entity);
> +
> +	for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
> +		media_entity_remove_links(&isp->caps[i].vdev.entity);
> +}
> +
> +static int c3_isp_create_links(struct c3_isp_device *isp)
> +{
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
> +		ret = media_create_pad_link(&isp->resizers[i].sd.entity,
> +					    C3_ISP_RESIZER_PAD_SOURCE,
> +					    &isp->resizers[i].cap->vdev.entity,
> +					    0, MEDIA_LNK_FL_ENABLED);

This could be made IMMUTABLE

> +		if (ret) {
> +			dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
> +			goto err_remove_links;
> +		}
> +
> +		ret = media_create_pad_link(&isp->core.sd.entity,
> +					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +					    &isp->resizers[i].sd.entity,
> +					    C3_ISP_RESIZER_PAD_SINK,
> +					    MEDIA_LNK_FL_ENABLED);
> +		if (ret) {
> +			dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
> +			goto err_remove_links;
> +		}
> +	}
> +
> +	ret = media_create_pad_link(&isp->core.sd.entity,
> +				    C3_ISP_CORE_PAD_SOURCE_STATS,
> +				    &isp->stats.vdev.entity,
> +				    0, MEDIA_LNK_FL_ENABLED);
> +	if (ret) {
> +		dev_err(isp->dev, "Failed to link core and stats\n");
> +		goto err_remove_links;
> +	}
> +
> +	ret = media_create_pad_link(&isp->params.vdev.entity, 0,
> +				    &isp->core.sd.entity,
> +				    C3_ISP_CORE_PAD_SINK_PARAMS,
> +				    MEDIA_LNK_FL_ENABLED);
> +	if (ret) {
> +		dev_err(isp->dev, "Failed to link params and core\n");
> +		goto err_remove_links;
> +	}
> +
> +	return 0;
> +
> +err_remove_links:
> +	c3_isp_remove_links(isp);
> +	return ret;
> +}
> +
> +static int c3_isp_videos_register(struct c3_isp_device *isp)
> +{
> +	int ret;
> +
> +	ret = c3_isp_captures_register(isp);
> +	if (ret)
> +		return ret;
> +
> +	ret = c3_isp_stats_register(isp);
> +	if (ret)
> +		goto err_captures_unregister;
> +
> +	ret = c3_isp_params_register(isp);
> +	if (ret)
> +		goto err_stats_unregister;
> +
> +	ret = c3_isp_create_links(isp);
> +	if (ret)
> +		goto err_params_unregister;
> +
> +	return 0;
> +
> +err_params_unregister:
> +	c3_isp_params_unregister(isp);
> +err_stats_unregister:
> +	c3_isp_stats_unregister(isp);
> +err_captures_unregister:
> +	c3_isp_captures_unregister(isp);
> +	return ret;
> +}
> +
> +static void c3_isp_videos_unregister(struct c3_isp_device *isp)
> +{
> +	c3_isp_remove_links(isp);
> +	c3_isp_params_unregister(isp);
> +	c3_isp_stats_unregister(isp);
> +	c3_isp_captures_unregister(isp);
> +}
> +
> +static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
> +{
> +	const struct c3_isp_info *info = isp->info;
> +	int ret;
> +	u32 i;
> +
> +	for (i = 0; i < info->clock_num; i++)
> +		isp->clks[i].id = info->clocks[i];
> +
> +	ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < info->clock_num; i++) {
> +		if (!info->clock_rates[i])
> +			continue;
> +		ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);
> +		if (ret) {
> +			dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
> +				info->clock_rates[i]);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct c3_isp_device *isp;
> +	int irq;
> +	int ret;
> +
> +	isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
> +	if (!isp)
> +		return -ENOMEM;
> +
> +	isp->info = of_device_get_match_data(dev);
> +	isp->dev = dev;
> +
> +	isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
> +	if (IS_ERR(isp->base))
> +		return dev_err_probe(dev, PTR_ERR(isp->base),
> +				     "Failed to ioremap resource\n");
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return irq;
> +
> +	ret = c3_isp_cfg_clocks(isp);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to configure clocks\n");
> +
> +	platform_set_drvdata(pdev, isp);
> +
> +	pm_runtime_enable(dev);
> +
> +	ret = c3_isp_v4l2_register(isp);
> +	if (ret)
> +		goto err_runtime_disable;
> +
> +	ret = c3_isp_core_register(isp);
> +	if (ret)
> +		goto err_v4l2_unregister;
> +
> +	ret = c3_isp_resizers_register(isp);
> +	if (ret)
> +		goto err_core_unregister;
> +
> +	ret = c3_isp_async_nf_register(isp);
> +	if (ret)
> +		goto err_resizers_unregister;
> +
> +	ret = c3_isp_videos_register(isp);
> +	if (ret)
> +		goto err_nf_unregister;
> +
> +	ret = devm_request_irq(dev, irq,
> +			       c3_isp_irq_handler, IRQF_SHARED,
> +			       dev_driver_string(dev), isp);
> +	if (ret)
> +		goto err_streams_unregister;

I would request the IRQ before registering devices to userspace.

> +
> +	mutex_init(&isp->lock);
> +
> +	return 0;
> +
> +err_streams_unregister:
> +	c3_isp_videos_unregister(isp);
> +err_nf_unregister:
> +	c3_isp_async_nf_unregister(isp);
> +err_resizers_unregister:
> +	c3_isp_resizers_unregister(isp);
> +err_core_unregister:
> +	c3_isp_core_unregister(isp);
> +err_v4l2_unregister:
> +	c3_isp_v4l2_unregister(isp);
> +err_runtime_disable:
> +	pm_runtime_disable(dev);
> +	return ret;
> +};
> +
> +static void c3_isp_remove(struct platform_device *pdev)
> +{
> +	struct c3_isp_device *isp = platform_get_drvdata(pdev);
> +
> +	mutex_destroy(&isp->lock);
> +	c3_isp_videos_unregister(isp);
> +	c3_isp_async_nf_unregister(isp);
> +	c3_isp_core_unregister(isp);
> +	c3_isp_resizers_unregister(isp);
> +	c3_isp_v4l2_unregister(isp);
> +	pm_runtime_disable(isp->dev);
> +};
> +
> +static const struct c3_isp_info isp_info = {
> +	.clocks = {"vapb", "isp0"},
> +	.clock_rates = {0, 400000000},
> +	.clock_num = 2
> +};
> +
> +static const struct of_device_id c3_isp_of_match[] = {
> +	{ .compatible = "amlogic,c3-isp",
> +	  .data = &isp_info },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, c3_isp_of_match);
> +
> +static struct platform_driver c3_isp_driver = {
> +	.probe = c3_isp_probe,
> +	.remove = c3_isp_remove,
> +	.driver = {
> +		.name = "c3-isp",
> +		.of_match_table = c3_isp_of_match,
> +		.pm = &c3_isp_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(c3_isp_driver);
> +
> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
> +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
> new file mode 100644
> index 000000000000..8a6b7ce86eaf
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
> @@ -0,0 +1,857 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-event.h>

Do you need these ?

> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>

See below about using the dma-contig vb2 ops

> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +#include "include/uapi/c3-isp-config.h"
> +
> +typedef void (*block_handler)(struct c3_isp_device *isp,
> +			      struct c3_isp_param_block_header *block);
> +
> +struct c3_isp_block_handler {
> +	size_t size;
> +	block_handler handler;
> +};
> +
> +/* Hardware configuration */
> +
> +static void c3_isp_params_cfg_wb_change(struct c3_isp_device *isp,
> +					struct c3_isp_param_block_header *block)
> +{
> +	struct wb_change_cfg *wb = (struct wb_change_cfg *)block;
> +
> +	if (!block->enabled) {
> +		c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
> +				   TOP_BEO_CTRL_WB_EN, false);
> +		return;
> +	}
> +
> +	c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
> +			   TOP_BEO_CTRL_WB_EN, TOP_BEO_CTRL_WB_EN);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, LSWB_WB_GAIN0_MASK,
> +			   wb->wb_gain[0] << LSWB_WB_GAIN0_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0,
> +			   LSWB_WB_GAIN1_MASK, wb->wb_gain[1]);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, LSWB_WB_GAIN2_MASK,
> +			   wb->wb_gain[2] << LSWB_WB_GAIN2_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1,
> +			   LSWB_WB_GAIN3_MASK, wb->wb_gain[3]);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN2,
> +			   LSWB_WB_GAIN4_MASK, wb->wb_gain[4]);
> +
> +	c3_isp_write(isp, ISP_LSWB_WB_LIMIT0,
> +		     LSWB_WB_LIMIT0(wb->wb_limit[0]) | LSWB_WB_LIMIT1(wb->wb_limit[1]));
> +
> +	c3_isp_write(isp, ISP_LSWB_WB_LIMIT1,
> +		     LSWB_WB_LIMIT2(wb->wb_limit[2]) | LSWB_WB_LIMIT3(wb->wb_limit[3]));
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_LIMIT2, LSWB_WB_LIMIT4_MASK,
> +			   wb->wb_limit[4]);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_BL12_GRBGI0_MASK,
> +			   wb->ae_bl12_grbgi[0]);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_GAIN_GRBGI0_MASK,
> +			   wb->ae_gain_grbgi[0] << AE_CRTL2_GAIN_GRBGI0_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_BL12_GRBGI1_MASK,
> +			   wb->ae_bl12_grbgi[1]);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_GAIN_GRBGI1_MASK,
> +			   wb->ae_gain_grbgi[1] << AE_CRTL2_GAIN_GRBGI1_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_BL12_GRBGI2_MASK,
> +			   wb->ae_bl12_grbgi[2]);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_GAIN_GRBGI2_MASK,
> +			   wb->ae_gain_grbgi[2] << AE_CRTL2_GAIN_GRBGI2_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_BL12_GRBGI3_MASK,
> +			   wb->ae_bl12_grbgi[3]);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_GAIN_GRBGI3_MASK,
> +			   wb->ae_gain_grbgi[3] << AE_CRTL2_GAIN_GRBGI3_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_BL12_GRBGI4_MASK,
> +			   wb->ae_bl12_grbgi[4]);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_GAIN_GRBGI4_MASK,
> +			   wb->ae_gain_grbgi[4] << AE_CRTL2_GAIN_GRBGI4_SHIFT);
> +}
> +
> +static void c3_isp_params_cfg_wb_luma(struct c3_isp_device *isp,
> +				      struct c3_isp_param_block_header *block)
> +{
> +	struct wb_luma_cfg *wb_luma = (struct wb_luma_cfg *)block;
> +
> +	if (!block->enabled)
> +		return;
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_0,
> +			   AWB_STAT_BLC20_GR_MASK, wb_luma->awb_stat_blc20[0]);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_1,
> +			   AWB_STAT_BLC20_R_MASK, wb_luma->awb_stat_blc20[1]);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_2,
> +			   AWB_STAT_BLC20_B_MASK, wb_luma->awb_stat_blc20[2]);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_3,
> +			   AWB_STAT_BLC20_GB_MASK, wb_luma->awb_stat_blc20[3]);
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_0,
> +			   AWB_STAT_GAIN10_GR_MASK, wb_luma->awb_stat_gain10[0]);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_1,
> +			   AWB_STAT_GAIN10_R_MASK, wb_luma->awb_stat_gain10[1]);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_2,
> +			   AWB_STAT_GAIN10_B_MASK, wb_luma->awb_stat_gain10[2]);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_3,
> +			   AWB_STAT_GAIN10_GB_MASK, wb_luma->awb_stat_gain10[3]);
> +
> +	c3_isp_write(isp, ISP_AWB_STAT_SATUR_CTRL,
> +		     AWB_STAT_SATUR_LOW(wb_luma->awb_stat_satur_low) |
> +		     AWB_STAT_SATUR_HIGH(wb_luma->awb_stat_satur_high));
> +}
> +
> +static void c3_isp_params_cfg_wb_triangle(struct c3_isp_device *isp,
> +					  struct c3_isp_param_block_header *block)
> +{
> +	struct wb_triangle_cfg *wb = (struct wb_triangle_cfg *)block;
> +
> +	if (!block->enabled)
> +		return;
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_CTRL2, AWB_STAT_SATUR_VALID_MASK,
> +			   wb->awb_stat_satur_vald);
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MIN_MASK,
> +			   wb->awb_stat_rg_min);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MAX_MASK,
> +			   wb->awb_stat_rg_max << AWB_STAT_RG_MAX_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MIN_MASK,
> +			   wb->awb_stat_bg_min);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MAX_MASK,
> +			   wb->awb_stat_bg_max << AWB_STAT_BG_MAX_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_LOW_MASK,
> +			   wb->awb_stat_rg_low);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_HIGH_MASK,
> +			   wb->awb_stat_rg_high << AWB_STAT_RG_HIGH_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_LOW_MASK,
> +			   wb->awb_stat_bg_low);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_HIGH_MASK,
> +			   wb->awb_stat_bg_high << AWB_STAT_BG_HIGH_SHIFT);
> +}
> +
> +static void c3_isp_params_cfg_awb_stats(struct c3_isp_device *isp,
> +					struct c3_isp_param_block_header *block)
> +{
> +	struct awb_stats_cfg *awb_stats = (struct awb_stats_cfg *)block;
> +	u32 *weight = awb_stats->awb_stat_blk_weight;
> +	int idx_base;
> +	int group;
> +	int i;
> +
> +	if (!block->enabled)
> +		return;
> +
> +	c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AWB_STAT_SWITCH_MASK,
> +			   awb_stats->awb_stat_switch << TOP_3A_AWB_STAT_SWITCH_SHIFT);
> +
> +	/* Calculate the group number */
> +	group = AWB_BLOCK_WT_NUM / AWB_BLK_WT_DATA_NUM_OF_GROUP;
> +
> +	/* Set the weight address to 0 position */
> +	c3_isp_write(isp, ISP_AWB_BLK_WT_ADDR, 0);
> +	for (i = 0; i < group; i++) {

you can now use
        for (unsigned int i = 0; ...)

if 'i' is not needed outside of the loop

> +		idx_base = i * AWB_BLK_WT_DATA_NUM_OF_GROUP;
> +		c3_isp_write(isp, ISP_AWB_BLK_WT_DATA,
> +			     AWB_BLK_WT_DATA0(weight[idx_base + 0]) |
> +			     AWB_BLK_WT_DATA1(weight[idx_base + 1]) |
> +			     AWB_BLK_WT_DATA2(weight[idx_base + 2]) |
> +			     AWB_BLK_WT_DATA3(weight[idx_base + 3]) |
> +			     AWB_BLK_WT_DATA4(weight[idx_base + 4]) |
> +			     AWB_BLK_WT_DATA5(weight[idx_base + 5]) |
> +			     AWB_BLK_WT_DATA6(weight[idx_base + 6]) |
> +			     AWB_BLK_WT_DATA7(weight[idx_base + 7]));
> +	}
> +}
> +
> +static void c3_isp_params_cfg_ae_stats(struct c3_isp_device *isp,
> +				       struct c3_isp_param_block_header *block)
> +{
> +	struct ae_stats_cfg *ae_stats = (struct ae_stats_cfg *)block;
> +	u32 *weight = ae_stats->ae_stat_blk_weight;
> +	int idx_base;
> +	int group;
> +	int i;
> +
> +	if (!block->enabled)
> +		return;
> +
> +	c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AE_STAT_SWITCH_MASK,
> +			   ae_stats->ae_stat_switch << TOP_3A_AE_STAT_SWITCH_SHIFT);
> +
> +	/* Calculate the group number */
> +	group = AE_BLOCK_WT_NUM / AE_BLK_WT_DATA_NUM_OF_GROUP;
> +
> +	/* Set the weight address to 0 position */
> +	c3_isp_write(isp, ISP_AE_BLK_WT_ADDR, 0);
> +	for (i = 0; i < group; i++) {
> +		idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
> +		c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
> +			     AE_BLK_WT_DATA0(weight[idx_base + 0]) |
> +			     AE_BLK_WT_DATA1(weight[idx_base + 1]) |
> +			     AE_BLK_WT_DATA2(weight[idx_base + 2]) |
> +			     AE_BLK_WT_DATA3(weight[idx_base + 3]) |
> +			     AE_BLK_WT_DATA4(weight[idx_base + 4]) |
> +			     AE_BLK_WT_DATA5(weight[idx_base + 5]) |
> +			     AE_BLK_WT_DATA6(weight[idx_base + 6]) |
> +			     AE_BLK_WT_DATA7(weight[idx_base + 7]));
> +	}
> +
> +	/* Write the last weight data */
> +	idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
> +	c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
> +		     AE_BLK_WT_DATA0(weight[idx_base + 0]) |
> +		     AE_BLK_WT_DATA1(weight[idx_base + 1]) |
> +		     AE_BLK_WT_DATA2(weight[idx_base + 2]) |
> +		     AE_BLK_WT_DATA3(weight[idx_base + 3]) |
> +		     AE_BLK_WT_DATA4(weight[idx_base + 4]) |
> +		     AE_BLK_WT_DATA5(weight[idx_base + 5]) |
> +		     AE_BLK_WT_DATA6(weight[idx_base + 6]));
> +}
> +
> +static void c3_isp_params_cfg_af_stats(struct c3_isp_device *isp,
> +				       struct c3_isp_param_block_header *block)
> +{
> +	struct af_stats_cfg *af_stats = (struct af_stats_cfg *)block;
> +
> +	if (!block->enabled)
> +		return;
> +
> +	c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AF_STAT_SWITCH_MASK,
> +			   af_stats->af_stat_switch << TOP_3A_AF_STAT_SWITCH_SHIFT);
> +}
> +
> +static void c3_isp_params_cfg_pst_gamma(struct c3_isp_device *isp,
> +					struct c3_isp_param_block_header *block)
> +{
> +	struct pst_gamma_cfg *gamma = (struct pst_gamma_cfg *)block;
> +	int idx_base;
> +	int i, j;
> +
> +	if (!block->enabled) {
> +		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, false);
> +		return;
> +	}
> +
> +	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, TOP_BED_GAMMA_EN);
> +
> +	for (j = 0; j < GAMMA_LUT_GROUP_NUM; j++) {
> +		c3_isp_write(isp, ISP_PST_GAMMA_LUT_ADDR, PST_GAMMA_LUT_ADDR(j));
> +
> +		/* Calculate the block number */
> +		for (i = 0; i < GAMMA_LUT_POINT_NUM / GAMMA_LUT_DATA_NUM_OF_GROUP; i++) {
> +			idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
> +			c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
> +				     PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]) |
> +				     PST_GAMMA_LUT_DATA1(gamma->pst_gamma_lut[j][idx_base + 1]));
> +		}
> +
> +		/* Write the last one lut data of group j */
> +		idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
> +		c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
> +			     PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]));
> +	}
> +}
> +
> +static void c3_isp_params_cfg_dmsc(struct c3_isp_device *isp,
> +				   struct c3_isp_param_block_header *block)
> +{
> +	if (!block->enabled) {
> +		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, false);
> +		return;
> +	}
> +
> +	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, TOP_BED_DMSC_EN);
> +}
> +
> +/* Configure 4 x 3 ccm matrix */
> +static void c3_isp_params_cfg_ccm(struct c3_isp_device *isp,
> +				  struct c3_isp_param_block_header *block)
> +{
> +	struct ccm_cfg *ccm = (struct ccm_cfg *)block;
> +
> +	if (!block->enabled) {
> +		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, false);
> +		return;
> +	}
> +
> +	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, TOP_BED_CCM_EN);
> +
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_00_MASK,
> +			   ccm->ccm_4x3matrix[0][0]);
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_01_MASK,
> +			   ccm->ccm_4x3matrix[0][1] << CCM_MTX_01_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_02_MASK,
> +			   ccm->ccm_4x3matrix[0][2]);
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_03_MASK,
> +			   ccm->ccm_4x3matrix[0][3] << CCM_MTX_03_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_10_MASK,
> +			   ccm->ccm_4x3matrix[1][0]);
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_11_MASK,
> +			   ccm->ccm_4x3matrix[1][1] << CCM_MTX_11_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_12_MASK,
> +			   ccm->ccm_4x3matrix[1][2]);
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_13_MASK,
> +			   ccm->ccm_4x3matrix[1][3] << CCM_MTX_13_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_20_MASK,
> +			   ccm->ccm_4x3matrix[2][0]);
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_21_MASK,
> +			   ccm->ccm_4x3matrix[2][1] << CCM_MTX_21_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_22_MASK,
> +			   ccm->ccm_4x3matrix[2][2]);
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_23_MASK,
> +			   ccm->ccm_4x3matrix[2][3] << CCM_MTX_23_SHIFT);
> +}
> +
> +/* Configure color space conversion matrix parameters */
> +static void c3_isp_params_cfg_csc(struct c3_isp_device *isp,
> +				  struct c3_isp_param_block_header *block)
> +{
> +	struct csc_cfg *csc = (struct csc_cfg *)block;
> +
> +	if (!block->enabled) {
> +		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, false);
> +		return;
> +	}
> +
> +	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, TOP_BED_CM0_EN);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST0_MASK,
> +			   csc->cm0_offset_inp[0]);
> +	c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST1_MASK,
> +			   csc->cm0_offset_inp[1] << CM0_INP_OFST1_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_INP_OFST2, CM0_INP_OFST2_MASK,
> +			   csc->cm0_offset_inp[2]);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_00_MASK,
> +			   csc->cm0_3x3matrix[0][0]);
> +	c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_01_MASK,
> +			   csc->cm0_3x3matrix[0][1] << CM0_MTX_01_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_02_MASK,
> +			   csc->cm0_3x3matrix[0][2]);
> +	c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_10_MASK,
> +			   csc->cm0_3x3matrix[1][0] << CM0_MTX_10_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_11_MASK,
> +			   csc->cm0_3x3matrix[1][1]);
> +	c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_12_MASK,
> +			   csc->cm0_3x3matrix[1][2] << CM0_MTX_12_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_20_MASK,
> +			   csc->cm0_3x3matrix[2][0]);
> +	c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_21_MASK,
> +			   csc->cm0_3x3matrix[2][1] << CM0_MTX_21_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_MTX_22_MASK,
> +			   csc->cm0_3x3matrix[2][2]);
> +	c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_OFST_OUP0_MASK,
> +			   csc->cm0_offset_oup[0] << CM0_OFST_OUP0_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP1_MASK,
> +			   csc->cm0_offset_oup[1]);
> +	c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP2_MASK,
> +			   csc->cm0_offset_oup[2] << CM0_OFST_OUP2_SHIFT);
> +	c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_MTX_RS_MASK,
> +			   csc->cm0_3x3mtrx_rs << CM0_MTX_RS_SHIFT);
> +}
> +
> +/* Set blc offset of each color channel */
> +static void c3_isp_params_cfg_blc(struct c3_isp_device *isp,
> +				  struct c3_isp_param_block_header *block)
> +{
> +	struct blc_cfg *blc = (struct blc_cfg *)block;
> +
> +	if (!block->enabled) {
> +		c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN, false);
> +		return;
> +	}
> +
> +	c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN,
> +			   TOP_BEO_CTRL_BLC_EN);
> +
> +	c3_isp_write(isp, ISP_FED_BL_OFST_GR, FED_BL_GR_OFST(blc->fe_bl_ofst[0]));
> +	c3_isp_write(isp, ISP_FED_BL_OFST_R, FED_BL_R_OFST(blc->fe_bl_ofst[1]));
> +	c3_isp_write(isp, ISP_FED_BL_OFST_B, FED_BL_B_OFST(blc->fe_bl_ofst[2]));
> +	c3_isp_write(isp, ISP_FED_BL_OFST_GB, FED_BL_GB_OFST(blc->fe_bl_ofst[3]));
> +	c3_isp_write(isp, ISP_FED_BL_OFST_IR, FED_BL_IR_OFST(blc->fe_bl_ofst[4]));
> +
> +	c3_isp_write(isp, ISP_LSWB_BLC_OFST0,
> +		     LSWB_BLC_OFST0(blc->blc_ofst[0]) | LSWB_BLC_OFST1(blc->blc_ofst[1]));
> +	c3_isp_write(isp, ISP_LSWB_BLC_OFST1,
> +		     LSWB_BLC_OFST2(blc->blc_ofst[2]) | LSWB_BLC_OFST3(blc->blc_ofst[3]));
> +	c3_isp_write(isp, ISP_LSWB_BLC_OFST2, LSWB_BLC_OFST4(blc->blc_ofst[4]));
> +}
> +
> +static const struct c3_isp_block_handler c3_isp_block_handlers[] = {
> +	[C3_ISP_PARAM_BLOCK_WB_CHANGE] = {
> +		.size = sizeof(struct wb_change_cfg),
> +		.handler = c3_isp_params_cfg_wb_change,
> +	},
> +	[C3_ISP_PARAM_BLOCK_WB_LUMA] = {
> +		.size = sizeof(struct wb_luma_cfg),
> +		.handler = c3_isp_params_cfg_wb_luma,
> +	},
> +	[C3_ISP_PARAM_BLOCK_WB_TRIANGLE] = {
> +		.size = sizeof(struct wb_triangle_cfg),
> +		.handler = c3_isp_params_cfg_wb_triangle,
> +	},
> +	[C3_ISP_PARAM_BLOCK_AWB_STATS] = {
> +		.size = sizeof(struct awb_stats_cfg),
> +		.handler = c3_isp_params_cfg_awb_stats,
> +	},
> +	[C3_ISP_PARAM_BLOCK_AE_STATS] = {
> +		.size = sizeof(struct ae_stats_cfg),
> +		.handler = c3_isp_params_cfg_ae_stats,
> +	},
> +	[C3_ISP_PARAM_BLOCK_AF_STATS] = {
> +		.size = sizeof(struct af_stats_cfg),
> +		.handler = c3_isp_params_cfg_af_stats,
> +	},
> +	[C3_ISP_PARAM_BLOCK_PST_GAMMA] = {
> +		.size = sizeof(struct pst_gamma_cfg),
> +		.handler = c3_isp_params_cfg_pst_gamma,
> +	},
> +	[C3_ISP_PARAM_BLOCK_DMSC] = {
> +		.size = sizeof(struct dmsc_cfg),
> +		.handler = c3_isp_params_cfg_dmsc,
> +	},
> +	[C3_ISP_PARAM_BLOCK_CCM] = {
> +		.size = sizeof(struct ccm_cfg),
> +		.handler = c3_isp_params_cfg_ccm,
> +	},
> +	[C3_ISP_PARAM_BLOCK_CSC] = {
> +		.size = sizeof(struct csc_cfg),
> +		.handler = c3_isp_params_cfg_csc,
> +	},
> +	[C3_ISP_PARAM_BLOCK_BLC] = {
> +		.size = sizeof(struct blc_cfg),
> +		.handler = c3_isp_params_cfg_blc,
> +	},
> +};
> +
> +static enum vb2_buffer_state
> +c3_isp_params_cfg_blocks(struct c3_isp_params *params)
> +{
> +	struct c3_isp_params_buffer *config = params->buff->vaddr;
> +	enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
> +	size_t block_offset = 0;
> +	size_t max_offset = 0;
> +
> +	if (config->total_size > C3_ISP_PARAMS_MAX_SIZE) {
> +		dev_dbg(params->isp->dev, "Invalid parameters buffer size %lu\n",
> +			config->total_size);
> +		state = VB2_BUF_STATE_ERROR;
> +		goto err_return_state;
> +	}

I suggest to move validation of the parameters buffer to .buf_prepare
time.

This function is called in irq context, and it's better to do all
validation check at buffer queuing time instead.

You can have a look at the rkisp1-params.c module, where
rkisp1_params_prepare_ext_params does the validation in the
.buf_prepare call path

> +
> +	/* Ensure config->data has a full struct c3_isp_param_block_header */
> +	max_offset = config->total_size - sizeof(struct c3_isp_param_block_header);
> +
> +	while (block_offset <= max_offset) {
> +		const struct c3_isp_block_handler *block_handler;
> +		struct c3_isp_param_block_header *block;
> +
> +		block = (struct c3_isp_param_block_header *)
> +			 &config->data[block_offset];
> +
> +		if (block->type >= C3_ISP_PARAM_BLOCK_SENTINEL) {
> +			dev_dbg(params->isp->dev, "Invalid parameters block type\n");
> +			state = VB2_BUF_STATE_ERROR;
> +			goto err_return_state;
> +		}
> +
> +		block_handler = &c3_isp_block_handlers[block->type];
> +		if (block->size != block_handler->size) {
> +			dev_dbg(params->isp->dev, "Invalid parameters block size\n");
> +			state = VB2_BUF_STATE_ERROR;
> +			goto err_return_state;
> +		}
> +
> +		block_handler->handler(params->isp, block);
> +
> +		block_offset += block->size;
> +	}
> +
> +err_return_state:
> +	return state;
> +}
> +
> +/* Initialize ISP pipeline */
> +static int c3_isp_params_start(struct c3_isp_params *params)
> +{
> +	enum vb2_buffer_state state;
> +	unsigned long flags;
> +
> +	/* Reset these controllers */
> +	c3_isp_write(params->isp, ISP_TOP_FEO_CTRL0, TOP_FEO_CTRL0_ALL_DIS);
> +	c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_0, TOP_FEO_CTRL1_0_ALL_DIS);
> +	c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_1, TOP_FEO_CTRL1_1_ALL_DIS);
> +	c3_isp_write(params->isp, ISP_TOP_FED_CTRL, TOP_FED_CTRL_ALL_DIS);
> +	c3_isp_write(params->isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_ALL_DIS);
> +	c3_isp_write(params->isp, ISP_TOP_BED_CTRL, TOP_BED_CTRL_ALL_DIS);
> +
> +	spin_lock_irqsave(&params->buff_lock, flags);
> +
> +	/* Only use the first buffer to initialize ISP */
> +	params->buff = list_first_entry_or_null(&params->pending,
> +						struct c3_isp_vb2_buffer, list);
> +	if (!params->buff) {
> +		spin_unlock_irqrestore(&params->buff_lock, flags);
> +		return -EINVAL;
> +	}
> +
> +	state = c3_isp_params_cfg_blocks(params);
> +
> +	spin_unlock_irqrestore(&params->buff_lock, flags);
> +
> +	return 0;
> +}
> +
> +/* V4L2 video operations */
> +
> +static void c3_isp_params_return_buffers(struct c3_isp_params *params,
> +					 enum vb2_buffer_state state)
> +{
> +	unsigned long flags;
> +	struct c3_isp_vb2_buffer *buff;
> +
> +	spin_lock_irqsave(&params->buff_lock, flags);
> +
> +	while (!list_empty(&params->pending)) {
> +		buff = list_first_entry(&params->pending,
> +					struct c3_isp_vb2_buffer, list);
> +		list_del(&buff->list);
> +		vb2_buffer_done(&buff->vb.vb2_buf, state);
> +	}
> +
> +	spin_unlock_irqrestore(&params->buff_lock, flags);
> +}
> +
> +static int c3_isp_params_querycap(struct file *file, void *fh,
> +				  struct v4l2_capability *cap)
> +{
> +	strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
> +	strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
> +
> +	return 0;
> +}
> +
> +static int c3_isp_params_enum_fmt(struct file *file, void *fh,
> +				  struct v4l2_fmtdesc *f)
> +{
> +	if (f->index)
> +		return -EINVAL;
> +
> +	f->pixelformat = V4L2_META_FMT_C3ISP_PARAMS;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_params_g_fmt(struct file *file, void *fh,
> +			       struct v4l2_format *f)
> +{
> +	struct c3_isp_params *params = video_drvdata(file);
> +
> +	f->fmt.meta = params->vfmt.fmt.meta;
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops isp_params_v4l2_ioctl_ops = {
> +	.vidioc_querycap                = c3_isp_params_querycap,
> +	.vidioc_enum_fmt_meta_out       = c3_isp_params_enum_fmt,
> +	.vidioc_g_fmt_meta_out          = c3_isp_params_g_fmt,
> +	.vidioc_s_fmt_meta_out          = c3_isp_params_g_fmt,
> +	.vidioc_try_fmt_meta_out        = c3_isp_params_g_fmt,
> +	.vidioc_reqbufs                 = vb2_ioctl_reqbufs,
> +	.vidioc_querybuf                = vb2_ioctl_querybuf,
> +	.vidioc_qbuf                    = vb2_ioctl_qbuf,
> +	.vidioc_expbuf                  = vb2_ioctl_expbuf,
> +	.vidioc_dqbuf                   = vb2_ioctl_dqbuf,
> +	.vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
> +	.vidioc_create_bufs             = vb2_ioctl_create_bufs,
> +	.vidioc_streamon                = vb2_ioctl_streamon,
> +	.vidioc_streamoff               = vb2_ioctl_streamoff,
> +	.vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
> +	.vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
> +};
> +
> +static const struct v4l2_file_operations isp_params_v4l2_fops = {
> +	.open = v4l2_fh_open,
> +	.release = vb2_fop_release,
> +	.poll = vb2_fop_poll,
> +	.unlocked_ioctl = video_ioctl2,
> +	.mmap = vb2_fop_mmap,
> +};
> +
> +static int c3_isp_params_vb2_queue_setup(struct vb2_queue *q,
> +					 unsigned int *num_buffers,
> +					 unsigned int *num_planes,
> +					 unsigned int sizes[],
> +					 struct device *alloc_devs[])
> +{
> +	if (*num_planes) {
> +		if (*num_planes != 1)
> +			return -EINVAL;
> +
> +		if (sizes[0] < sizeof(struct c3_isp_params_buffer))
> +			return -EINVAL;
> +	} else {
> +		*num_planes = 1;
> +		sizes[0] = sizeof(struct c3_isp_params_buffer);
> +	}
> +
> +	return 0;
> +}
> +
> +static void c3_isp_params_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_vb2_buffer *buf =
> +			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> +	struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&params->buff_lock, flags);
> +
> +	list_add_tail(&buf->list, &params->pending);
> +
> +	spin_unlock_irqrestore(&params->buff_lock, flags);
> +}
> +
> +static int c3_isp_params_vb2_buf_prepare(struct vb2_buffer *vb)
> +{
> +	struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned int size = params->vfmt.fmt.meta.buffersize;
> +
> +	if (vb2_plane_size(vb, 0) < size) {

How does this work ?

'size' is
	params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);

if I got this right

Now, as you're using an extensible parameters implementation,
userspace is allowed to submit buffers of a smaller size, with only
the "interesting" blocks there.

This check instead makes sure that userspace always fill the
paramteres buffer with all blocks, am I wrong ?

This defeates the purpose of extensible formats, where instead
userspace is allowed to only fill the buffers with a subset of the
configuration blocks.

Have I missed something ?

> +		dev_err(params->isp->dev,
> +			"User buffer too small (%ld < %u)\n",
> +			vb2_plane_size(vb, 0), size);
> +		return -EINVAL;
> +	}
> +
> +	vb2_set_plane_payload(vb, 0, size);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_params_vb2_buf_init(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_vb2_buffer *buf =
> +		container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> +	struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
> +
> +	buf->vaddr = vb2_plane_vaddr(vb, 0);
> +	buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);

This is not used

> +
> +	memset(buf->vaddr, 0, params->vfmt.fmt.meta.buffersize);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_params_vb2_start_streaming(struct vb2_queue *q,
> +					     unsigned int count)
> +{
> +	struct c3_isp_params *params = vb2_get_drv_priv(q);
> +	int ret;
> +
> +	guard(mutex)(&params->isp->lock);
> +
> +	ret = pm_runtime_resume_and_get(params->isp->dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = video_device_pipeline_start(&params->vdev, &params->isp->pipe);
> +	if (ret) {
> +		dev_err(params->isp->dev,
> +			"Failed to start params pipeline: %d\n", ret);
> +		goto err_pm_put;
> +	}
> +
> +	if (c3_isp_pipeline_ready(params->isp)) {

I understand this counts how many video devices have been started...


> +		ret = v4l2_subdev_enable_streams(&params->isp->core.sd,
> +						 C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +						 BIT(0));

... but does it need to be called by this module ? it's not like there is a
parameters stream to be enabled in the ISP (see also the suggestion to
drop routing support from there).


> +		if (ret)
> +			goto err_pipeline_stop;
> +	}
> +
> +	c3_isp_params_start(params);

Does this perform initialization and apply the first parameters from a
queued buffer ?

I'm wondering if it wouldn't be better to:

- Implement a start_streaming operation on capture nodes only
- Call the ISP's enable_streams
- The ISP driver counts how many enabled links to resizers are there
- If the number of enable_streams calls matches the number of enabled
  links:
  - call c3_isp_params_start() and any function that does the stats or
    params setup
  - actually start the ISP
  - propagate the enable_streams call to the downstream subdevs (Adap,
    then csi2-rx then sensor)

> +
> +	return 0;
> +
> +err_pipeline_stop:
> +	video_device_pipeline_stop(&params->vdev);
> +err_pm_put:
> +	pm_runtime_put(params->isp->dev);
> +	c3_isp_params_return_buffers(params, VB2_BUF_STATE_QUEUED);
> +	return ret;
> +}
> +
> +static void c3_isp_params_vb2_stop_streaming(struct vb2_queue *q)
> +{
> +	struct c3_isp_params *params = vb2_get_drv_priv(q);
> +
> +	guard(mutex)(&params->isp->lock);
> +
> +	c3_isp_params_return_buffers(params, VB2_BUF_STATE_ERROR);

Even if you don't implement start_streaming as I've suggested, you
will need this one to return buffers to userspace.

> +
> +	if (params->isp->pipe.start_count == 1)
> +		v4l2_subdev_disable_streams(&params->isp->core.sd,
> +					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +					    BIT(0));
> +
> +	video_device_pipeline_stop(&params->vdev);
> +	pm_runtime_put(params->isp->dev);
> +}
> +
> +static const struct vb2_ops isp_params_vb2_ops = {
> +	.queue_setup = c3_isp_params_vb2_queue_setup,
> +	.buf_queue = c3_isp_params_vb2_buf_queue,
> +	.buf_prepare = c3_isp_params_vb2_buf_prepare,
> +	.buf_init = c3_isp_params_vb2_buf_init,
> +	.wait_prepare = vb2_ops_wait_prepare,
> +	.wait_finish = vb2_ops_wait_finish,
> +	.start_streaming = c3_isp_params_vb2_start_streaming,
> +	.stop_streaming = c3_isp_params_vb2_stop_streaming,
> +};
> +
> +int c3_isp_params_register(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_params *params = &isp->params;
> +	struct video_device *vdev = &params->vdev;
> +	struct vb2_queue *vb2_q = &params->vb2_q;
> +	int ret;
> +
> +	memset(params, 0, sizeof(*params));
> +	params->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_PARAMS;
> +	params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
> +	params->isp = isp;
> +	INIT_LIST_HEAD(&params->pending);
> +	spin_lock_init(&params->buff_lock);
> +	mutex_init(&params->lock);
> +
> +	snprintf(vdev->name, sizeof(vdev->name), "isp-params");

Here and in all other names, I would prefix them with "c3-"

> +	vdev->fops = &isp_params_v4l2_fops;
> +	vdev->ioctl_ops = &isp_params_v4l2_ioctl_ops;
> +	vdev->v4l2_dev = &isp->v4l2_dev;
> +	vdev->lock = &params->lock;
> +	vdev->minor = -1;
> +	vdev->queue = vb2_q;
> +	vdev->release = video_device_release_empty;
> +	vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING;
> +	vdev->vfl_dir = VFL_DIR_TX;
> +	video_set_drvdata(vdev, params);
> +
> +	vb2_q->drv_priv = params;
> +	vb2_q->mem_ops = &vb2_dma_contig_memops;

Do you need to use DMA ?

This implementation seems to rather receive a buffer of parameters,
inspect its content and write registers according to values there.

I don't see direct DMA transfers to a memory mapped register area, but
instead single writes to registers.

Should you use vb2_vmalloc_memops instead ?
Make sure to change the headers inclusions and Kconfig dependencies
accordingly.

> +	vb2_q->ops = &isp_params_vb2_ops;
> +	vb2_q->type = V4L2_BUF_TYPE_META_OUTPUT;
> +	vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
> +	vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
> +	vb2_q->dev = isp->dev;
> +	vb2_q->lock = &params->lock;
> +	vb2_q->min_queued_buffers = 1;
> +
> +	ret = vb2_queue_init(vb2_q);
> +	if (ret)
> +		goto err_detroy;
> +
> +	params->pad.flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&vdev->entity, 1, &params->pad);
> +	if (ret)
> +		goto err_queue_release;
> +
> +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +	if (ret < 0) {
> +		dev_err(isp->dev,
> +			"Failed to register %s: %d\n", vdev->name, ret);
> +		goto err_entity_cleanup;
> +	}
> +
> +	return 0;
> +
> +err_entity_cleanup:
> +	media_entity_cleanup(&vdev->entity);
> +err_queue_release:
> +	vb2_queue_release(vb2_q);
> +err_detroy:
> +	mutex_destroy(&params->lock);
> +	return ret;
> +}
> +
> +void c3_isp_params_unregister(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_params *params = &isp->params;
> +
> +	vb2_queue_release(&params->vb2_q);
> +	media_entity_cleanup(&params->vdev.entity);
> +	video_unregister_device(&params->vdev);
> +	mutex_destroy(&params->lock);
> +}
> +
> +int c3_isp_params_done(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_params *params = &isp->params;
> +	enum vb2_buffer_state state;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&params->buff_lock, flags);
> +
> +	params->buff = list_first_entry_or_null(&params->pending,
> +						struct c3_isp_vb2_buffer, list);
> +	if (!params->buff) {
> +		spin_unlock_irqrestore(&params->buff_lock, flags);
> +		return -EINVAL;
> +	}
> +
> +	list_del(&params->buff->list);
> +
> +	state = c3_isp_params_cfg_blocks(params);
> +
> +	params->buff->vb.sequence = params->isp->frm_sequence;
> +	params->buff->vb.vb2_buf.timestamp = ktime_get();
> +	params->buff->vb.field = V4L2_FIELD_NONE;
> +	vb2_buffer_done(&params->buff->vb.vb2_buf, state);
> +
> +	spin_unlock_irqrestore(&params->buff_lock, flags);
> +
> +	return 0;
> +}
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
> new file mode 100644
> index 000000000000..de1938f7c354
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
> @@ -0,0 +1,683 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#ifndef __C3_ISP_REGS_H__
> +#define __C3_ISP_REGS_H__
> +
> +#define ISP_TOP_INPUT_SIZE                       0x0000
> +#define TOP_INPUT_HSIZE(x)                       (((x) & 0xffff) << 16)
> +#define TOP_INPUT_VSIZE(x)                       ((x) & 0xffff)
> +
> +#define ISP_TOP_FRM_SIZE                         0x0004
> +#define TOP_FRM_CORE_IHSIZE(x)                   (((x) & 0xffff) << 16)
> +#define TOP_FRM_CORE_IVSIZE(x)                   ((x) & 0xffff)
> +
> +#define ISP_TOP_HOLD_SIZE                        0x0008
> +#define TOP_HOLD_HSIZE_MASK                      GENMASK(31, 16)
> +#define TOP_HOLD_HSIZE_SHIFT                     16
> +
> +#define ISP_TOP_PATH_EN                          0x0010
> +#define TOP_DISP_EN(x)                           BIT((x))
> +#define TOP_WRMIF_EN(x)                          BIT((x) + 8)
> +
> +#define ISP_TOP_PATH_SEL                         0x0014
> +#define TOP_DATA_PATH_MASK                       GENMASK(18, 16)
> +#define TOP_PATH_MIPI_TO_CORE                    BIT(16)
> +
> +#define ISP_TOP_IRQ_EN                           0x0080
> +#define TOP_IRQ_FRAME_DONE                       BIT(0)
> +#define TOP_IRQ_STATS_ERR                        BIT(5)
> +
> +#define ISP_TOP_IRQ_CLR                          0x0084
> +#define ISP_TOP_IRQ_LINE_THRD                    0x0088
> +#define TOP_IRQ_DIN_HSYNC                        BIT(16)
> +
> +#define ISP_TOP_RO_IRQ_STAT                      0x01c4
> +#define ISP_TOP_MODE_CTRL                        0x0400
> +#define ISP_TOP_FEO_CTRL0                        0x040c
> +#define TOP_FEO_CTRL0_ALL_DIS                    0
> +#define TOP_FEO_INPUT_FMT_EN                     BIT(8)
> +
> +#define ISP_TOP_FEO_CTRL1_0                      0x0410
> +#define TOP_FEO_CTRL1_0_ALL_DIS                  0
> +
> +#define ISP_TOP_FEO_CTRL1_1                      0x0414
> +#define TOP_FEO_CTRL1_1_ALL_DIS                  0
> +
> +#define ISP_TOP_FED_CTRL                         0x0418
> +#define TOP_FED_CTRL_ALL_DIS                     0
> +
> +#define ISP_TOP_BEO_CTRL                         0x041c
> +#define TOP_BEO_CTRL_ALL_DIS                     0
> +#define TOP_BEO_CTRL_GTM_EN                      BIT(2)
> +#define TOP_BEO_CTRL_WB_EN                       BIT(6)
> +#define TOP_BEO_CTRL_BLC_EN                      BIT(7)
> +#define TOP_BEO_CTRL_IDGAIN_EN                   BIT(8)
> +#define TOP_BEO_CTRL_EOTF_EN                     BIT(9)
> +
> +#define ISP_TOP_BED_CTRL                         0x0420
> +#define TOP_BED_CTRL_ALL_DIS                     0
> +#define TOP_BED_CM0_EN                           BIT(14)
> +#define TOP_BED_GAMMA_EN                         BIT(16)
> +#define TOP_BED_CCM_EN                           BIT(18)
> +#define TOP_BED_DMSC_EN                          BIT(19)
> +
> +#define ISP_TOP_3A_STAT_CRTL                     0x0424
> +#define TOP_3A_AE_STAT_EN                        BIT(0)
> +#define TOP_3A_AWB_STAT_EN                       BIT(1)
> +#define TOP_3A_AF_STAT_EN                        BIT(2)
> +#define TOP_3A_AWB_STAT_SWITCH_MASK              GENMASK(6, 4)
> +#define TOP_3A_AWB_STAT_SWITCH_SHIFT             4
> +#define TOP_3A_AWB_STAT_SWITCH_BEFORE_WB         2
> +
> +#define TOP_3A_AE_STAT_SWITCH_MASK               GENMASK(9, 8)
> +#define TOP_3A_AE_STAT_SWITCH_SHIFT              8
> +#define TOP_3A_AE_STAT_SWITCH_FROM_MLS           1
> +#define TOP_3A_AF_STAT_SWITCH_MASK               GENMASK(13, 12)
> +#define TOP_3A_AF_STAT_SWITCH_SHIFT              12
> +#define TOP_3A_AF_STAT_SWITCH_FROM_SNR            0
> +
> +#define ISP_FED_BL_OFST_GR                       0x2018
> +#define FED_BL_GR_OFST(x)                        ((x) & 0x1fffff)
> +
> +#define ISP_FED_BL_OFST_R                        0x201c
> +#define FED_BL_R_OFST(x)                         ((x) & 0x1fffff)
> +
> +#define ISP_FED_BL_OFST_B                        0x2020
> +#define FED_BL_B_OFST(x)                         ((x) & 0x1fffff)
> +
> +#define ISP_FED_BL_OFST_GB                       0x2024
> +#define FED_BL_GB_OFST(x)                        ((x) & 0x1fffff)
> +
> +#define ISP_FED_BL_OFST_IR                       0x2028
> +#define FED_BL_IR_OFST(x)                        ((x) & 0x1fffff)
> +
> +#define ISP_LSWB_BLC_OFST0                       0x4028
> +#define LSWB_BLC_OFST1_MASK                      GENMASK(15, 0)
> +#define LSWB_BLC_OFST1(x)                        ((x) & 0xffff)
> +#define LSWB_BLC_OFST1_EXT(x)                    ((x) & 0xffff)
> +#define LSWB_BLC_OFST0_MASK                      GENMASK(31, 16)
> +#define LSWB_BLC_OFST0(x)                        (((x) & 0xffff) << 16)
> +#define LSWB_BLC_OFST0_EXT(x)                    (((x) >> 16) & 0xffff)
> +
> +#define ISP_LSWB_BLC_OFST1                       0x402c
> +#define LSWB_BLC_OFST3_MASK                      GENMASK(15, 0)
> +#define LSWB_BLC_OFST3(x)                        ((x) & 0xffff)
> +#define LSWB_BLC_OFST3_EXT(x)                    ((x) & 0xffff)
> +#define LSWB_BLC_OFST2_MASK                      GENMASK(31, 16)
> +#define LSWB_BLC_OFST2(x)                        (((x) & 0xffff) << 16)
> +#define LSWB_BLC_OFST2_EXT(x)                    (((x) >> 16) & 0xffff)
> +
> +#define ISP_LSWB_BLC_OFST2                       0x4030
> +#define LSWB_BLC_OFST4_MASK                      GENMASK(15, 0)
> +#define LSWB_BLC_OFST4(x)                        ((x) & 0xffff)
> +#define LSWB_BLC_OFST4_EXT(x)                    ((x) & 0xffff)
> +
> +#define ISP_LSWB_BLC_PHSOFST                     0x4034
> +#define LSWB_BLC_YPHS_OFST_MASK                  GENMASK(1, 0)
> +#define LSWB_BLC_XPHS_OFST_MASK                  GENMASK(3, 2)
> +#define LSWB_BLC_XPHS_OFST_SHIFT                 2
> +
> +#define ISP_LSWB_WB_GAIN0                        0x4038
> +#define LSWB_WB_GAIN1_MASK                       GENMASK(11, 0)
> +#define LSWB_WB_GAIN0_MASK                       GENMASK(27, 16)
> +#define LSWB_WB_GAIN0_SHIFT                      16
> +
> +#define ISP_LSWB_WB_GAIN1                        0x403c
> +#define LSWB_WB_GAIN3_MASK                       GENMASK(11, 0)
> +#define LSWB_WB_GAIN2_MASK                       GENMASK(27, 16)
> +#define LSWB_WB_GAIN2_SHIFT                      16
> +
> +#define ISP_LSWB_WB_GAIN2                        0x4040
> +#define LSWB_WB_GAIN4_MASK                       GENMASK(11, 0)
> +
> +#define ISP_LSWB_WB_LIMIT0                       0x4044
> +#define LSWB_WB_LIMIT1(x)                        ((x) & 0xffff)
> +#define LSWB_WB_LIMIT0(x)                        (((x) & 0xffff) << 16)
> +
> +#define ISP_LSWB_WB_LIMIT1                       0x4048
> +#define LSWB_WB_LIMIT3(x)                        ((x) & 0xffff)
> +#define LSWB_WB_LIMIT2(x)                        (((x) & 0xffff) << 16)
> +
> +#define ISP_LSWB_WB_LIMIT2                       0x404c
> +#define LSWB_WB_LIMIT4_MASK                      GENMASK(15, 0)
> +
> +#define ISP_LSWB_WB_PHSOFST                      0x4050
> +#define LSWB_WB_YPHS_OFST_MASK                   GENMASK(1, 0)
> +#define LSWB_WB_XPHS_OFST_MASK                   GENMASK(3, 2)
> +#define LSWB_WB_XPHS_OFST_SHIFT                  2
> +
> +#define ISP_LSWB_LNS_PHSOFST                     0x4054
> +#define LSWB_LNS_YPHS_OFST_MASK                  GENMASK(1, 0)
> +#define LSWB_LNS_XPHS_OFST_MASK                  GENMASK(3, 2)
> +#define LSWB_LNS_XPHS_OFST_SHIFT                 2
> +
> +#define ISP_DMS_COMMON_PARAM0                    0x5000
> +#define DMS_COMMON_YPHS_OFST_MASK                GENMASK(1, 0)
> +#define DMS_COMMON_XPHS_OFST_MASK                GENMASK(3, 2)
> +#define DMS_COMMON_XPHS_OFST_SHIFT               2
> +
> +#define ISP_CM0_INP_OFST01                       0x6040
> +#define CM0_INP_OFST0_MASK                       GENMASK(12, 0)
> +#define CM0_INP_OFST1_MASK                       GENMASK(28, 16)
> +#define CM0_INP_OFST1_SHIFT                      16
> +
> +#define ISP_CM0_INP_OFST2                        0x6044
> +#define CM0_INP_OFST2_MASK                       GENMASK(12, 0)
> +
> +#define ISP_CM0_COEF00_01                        0x6048
> +#define CM0_MTX_00_MASK                          GENMASK(12, 0)
> +#define CM0_MTX_01_MASK                          GENMASK(28, 16)
> +#define CM0_MTX_01_SHIFT                         16
> +
> +#define ISP_CM0_COEF02_10                        0x604c
> +#define CM0_MTX_02_MASK                          GENMASK(12, 0)
> +#define CM0_MTX_10_MASK                          GENMASK(28, 16)
> +#define CM0_MTX_10_SHIFT                         16
> +
> +#define ISP_CM0_COEF11_12                        0x6050
> +#define CM0_MTX_11_MASK                          GENMASK(12, 0)
> +#define CM0_MTX_12_MASK                          GENMASK(28, 16)
> +#define CM0_MTX_12_SHIFT                         16
> +
> +#define ISP_CM0_COEF20_21                        0x6054
> +#define CM0_MTX_20_MASK                          GENMASK(12, 0)
> +#define CM0_MTX_21_MASK                          GENMASK(28, 16)
> +#define CM0_MTX_21_SHIFT                         16
> +
> +#define ISP_CM0_COEF22_OUP_OFST0                 0x6058
> +#define CM0_MTX_22_MASK                          GENMASK(12, 0)
> +#define CM0_OFST_OUP0_MASK                       GENMASK(28, 16)
> +#define CM0_OFST_OUP0_SHIFT                      16
> +
> +#define ISP_CM0_OUP_OFST12_RS                    0x605c
> +#define CM0_OFST_OUP1_MASK                       GENMASK(12, 0)
> +#define CM0_OFST_OUP2_MASK                       GENMASK(28, 16)
> +#define CM0_OFST_OUP2_SHIFT                      16
> +#define CM0_MTX_RS_MASK                          GENMASK(31, 30)
> +#define CM0_MTX_RS_SHIFT                         30
> +
> +#define ISP_CCM_MTX_00_01                        0x6098
> +#define CCM_MTX_00_MASK                          GENMASK(12, 0)
> +#define CCM_MTX_01_MASK                          GENMASK(28, 16)
> +#define CCM_MTX_01_SHIFT                         16
> +
> +#define ISP_CCM_MTX_02_03                        0x609c
> +#define CCM_MTX_02_MASK                          GENMASK(12, 0)
> +#define CCM_MTX_03_MASK                          GENMASK(28, 16)
> +#define CCM_MTX_03_SHIFT                         16
> +
> +#define ISP_CCM_MTX_10_11                        0x60A0
> +#define CCM_MTX_10_MASK                          GENMASK(12, 0)
> +#define CCM_MTX_11_MASK                          GENMASK(28, 16)
> +#define CCM_MTX_11_SHIFT                         16
> +
> +#define ISP_CCM_MTX_12_13                        0x60A4
> +#define CCM_MTX_12_MASK                          GENMASK(12, 0)
> +#define CCM_MTX_13_MASK                          GENMASK(28, 16)
> +#define CCM_MTX_13_SHIFT                         16
> +
> +#define ISP_CCM_MTX_20_21                        0x60A8
> +#define CCM_MTX_20_MASK                          GENMASK(12, 0)
> +#define CCM_MTX_21_MASK                          GENMASK(28, 16)
> +#define CCM_MTX_21_SHIFT                         16
> +
> +#define ISP_CCM_MTX_22_23_RS                     0x60Ac
> +#define CCM_MTX_22_MASK                          GENMASK(12, 0)
> +#define CCM_MTX_23_MASK                          GENMASK(28, 16)
> +#define CCM_MTX_23_SHIFT                         16
> +
> +#define ISP_PST_GAMMA_MODE                       0x60C0
> +#define PST_GAMMA_MODE                           BIT(0)
> +#define PST_GAMMA_MODE_EXT(x)                    ((x) & 0x1)
> +
> +#define ISP_PST_GAMMA_LUT_ADDR                   0x60cc
> +#define PST_GAMMA_LUT_ADDR(x)                    ((x) << 7)
> +
> +#define ISP_PST_GAMMA_LUT_DATA                   0x60d0
> +#define PST_GAMMA_LUT_DATA0(x)                   ((x) & 0xffff)
> +#define PST_GAMMA_LUT_DATA1(x)                   (((x) & 0xffff) << 16)
> +
> +#define DISP0_TOP_TOP_CTRL                       0x8000
> +#define DISP_CRP2_EN                             BIT(5)
> +
> +#define DISP0_TOP_CRP2_START                     0x8004
> +#define DISP_CRP2_VSTART_MASK                    GENMASK(15, 0)
> +#define DISP_CRP2_VSTART(x)                      ((x) & 0xffff)
> +#define DISP_CRP2_HSTART_MASK                    GENMASK(31, 16)
> +#define DISP_CRP2_HSTART(x)                      (((x) & 0xffff) << 16)
> +
> +#define DISP0_TOP_CRP2_SIZE                      0x8008
> +#define DISP_CRP2_VSIZE_MASK                     GENMASK(15, 0)
> +#define DISP_CRP2_VSIZE(x)                       ((x) & 0xffff)
> +#define DISP_CRP2_HSIZE_MASK                     GENMASK(31, 16)
> +#define DISP_CRP2_HSIZE(x)                       (((x) & 0xffff) << 16)
> +
> +#define DISP0_TOP_OUT_SIZE                       0x800c
> +#define DISP_OUT_VSIZE_MASK                      GENMASK(12, 0)
> +#define DISP_OUT_HSIZE_MASK                      GENMASK(28, 16)
> +#define DISP_OUT_HSIZE_SHIFT                     16
> +
> +#define ISP_DISP0_TOP_IN_SIZE                    0x804c
> +#define DISP_TOP_IN_VSIZE(x)                     ((x) & 0x1fff)
> +#define DISP_TOP_IN_HSIZE(x)                     (((x) & 0x1fff) << 16)
> +
> +#define DISP0_PPS_SCALE_EN                       0x8200
> +#define PPS_VSC_TAP_NUM_MASK                     GENMASK(3, 0)
> +#define PPS_HSC_TAP_NUM_MASK                     GENMASK(7, 4)
> +#define PPS_HSC_TAP_NUM_SHIFT                    4
> +#define PPS_HSC_TAP_NUM_INIT                     4
> +#define PPS_PREVSC_FLT_NUM_MASK                  GENMASK(11, 8)
> +#define PPS_PREVSC_FLT_NUM_SHIFT                 8
> +#define PPS_PREHSC_FLT_NUM_MASK                  GENMASK(15, 12)
> +#define PPS_PREHSC_FLT_NUM_SHIFT                 12
> +#define PPS_PREHSC_FLT_NUM_INIT                  8
> +#define PPS_PREVSC_RATE_MASK                     GENMASK(17, 16)
> +#define PPS_PREVSC_RATE_SHIFT                    16
> +#define PPS_PREHSC_RATE_MASK                     GENMASK(19, 18)
> +#define PPS_PREHSC_RATE_SHIFT                    18
> +#define PPS_HSC_EN_MASK                          BIT(20)
> +#define PPS_HSC_EN_SHIFT                         20
> +#define PPS_VSC_EN_MASK                          BIT(21)
> +#define PPS_VSC_EN_SHIFT                         21
> +#define PPS_PREVSC_EN_MASK                       BIT(22)
> +#define PPS_PREVSC_EN_SHIFT                      22
> +#define PPS_PREHSC_EN_MASK                       BIT(23)
> +#define PPS_PREHSC_EN_SHIFT                      23
> +#define PPS_HSC_NOR_RS_BITS_MASK                 GENMASK(27, 24)
> +#define PPS_HSC_NOR_RS_BITS_SHIFT                24
> +#define PPS_HSC_NOR_RS_BITS_INIT                 9
> +#define PPS_VSC_NOR_RS_BITS_MASK                 GENMASK(31, 28)
> +#define PPS_VSC_NOR_RS_BITS_SHIFT                28
> +#define PPS_VSC_NOR_RS_BITS_INIT                 9
> +
> +#define DISP0_PPS_PRE_HSCALE_COEF_0              0x8204
> +#define PPS_PREHSC_LUMA_COEF0_MASK               GENMASK(9, 0)
> +#define PPS_PREHSC_LUMA_COEF0_INIT               128
> +#define PPS_PREHSC_LUMA_COEF1_MASK               GENMASK(25, 16)
> +#define PPS_PREHSC_LUMA_COEF1_SHIFT              16
> +#define PPS_PREHSC_LUMA_COEF1_INIT               128
> +
> +#define DISP0_PPS_PRE_HSCALE_COEF_1              0x8208
> +#define PPS_PREHSC_LUMA_COEF2_MASK               GENMASK(9, 0)
> +#define PPS_PREHSC_LUMA_COEF2_INIT               32
> +#define PPS_PREHSC_LUMA_COEF3_MASK               GENMASK(25, 16)
> +#define PPS_PREHSC_LUMA_COEF3_SHIFT              16
> +#define PPS_PREHSC_LUMA_COEF3_INIT               32
> +
> +#define DISP0_PPS_VSC_START_PHASE_STEP           0x8224
> +#define PPS_VSC_FRACTION_PART_MASK               GENMASK(23, 0)
> +#define PPS_VSC_INTEGER_PART_MASK                GENMASK(27, 24)
> +#define PPS_VSC_INTEGER_PART_SHIFT               24
> +
> +#define DISP0_PPS_HSC_START_PHASE_STEP           0x8230
> +#define PPS_HSC_FRACTION_PART_MASK               GENMASK(23, 0)
> +#define PPS_HSC_INTEGER_PART_MASK                GENMASK(27, 24)
> +#define PPS_HSC_INTEGER_PART_SHIFT               24
> +
> +#define DISP0_PPS_444TO422                       0x823c
> +#define PPS_444TO422_EN_MASK                     BIT(0)
> +
> +#define ISP_SCALE0_COEF_IDX_LUMA                 0x8240
> +#define SCALE_LUMA_COEF_S11_MODE                 BIT(9)
> +#define SCALE_LUMA_CTYPE(x)                      (((x) & 0x7) << 10)
> +
> +#define ISP_SCALE0_COEF_LUMA                     0x8244
> +#define SCALE_COEF_LUMA_DATA1(x)                 ((x) & 0x7ff)
> +#define SCALE_COEF_LUMA_DATA0(x)                 (((x) & 0x7ff) << 16)
> +
> +#define ISP_SCALE0_COEF_IDX_CHRO                 0x8248
> +#define SCALE_CHRO_COEF_S11_MODE                 BIT(9)
> +#define SCALE_CHRO_CTYPE(x)                      (((x) & 0x7) << 10)
> +
> +#define ISP_SCALE0_COEF_CHRO                     0x824c
> +#define SCALE_COEF_CHRO_DATA1(x)                 ((x) & 0x7ff)
> +#define SCALE_COEF_CHRO_DATA0(x)                 (((x) & 0x7ff) << 16)
> +
> +#define ISP_AF_ROI0_WIN01                        0xa00c
> +#define AF_ROI_XYWIN_00_INIT                     (50 & 0xffff)
> +#define AF_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
> +
> +#define ISP_AF_ROI1_WIN01                        0xa010
> +#define AF_ROI_XYWIN_10_INIT                     (100 & 0xffff)
> +#define AF_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
> +
> +#define ISP_AF_ROI0_WIN23                        0xa014
> +#define AF_ROI_XYWIN_02_INIT                     (80 & 0xffff)
> +#define AF_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
> +
> +#define ISP_AF_ROI1_WIN23                        0xa018
> +#define AF_ROI_XYWIN_12_INIT                     (80 & 0xffff)
> +#define AF_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
> +
> +#define ISP_AF_CTRL                              0xa044
> +#define AF_CTRL_YPHS_OFST_MASK                   GENMASK(15, 14)
> +#define AF_CTRL_YPHS_OFST_SHIFT                  14
> +#define AF_CTRL_XPHS_OFST_MASK                   GENMASK(17, 16)
> +#define AF_CTRL_XPHS_OFST_SHIFT                  16
> +
> +#define ISP_AF_HV_SIZE                           0xa04c
> +#define AF_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
> +#define AF_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
> +
> +#define ISP_AF_HV_BLKNUM                         0xa050
> +#define AF_HV_STAT_VBLK_NUM_MASK                 GENMASK(5, 0)
> +#define AF_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x3f)
> +#define AF_HV_STAT_HBLK_NUM_MASK                 GENMASK(21, 16)
> +#define AF_HV_STAT_HBLK_NUM_SHIFT                16
> +
> +#define ISP_AF_EN_CTRL                           0xa054
> +#define AF_STAT_SELECT                           BIT(21)
> +#define AF_STAT_SELECT_SHIFT                     21
> +
> +#define ISP_RO_AF_GLB_STAT_PCK0                  0xa0e0
> +#define ISP_RO_AF_GLB_STAT_PCK1                  0xa0e4
> +#define ISP_RO_AF_GLB_STAT_PCK2                  0xa0e8
> +#define ISP_RO_AF_GLB_STAT_PCK3                  0xa0eC
> +#define ISP_AF_IDX_ADDR                          0xa1c0
> +#define ISP_AF_IDX_DATA                          0xa1c4
> +#define AF_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
> +#define AF_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
> +
> +#define ISP_AE_ROI0_WIN01                        0xa40c
> +#define AE_ROI_XYWIN_00_INIT                     (50 & 0xffff)
> +#define AE_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
> +
> +#define ISP_AE_ROI1_WIN01                        0xa410
> +#define AE_ROI_XYWIN_10_INIT                     (100 & 0xffff)
> +#define AE_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
> +
> +#define ISP_AE_ROI0_WIN23                        0xa414
> +#define AE_ROI_XYWIN_02_INIT                     (80 & 0xffff)
> +#define AE_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
> +
> +#define ISP_AE_ROI1_WIN23                        0xa418
> +#define AE_ROI_XYWIN_12_INIT                     (80 & 0xffff)
> +#define AE_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
> +
> +#define ISP_RO_AE_ROI_STAT_PCK0_0                0xa424
> +#define ISP_RO_AE_ROI_STAT_PCK1_0                0xa428
> +#define ISP_RO_AE_ROI_STAT_PCK0_1                0xa42c
> +#define ISP_RO_AE_ROI_STAT_PCK1_1                0xa430
> +#define ISP_AE_CTRL                              0xa448
> +#define AE_CTRL_AE_STAT_LOCAL_MODE               GENMASK(3, 2)
> +#define AE_CTRL_AE_STAT_LOCAL_MODE_EXT(x)        (((x) >> 2) & 0x3)
> +#define AE_CTRL_INPUT_2LINE_TOGETHER             BIT(7)
> +#define AE_CTRL_LUMA_MODE_MASK                   GENMASK(9, 8)
> +#define AE_CTRL_LUMA_MODE_SHIFT                  8
> +#define AE_CTRL_LUMA_MODE_FILTER                 2
> +#define AE_CTRL_YPHS_OFST_MASK                   GENMASK(25, 24)
> +#define AE_CTRL_YPHS_OFST_SHIFT                  24
> +#define AE_CTRL_XPHS_OFST_MASK                   GENMASK(27, 26)
> +#define AE_CTRL_XPHS_OFST_SHIFT                  26
> +
> +#define ISP_AE_CRTL2_0                           0xa44c
> +#define AE_CRTL2_BL12_GRBGI0_MASK                GENMASK(11, 0)
> +#define AE_CRTL2_GAIN_GRBGI0_MASK                GENMASK(23, 16)
> +#define AE_CRTL2_GAIN_GRBGI0_SHIFT               16
> +
> +#define ISP_AE_CRTL2_1                           0xa450
> +#define AE_CRTL2_BL12_GRBGI1_MASK                GENMASK(11, 0)
> +#define AE_CRTL2_GAIN_GRBGI1_MASK                GENMASK(23, 16)
> +#define AE_CRTL2_GAIN_GRBGI1_SHIFT               16
> +
> +#define ISP_AE_CRTL2_2                           0xa454
> +#define AE_CRTL2_BL12_GRBGI2_MASK                GENMASK(11, 0)
> +#define AE_CRTL2_GAIN_GRBGI2_MASK                GENMASK(23, 16)
> +#define AE_CRTL2_GAIN_GRBGI2_SHIFT               16
> +
> +#define ISP_AE_CRTL2_3                           0xa458
> +#define AE_CRTL2_BL12_GRBGI3_MASK                GENMASK(11, 0)
> +#define AE_CRTL2_GAIN_GRBGI3_MASK                GENMASK(23, 16)
> +#define AE_CRTL2_GAIN_GRBGI3_SHIFT               16
> +
> +#define ISP_AE_CRTL2_4                           0xa45C
> +#define AE_CRTL2_BL12_GRBGI4_MASK                GENMASK(11, 0)
> +#define AE_CRTL2_GAIN_GRBGI4_MASK                GENMASK(23, 16)
> +#define AE_CRTL2_GAIN_GRBGI4_SHIFT               16
> +
> +#define ISP_AE_HV_SIZE                           0xa464
> +#define AE_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
> +#define AE_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
> +
> +#define ISP_AE_HV_BLKNUM                         0xa468
> +#define AE_HV_STAT_VBLK_NUM_MASK                 GENMASK(6, 0)
> +#define AE_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x7f)
> +#define AE_HV_STAT_HBLK_NUM_MASK                 GENMASK(22, 16)
> +#define AE_HV_STAT_HBLK_NUM_SHIFT                16
> +
> +#define ISP_AE_STAT_THD01                        0xa46c
> +#define AE_STAT_THRD0_INIT                       (13107 & 0xffff)
> +#define AE_STAT_THRD1_INIT                       ((26214 & 0xffff) << 16)
> +
> +#define ISP_AE_STAT_THD23                        0xa470
> +#define AE_STAT_THRD2_INIT                       (39321 & 0xffff)
> +#define AE_STAT_THRD3_INIT                       ((52428 & 0xffff) << 16)
> +
> +#define ISP_RO_AE_STAT_GPNUM                     0xa49c
> +#define ISP_AE_IDX_ADDR                          0xa600
> +#define ISP_AE_IDX_DATA                          0xa604
> +#define AE_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
> +#define AE_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
> +
> +#define ISP_AE_BLK_WT_ADDR                       0xa608
> +#define ISP_AE_BLK_WT_DATA                       0xa60c
> +#define AE_BLK_WT_DATA0(x)                       ((x) & 0xf)
> +#define AE_BLK_WT_DATA1(x)                       (((x) & 0xf) << 4)
> +#define AE_BLK_WT_DATA2(x)                       (((x) & 0xf) << 8)
> +#define AE_BLK_WT_DATA3(x)                       (((x) & 0xf) << 12)
> +#define AE_BLK_WT_DATA4(x)                       (((x) & 0xf) << 16)
> +#define AE_BLK_WT_DATA5(x)                       (((x) & 0xf) << 20)
> +#define AE_BLK_WT_DATA6(x)                       (((x) & 0xf) << 24)
> +#define AE_BLK_WT_DATA7(x)                       (((x) & 0xf) << 28)
> +
> +#define ISP_AWB_CTRL                             0xa834
> +#define AWB_CTRL_YPHS_OFST_MASK                  GENMASK(1, 0)
> +#define AWB_CTRL_XPHS_OFST_MASK                  GENMASK(3, 2)
> +#define AWB_CTRL_XPHS_OFST_SHIFT                 2
> +#define AWB_CTRL_STAT_RATIO_MODE                 BIT(4)
> +
> +#define ISP_AWB_HV_SIZE                          0xa83c
> +#define AWB_HV_STAT_VSIZE(x)                     ((x) & 0xffff)
> +#define AWB_HV_STAT_HSIZE(x)                     (((x) & 0xffff) << 16)
> +
> +#define ISP_AWB_STAT_ROI0_WIN01                  0xa80c
> +#define AWB_ROI_XYWIN_00_INIT                    (32 & 0xffff)
> +#define AWB_ROI_XYWIN_01_INIT                    ((62 & 0xffff) << 16)
> +
> +#define ISP_AWB_STAT_ROI1_WIN01                  0xa810
> +#define AWB_ROI_XYWIN_10_INIT                    (32 & 0xffff)
> +#define AWB_ROI_XYWIN_11_INIT                    ((82 & 0xffff) << 16)
> +
> +#define ISP_AWB_STAT_ROI0_WIN23                  0xa814
> +#define AWB_ROI_XYWIN_02_INIT                    (32 & 0xffff)
> +#define AWB_ROI_XYWIN_03_INIT                    ((42 & 0xffff) << 16)
> +
> +#define ISP_AWB_STAT_ROI1_WIN23                  0xa818
> +#define AWB_ROI_XYWIN_12_INIT                    (64 & 0xffff)
> +#define AWB_ROI_XYWIN_13_INIT                    ((52 & 0xffff) << 16)
> +
> +#define ISP_AWB_HV_BLKNUM                        0xa840
> +#define AWB_HV_STAT_VBLK_NUM_MASK                GENMASK(5, 0)
> +#define AWB_HV_STAT_VBLK_NUM_EXT(x)              ((x) & 0x3f)
> +#define AWB_HV_STAT_HBLK_NUM_MASK                GENMASK(21, 16)
> +#define AWB_HV_STAT_HBLK_NUM_SHIFT               16
> +
> +#define ISP_AWB_STAT_RG                          0xa848
> +#define AWB_STAT_RG_MIN_MASK                     GENMASK(11, 0)
> +#define AWB_STAT_RG_MAX_MASK                     GENMASK(27, 16)
> +#define AWB_STAT_RG_MAX_SHIFT                    16
> +
> +#define ISP_AWB_STAT_BG                          0xa84c
> +#define AWB_STAT_BG_MIN_MASK                     GENMASK(11, 0)
> +#define AWB_STAT_BG_MAX_MASK                     GENMASK(27, 16)
> +#define AWB_STAT_BG_MAX_SHIFT                    16
> +
> +#define ISP_AWB_STAT_RG_HL                       0xa850
> +#define AWB_STAT_RG_LOW_MASK                     GENMASK(11, 0)
> +#define AWB_STAT_RG_HIGH_MASK                    GENMASK(27, 16)
> +#define AWB_STAT_RG_HIGH_SHIFT                   16
> +
> +#define ISP_AWB_STAT_BG_HL                       0xa854
> +#define AWB_STAT_BG_LOW_MASK                     GENMASK(11, 0)
> +#define AWB_STAT_BG_HIGH_MASK                    GENMASK(27, 16)
> +#define AWB_STAT_BG_HIGH_SHIFT                   16
> +
> +#define ISP_AWB_STAT_CTRL2                       0xa858
> +#define AWB_STAT_SATUR_VALID_MASK                BIT(0)
> +#define AWB_STAT_LOCAL_MODE                      BIT(2)
> +#define AWB_STAT_LOCAL_MODE_EXT(x)               (((x) >> 2) & 0x1)
> +#define AWB_STAT_LUMA_DIV_MODE                   GENMASK(4, 3)
> +#define AWB_STAT_LUMA_DIV_MODE_EXT(x)            (((x) >> 3) & 0x3)
> +
> +#define ISP_AWB_STAT_BLC20_0                     0xa85c
> +#define AWB_STAT_BLC20_GR_MASK                   GENMASK(19, 0)
> +
> +#define ISP_AWB_STAT_BLC20_1                     0xa860
> +#define AWB_STAT_BLC20_R_MASK                    GENMASK(19, 0)
> +
> +#define ISP_AWB_STAT_BLC20_2                     0xa864
> +#define AWB_STAT_BLC20_B_MASK                    GENMASK(19, 0)
> +
> +#define ISP_AWB_STAT_BLC20_3                     0xa868
> +#define AWB_STAT_BLC20_GB_MASK                   GENMASK(19, 0)
> +
> +#define ISP_AWB_STAT_GAIN10_0                    0xa86c
> +#define AWB_STAT_GAIN10_GR_MASK                  GENMASK(9, 0)
> +
> +#define ISP_AWB_STAT_GAIN10_1                    0xa870
> +#define AWB_STAT_GAIN10_R_MASK                   GENMASK(9, 0)
> +
> +#define ISP_AWB_STAT_GAIN10_2                    0xa874
> +#define AWB_STAT_GAIN10_B_MASK                   GENMASK(9, 0)
> +
> +#define ISP_AWB_STAT_GAIN10_3                    0xa878
> +#define AWB_STAT_GAIN10_GB_MASK                  GENMASK(9, 0)
> +
> +#define ISP_AWB_STAT_SATUR_CTRL                  0xa884
> +#define AWB_STAT_SATUR_LOW(x)                    ((x) & 0xffff)
> +#define AWB_STAT_SATUR_HIGH(x)                   (((x) & 0xffff) << 16)
> +
> +#define ISP_AWB_IDX_ADDR                         0xaa00
> +#define ISP_AWB_IDX_DATA                         0xaa04
> +#define AWB_IDX_VIDX_DATA(x)                     ((x) & 0xffff)
> +#define AWB_IDX_HIDX_DATA(x)                     (((x) & 0xffff) << 16)
> +
> +#define ISP_AWB_BLK_WT_ADDR                      0xaa08
> +#define ISP_AWB_BLK_WT_DATA                      0xaa0c
> +#define AWB_BLK_WT_DATA0(x)                      ((x) & 0xf)
> +#define AWB_BLK_WT_DATA1(x)                      (((x) & 0xf) << 4)
> +#define AWB_BLK_WT_DATA2(x)                      (((x) & 0xf) << 8)
> +#define AWB_BLK_WT_DATA3(x)                      (((x) & 0xf) << 12)
> +#define AWB_BLK_WT_DATA4(x)                      (((x) & 0xf) << 16)
> +#define AWB_BLK_WT_DATA5(x)                      (((x) & 0xf) << 20)
> +#define AWB_BLK_WT_DATA6(x)                      (((x) & 0xf) << 24)
> +#define AWB_BLK_WT_DATA7(x)                      (((x) & 0xf) << 28)
> +
> +#define ISP_WRMIFX3_0_CH0_CTRL0                  0xc400
> +#define WRMIFX3_CH0_STRIDE_MASK                  GENMASK(28, 16)
> +#define WRMIFX3_CH0_STRIDE_SHIFT                 16
> +
> +#define ISP_WRMIFX3_0_CH0_CTRL1                  0xc404
> +#define WRMIFX3_CH0_PIX_BITS_MODE_MASK           GENMASK(30, 27)
> +#define WRMIFX3_CH0_PIX_BITS_MODE_SHIFT          27
> +#define WRMIFX3_CH0_PIX_BITS_8BITS               1
> +#define WRMIFX3_CH0_PIX_BITS_16BITS              2
> +
> +#define ISP_WRMIFX3_0_CH1_CTRL0                  0xc408
> +#define WRMIFX3_CH1_STRIDE_MASK                  GENMASK(28, 16)
> +#define WRMIFX3_CH1_STRIDE_SHIFT                 16
> +
> +#define ISP_WRMIFX3_0_CH1_CTRL1                  0xc40c
> +#define WRMIFX3_CH1_PIX_BITS_MODE_MASK           GENMASK(30, 27)
> +#define WRMIFX3_CH1_PIX_BITS_MODE_SHIFT          27
> +#define WRMIFX3_CH1_PIX_BITS_16BITS              2
> +#define WRMIFX3_CH1_PIX_BITS_32BITS              3
> +
> +#define ISP_WRMIFX3_0_CH0_BADDR                  0xc440
> +/* WRMIF base address need 16 bits alignment */
> +#define WRMIFX3_CH0_BADDR(x)                     (((x) >> 4) & 0xffffffff)
> +
> +#define ISP_WRMIFX3_0_CH1_BADDR                  0xc444
> +#define WRMIFX3_CH1_BADDR(x)                     (((x) >> 4) & 0xffffffff)
> +
> +#define ISP_WRMIFX3_0_FMT_SIZE                   0xc464
> +#define WRMIFX3_FMT_HSIZE(x)                     ((x) & 0xffff)
> +#define WRMIFX3_FMT_VSIZE(x)                     (((x) & 0xffff) << 16)
> +
> +#define ISP_WRMIFX3_0_FMT_CTRL                   0xc468
> +#define WRMIFX3_FMT_MTX_IBITS_MASK               GENMASK(1, 0)
> +#define WRMIFX3_FMT_MTX_IBITS_8BIT               0
> +#define WRMIFX3_FMT_MTX_IBITS_10BIT              1
> +#define WRMIFX3_FMT_MTX_IBITS_12BIT              2
> +#define WRMIFX3_FMT_MTX_IBITS_16BIT              3
> +#define WRMIFX3_FMT_MTX_UV_SWAP_MASK             BIT(2)
> +#define WRMIFX3_FMT_MTX_UV_SWAP_SHIFT            2
> +#define WRMIFX3_FMT_MTX_UV_SWAP_VU               0
> +#define WRMIFX3_FMT_MTX_UV_SWAP_UV               1
> +#define WRMIFX3_FMT_MTX_PLANE_MASK               GENMASK(5, 4)
> +#define WRMIFX3_FMT_MTX_PLANE_SHIFT              4
> +#define WRMIFX3_FMT_MTX_PLANE_X1                 0
> +#define WRMIFX3_FMT_MTX_PLANE_X2                 1
> +#define WRMIFX3_FMT_MODE_OUT_MASK                GENMASK(18, 16)
> +#define WRMIFX3_FMT_MODE_OUT_SHIFT               16
> +#define WRMIFX3_FMT_MODE_OUT_YUV422              1
> +#define WRMIFX3_FMT_MODE_OUT_YUV420              2
> +#define WRMIFX3_FMT_MODE_OUT_Y_ONLY              3
> +
> +#define ISP_WRMIFX3_0_WIN_LUMA_H                 0xc420
> +#define WRMIFX3_WIN_LUMA_HEND_MASK               GENMASK(28, 16)
> +#define WRMIFX3_WIN_LUMA_HEND_SHIFT              16
> +#define WRMIFX3_WIN_LUMA_HEND(x)                 ((x) - 1)
> +
> +#define ISP_WRMIFX3_0_WIN_LUMA_V                 0xc424
> +#define WRMIFX3_WIN_LUMA_VEND_MASK               GENMASK(28, 16)
> +#define WRMIFX3_WIN_LUMA_VEND_SHIFT              16
> +#define WRMIFX3_WIN_LUMA_VEND(x)                 ((x) - 1)
> +
> +#define ISP_WRMIFX3_0_WIN_CHROM_H                0xc428
> +#define WRMIFX3_WIN_CHROM_HEND_MASK              GENMASK(28, 16)
> +#define WRMIFX3_WIN_CHROM_HEND_SHIFT             16
> +#define WRMIFX3_WIN_CHROM_HEND(x)                (((x) >> 1) - 1)
> +
> +#define ISP_WRMIFX3_0_WIN_CHROM_V                0xc42c
> +#define WRMIFX3_WIN_CHROM_VEND_MASK              GENMASK(28, 16)
> +#define WRMIFX3_WIN_CHROM_VEND_SHIFT             16
> +#define WRMIFX3_WIN_CHROM_VEND(x)                (((x) >> 1) - 1)
> +
> +#define ISP_WRMIFX3_0_CRP_HSIZE                  0xc48c
> +#define WRMIFX3_CROP_HEND_MASK                   GENMASK(31, 16)
> +#define WRMIFX3_CROP_HEND_SHIFT                  16
> +#define WRMIFX3_CROP_HEND(x)                     ((x) - 1)
> +
> +#define ISP_WRMIFX3_0_CRP_VSIZE                  0xc490
> +#define WRMIFX3_CROP_VEND_MASK                   GENMASK(31, 16)
> +#define WRMIFX3_CROP_VEND_SHIFT                  16
> +#define WRMIFX3_CROP_VEND(x)                     ((x) - 1)
> +
> +#define VIU_DMAWR_BADDR0                         0xc840
> +#define VIU_DMAWR_AF_BADDR_MASK                  GENMASK(27, 0)
> +/* AF base address need 16 bits alignment */
> +#define VIU_DMAWR_AF_BADDR(x)                    ((x) >> 4)
> +
> +#define VIU_DMAWR_BADDR1                         0xc844
> +#define VIU_DMAWR_AWB_BADDR_MASK                  GENMASK(27, 0)
> +/* AWB base address need 16 bits alignment */
> +#define VIU_DMAWR_AWB_BADDR(x)                    ((x) >> 4)
> +
> +#define VIU_DMAWR_BADDR2                         0xc848
> +#define VIU_DMAWR_AE_BADDR_MASK                  GENMASK(27, 0)
> +/* AE base address need 16 bits alignment */
> +#define VIU_DMAWR_AE_BADDR(x)                    ((x) >> 4)
> +
> +#define VIU_DMAWR_SIZE0                          0xc854
> +#define VIU_DMAWR_SIZE_AF_MASK                   GENMASK(15, 0)
> +#define VIU_DMAWR_SIZE_AWB_MASK                  GENMASK(31, 16)
> +#define VIU_DMAWR_SIZE_AWB_SHIFT                 16
> +
> +#define VIU_DMAWR_SIZE1                          0xc858
> +#define VIU_DMAWR_SIZE_AE_MASK                   GENMASK(15, 0)
> +
> +#endif
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
> new file mode 100644
> index 000000000000..01d99b66cb32
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
> @@ -0,0 +1,768 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/pm_runtime.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +
> +static const struct c3_isp_mbus_format_info c3_isp_rsz_mbus_formats[] = {
> +	/* YUV formats */
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_Y8_1X8,
> +		.pads		= BIT(C3_ISP_RESIZER_PAD_SINK)
> +				| BIT(C3_ISP_RESIZER_PAD_SOURCE),
> +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_YUYV8_1_5X8,
> +		.pads		= BIT(C3_ISP_RESIZER_PAD_SINK)
> +				| BIT(C3_ISP_RESIZER_PAD_SOURCE),
> +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_YUYV8_2X8,

These mbus_codes come from the ISP, I presume after debayering.
Is the different samples number (5X8, 2X8) a representation of the
format on the internal bus between the ISP and the resizers ?

> +		.pads		= BIT(C3_ISP_RESIZER_PAD_SINK)
> +				| BIT(C3_ISP_RESIZER_PAD_SOURCE),
> +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> +	},
> +};
> +
> +/* The normal parameters of pps module */
> +static const int pps_lut_tap4_s11[C3_PPS_TAP4_S11_H_NUM][4] =  {
> +	{  0, 511,   0,   0}, { -5, 511,   5,   0}, {-10, 511,  11,   0},
> +	{-14, 510,  17,  -1}, {-18, 508,  23,  -1}, {-22, 506,  29,  -1},
> +	{-25, 503,  36,  -2}, {-28, 500,  43,  -3}, {-32, 496,  51,  -3},
> +	{-34, 491,  59,  -4}, {-37, 487,  67,  -5}, {-39, 482,  75,  -6},
> +	{-41, 476,  84,  -7}, {-42, 470,  92,  -8}, {-44, 463, 102,  -9},
> +	{-45, 456, 111, -10}, {-45, 449, 120, -12}, {-47, 442, 130, -13},
> +	{-47, 434, 140, -15}, {-47, 425, 151, -17}, {-47, 416, 161, -18},
> +	{-47, 407, 172, -20}, {-47, 398, 182, -21}, {-47, 389, 193, -23},
> +	{-46, 379, 204, -25}, {-45, 369, 215, -27}, {-44, 358, 226, -28},
> +	{-43, 348, 237, -30}, {-43, 337, 249, -31}, {-41, 326, 260, -33},
> +	{-40, 316, 271, -35}, {-39, 305, 282, -36}, {-37, 293, 293, -37}
> +};
> +
> +static const struct c3_isp_mbus_format_info
> +*rsz_find_format_by_code(u32 code, u32 pad)
> +{
> +	int i;

unsigned

> +
> +	for (i = 0; i < ARRAY_SIZE(c3_isp_rsz_mbus_formats); i++) {
> +		const struct c3_isp_mbus_format_info *info =
> +			&c3_isp_rsz_mbus_formats[i];
> +
> +		if (info->mbus_code == code && info->pads & BIT(pad))
> +			return info;
> +	}
> +
> +	return NULL;
> +}
> +
> +static void c3_isp_rsz_cfg_fmt(struct c3_isp_resizer *rsz,
> +			       struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> +
> +	c3_isp_write(rsz->isp, C3_DISP_REG(ISP_DISP0_TOP_IN_SIZE, rsz->id),
> +		     DISP_TOP_IN_HSIZE(fmt->width) | DISP_TOP_IN_VSIZE(fmt->height));
> +}
> +
> +static void c3_isp_rsz_crop_enable(struct c3_isp_resizer *rsz,
> +				   struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_rect *crop;
> +
> +	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> +
> +	c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_START, rsz->id),
> +		     DISP_CRP2_VSTART(crop->top) | DISP_CRP2_HSTART(crop->left));
> +	c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_SIZE, rsz->id),
> +		     DISP_CRP2_VSIZE(crop->height) | DISP_CRP2_HSIZE(crop->width));
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
> +			   DISP_CRP2_EN, DISP_CRP2_EN);
> +}
> +
> +static void c3_isp_rsz_pps_size(struct c3_isp_resizer *rsz,
> +				struct c3_isp_pps_io_size *io_size)
> +{
> +	int thsize = io_size->thsize;
> +	int tvsize = io_size->tvsize;
> +	u32 ohsize = io_size->ohsize;
> +	u32 ovsize = io_size->ovsize;
> +	u32 ihsize = io_size->ihsize;
> +	u32 max_hsize = io_size->max_hsize;
> +	int step_h_integer, step_v_integer;
> +	int step_h_fraction, step_v_fraction;
> +	int yuv444to422_en;
> +
> +	/* Calculate the integer part of horizonal scaler step */
> +	step_h_integer = thsize / ohsize;
> +
> +	/* Calculate the vertical part of horizonal scaler step */
> +	step_v_integer = tvsize / ovsize;
> +
> +	/*
> +	 * Calculate the fraction part of horizonal scaler step.
> +	 * step_h_fraction = (source / dest) * 2^24,
> +	 * so step_h_fraction = ((source << 12) / dest) << 12.
> +	 */
> +	step_h_fraction = ((thsize << 12) / ohsize) << 12;
> +
> +	/*
> +	 * Calculate the fraction part of vertical scaler step
> +	 * step_v_fraction = (source / dest) * 2^24,
> +	 * so step_v_fraction = ((source << 12) / dest) << 12.
> +	 */
> +	step_v_fraction = ((tvsize << 12) / ovsize) << 12;
> +
> +	yuv444to422_en = ihsize > (max_hsize / 2) ? 1 : 0;
> +
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_444TO422, rsz->id),
> +			   PPS_444TO422_EN_MASK, yuv444to422_en);
> +
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
> +			   PPS_VSC_FRACTION_PART_MASK, step_v_fraction);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
> +			   PPS_VSC_INTEGER_PART_MASK,
> +			   step_v_integer << PPS_VSC_INTEGER_PART_SHIFT);
> +
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
> +			   PPS_HSC_FRACTION_PART_MASK, step_h_fraction);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
> +			   PPS_HSC_INTEGER_PART_MASK,
> +			   step_h_integer << PPS_HSC_INTEGER_PART_SHIFT);
> +
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
> +			   PPS_PREHSC_LUMA_COEF0_MASK, PPS_PREHSC_LUMA_COEF0_INIT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
> +			   PPS_PREHSC_LUMA_COEF1_MASK,
> +			   PPS_PREHSC_LUMA_COEF1_INIT << PPS_PREHSC_LUMA_COEF1_SHIFT);
> +
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
> +			   PPS_PREHSC_LUMA_COEF2_MASK, PPS_PREHSC_LUMA_COEF2_INIT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
> +			   PPS_PREHSC_LUMA_COEF3_MASK,
> +			   PPS_PREHSC_LUMA_COEF3_INIT << PPS_PREHSC_LUMA_COEF3_SHIFT);
> +}
> +
> +static void c3_isp_rsz_pps_lut(struct c3_isp_resizer *rsz, u32 ctype)
> +{
> +	int i;

unsigned

> +
> +	/*
> +	 * Default value of this register is 0,
> +	 * so only need to set SCALE_LUMA_COEF_S11_MODE
> +	 * and SCALE_LUMA_CTYPE.

You can fit this in 2 lines ?

> +	 * This register needs to be written in one time.
> +	 */
> +	c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_LUMA, rsz->id),
> +		     SCALE_LUMA_COEF_S11_MODE | SCALE_LUMA_CTYPE(ctype));
> +
> +	for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
> +		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
> +			     SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
> +			     SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
> +		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
> +			     SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
> +			     SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][3]));
> +	}
> +
> +	/*
> +	 * Default value of this register is 0,
> +	 * so only need to set SCALE_CHRO_COEF_S11_MODE
> +	 * and SCALE_CHRO_CTYPE.

same

> +	 * This register needs to be written in one time.
> +	 */
> +	c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_CHRO, rsz->id),
> +		     SCALE_CHRO_COEF_S11_MODE | SCALE_CHRO_CTYPE(ctype));
> +
> +	for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
> +		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
> +			     SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
> +			     SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
> +		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
> +			     SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
> +			     SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][3]));
> +	}
> +}
> +
> +static void c3_isp_rsz_pps_disable(struct c3_isp_resizer *rsz)
> +{
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_HSC_EN_MASK, 0);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_VSC_EN_MASK, 0);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREVSC_EN_MASK, 0);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREHSC_EN_MASK, 0);
> +}
> +
> +static int c3_isp_rsz_pps_enable(struct c3_isp_resizer *rsz,
> +				 struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_rect *crop;
> +	struct v4l2_rect *cmps;
> +	int max_hsize;
> +	int hsc_en, vsc_en;
> +	int preh_en, prev_en;
> +	u32 reg_prehsc_rate;
> +	u32 reg_prevsc_flt_num;
> +	int pre_vscale_max_hsize;
> +	u32 ihsize_after_pre_hsc;
> +	u32 ihsize_after_pre_hsc_alt;
> +	u32 reg_vsc_tap_num_alt;
> +	u32 ihsize;
> +	u32 ivsize;
> +	struct c3_isp_pps_io_size io_size;
> +
> +	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> +	cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> +
> +	ihsize = crop->width;
> +	ivsize = crop->height;
> +
> +	hsc_en = (ihsize == cmps->width) ? C3_SCALE_DIS : C3_SCALE_EN;
> +	vsc_en = (ivsize == cmps->height) ? C3_SCALE_DIS : C3_SCALE_EN;
> +
> +	/* Disable pps when there no need to use pps */
> +	if (!hsc_en && !vsc_en) {
> +		c3_isp_rsz_pps_disable(rsz);
> +		return 0;
> +	}
> +
> +	/*
> +	 * Pre-scale needs to be enable
> +	 * if the down scaling factor exceeds 4.
> +	 */
> +	preh_en = (ihsize > cmps->width * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
> +	prev_en = (ivsize > cmps->height * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
> +
> +	if (rsz->id == C3_ISP_RSZ_2) {
> +		max_hsize = C3_ISP_MAX_WIDTH;
> +		/*
> +		 * Set vertical tap number and
> +		 * the max hsize of pre-vertical scale.
> +		 */
> +		reg_prevsc_flt_num = 4;
> +		pre_vscale_max_hsize = max_hsize / 2;
> +	} else {
> +		max_hsize = C3_ISP_DEFAULT_WIDTH;
> +		preh_en  = (ihsize > max_hsize) ? C3_SCALE_EN : C3_SCALE_DIS;
> +		/*
> +		 * Set vertical tap number and
> +		 * the max hsize of pre-vertical scale.
> +		 */
> +		if (ihsize > (max_hsize / 2) &&
> +		    ihsize <= max_hsize && prev_en) {
> +			reg_prevsc_flt_num = 2;
> +			pre_vscale_max_hsize = max_hsize;
> +		} else {
> +			reg_prevsc_flt_num = 4;
> +			pre_vscale_max_hsize = max_hsize / 2;
> +		}
> +	}
> +
> +	/*
> +	 * Set pre-horizonal scale rate and
> +	 * the hsize of after pre-horizonal scale.
> +	 */
> +	if (preh_en) {
> +		reg_prehsc_rate = 1;
> +		ihsize_after_pre_hsc = DIV_ROUND_UP(ihsize, 2);
> +	} else {
> +		reg_prehsc_rate = 0;
> +		ihsize_after_pre_hsc = ihsize;
> +	}
> +
> +	/* Change pre-horizonal scale rate */
> +	if (prev_en && ihsize_after_pre_hsc >= pre_vscale_max_hsize)
> +		reg_prehsc_rate += 1;
> +
> +	/* Set the actual hsize of after pre-horizonal scale */
> +	if (preh_en)
> +		ihsize_after_pre_hsc_alt =
> +			DIV_ROUND_UP(ihsize, 1 << reg_prehsc_rate);
> +	else
> +		ihsize_after_pre_hsc_alt = ihsize;
> +
> +	/* Set vertical scaler bank length */
> +	if (ihsize_after_pre_hsc_alt <= (max_hsize / 2))
> +		reg_vsc_tap_num_alt = 4;
> +	else if (ihsize_after_pre_hsc_alt <= max_hsize)
> +		reg_vsc_tap_num_alt = prev_en ? 2 : 4;
> +	else
> +		reg_vsc_tap_num_alt = prev_en ? 4 : 2;
> +
> +	io_size.thsize = ihsize_after_pre_hsc_alt;
> +	io_size.tvsize = prev_en ? DIV_ROUND_UP(ivsize, 2) : ivsize;
> +	io_size.ohsize = cmps->width;
> +	io_size.ovsize = cmps->height;
> +	io_size.ihsize = ihsize;
> +	io_size.max_hsize = max_hsize;
> +
> +	c3_isp_rsz_pps_size(rsz, &io_size);
> +	c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_0);
> +	c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_2);
> +
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_VSC_TAP_NUM_MASK, reg_vsc_tap_num_alt);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_HSC_TAP_NUM_MASK,
> +			   PPS_HSC_TAP_NUM_INIT << PPS_HSC_TAP_NUM_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREVSC_FLT_NUM_MASK,
> +			   reg_prevsc_flt_num << PPS_PREVSC_FLT_NUM_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREHSC_FLT_NUM_MASK,
> +			   PPS_PREHSC_FLT_NUM_INIT << PPS_PREHSC_FLT_NUM_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREVSC_RATE_MASK, prev_en << PPS_PREVSC_RATE_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREHSC_RATE_MASK, reg_prehsc_rate << PPS_PREHSC_RATE_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_HSC_EN_MASK, hsc_en << PPS_HSC_EN_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_VSC_EN_MASK, vsc_en << PPS_VSC_EN_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREVSC_EN_MASK, prev_en << PPS_PREVSC_EN_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREHSC_EN_MASK, preh_en << PPS_PREHSC_EN_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_HSC_NOR_RS_BITS_MASK,
> +			   PPS_HSC_NOR_RS_BITS_INIT << PPS_HSC_NOR_RS_BITS_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_VSC_NOR_RS_BITS_MASK,
> +			   PPS_VSC_NOR_RS_BITS_INIT << PPS_VSC_NOR_RS_BITS_SHIFT);
> +
> +	return 0;
> +}
> +
> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz)
> +{
> +	struct v4l2_subdev_state *state;
> +
> +	state = v4l2_subdev_lock_and_get_active_state(&rsz->sd);
> +
> +	c3_isp_rsz_cfg_fmt(rsz, state);
> +	c3_isp_rsz_crop_enable(rsz, state);
> +	c3_isp_rsz_pps_enable(rsz, state);
> +
> +	c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN,
> +			   TOP_DISP_EN(rsz->id), TOP_DISP_EN(rsz->id));
> +
> +	v4l2_subdev_unlock_state(state);
> +}
> +
> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
> +{
> +	c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, TOP_DISP_EN(rsz->id), 0);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
> +			   DISP_CRP2_EN, 0x0);
> +
> +	c3_isp_rsz_pps_disable(rsz);
> +}
> +
> +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state,
> +				  struct v4l2_subdev_krouting *routing)
> +{
> +	static const struct v4l2_mbus_framefmt format = {
> +		.width = C3_ISP_DEFAULT_WIDTH,
> +		.height = C3_ISP_DEFAULT_HEIGHT,
> +		.code = C3_ISP_RSZ_DEF_PAD_FMT,
> +		.field = V4L2_FIELD_NONE,
> +		.colorspace = V4L2_COLORSPACE_SRGB,
> +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
> +		.xfer_func = V4L2_XFER_FUNC_SRGB,
> +	};
> +	int ret;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes;
> +	struct v4l2_subdev_krouting routing;
> +
> +	routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
> +	routes.sink_stream = 0;
> +	routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
> +	routes.source_stream = 0;
> +	routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> +
> +	routing.num_routes = 1;
> +	routing.routes = &routes;
> +
> +	return c3_isp_rsz_cfg_routing(sd, state, &routing);
> +}
> +
> +static int c3_isp_rsz_set_routing(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state,
> +				  enum v4l2_subdev_format_whence which,
> +				  struct v4l2_subdev_krouting *routing)
> +{
> +	bool is_streaming = v4l2_subdev_is_streaming(sd);
> +
> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> +		return -EBUSY;
> +
> +	return c3_isp_rsz_cfg_routing(sd, state, routing);
> +}

Unless there are reasons I missed, I would drop routing support from
the resizers

> +
> +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
> +				     struct v4l2_subdev_state *state,
> +				     struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	if (code->index >= ARRAY_SIZE(c3_isp_rsz_mbus_formats))
> +		return -EINVAL;
> +
> +	code->code = c3_isp_rsz_mbus_formats[code->index].mbus_code;
> +
> +	return 0;
> +}
> +
> +static void c3_isp_rsz_set_sink_fmt(struct v4l2_subdev_state *state,
> +				    struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	const struct c3_isp_mbus_format_info *isp_fmt;
> +	struct v4l2_rect *sink_crop;
> +	struct v4l2_rect *sink_cmps;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
> +	sink_crop = v4l2_subdev_state_get_crop(state, format->pad);
> +	sink_cmps = v4l2_subdev_state_get_compose(state, format->pad);
> +
> +	isp_fmt = rsz_find_format_by_code(format->format.code, format->pad);
> +	if (!isp_fmt)
> +		sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> +	else
> +		sink_fmt->code = format->format.code;
> +
> +	sink_fmt->width = clamp_t(u32, format->format.width,
> +				  C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> +	sink_fmt->height = clamp_t(u32, format->format.height,
> +				   C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> +
> +	sink_crop->width = sink_fmt->width;
> +	sink_crop->height = sink_fmt->height;
> +	sink_crop->left = 0;
> +	sink_crop->top = 0;
> +
> +	sink_cmps->width = sink_crop->width;
> +	sink_cmps->height = sink_crop->height;
> +	sink_cmps->left = 0;
> +	sink_cmps->top = 0;
> +
> +	format->format = *sink_fmt;
> +}
> +
> +static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state,
> +				      struct v4l2_subdev_format *format)
> +{
> +	const struct c3_isp_mbus_format_info *rsz_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;
> +	struct v4l2_rect *sink_crop;
> +	struct v4l2_rect *sink_cmps;
> +
> +	src_fmt = v4l2_subdev_state_get_format(state, format->pad);
> +	sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> +	sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> +
> +	rsz_fmt = rsz_find_format_by_code(format->format.code, format->pad);
> +	if (!rsz_fmt)
> +		src_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> +	else
> +		src_fmt->code = format->format.code;
> +
> +	src_fmt->width = clamp_t(u32, format->format.width,
> +				 C3_ISP_MIN_WIDTH, sink_crop->width);
> +	src_fmt->height = clamp_t(u32, format->format.height,
> +				  C3_ISP_MIN_HEIGHT, sink_crop->height);
> +
> +	/* The sink compose size must be same with the source size. */
> +	sink_cmps->width = src_fmt->width;
> +	sink_cmps->height = src_fmt->height;

Shouldn't it be the other way around ? The source sizes should always
match the sink compose rectangle sizes ?

> +
> +	format->format = *src_fmt;
> +}
> +
> +static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd,
> +			      struct v4l2_subdev_state *state,
> +			      struct v4l2_subdev_format *format)
> +{
> +	if (format->pad == C3_ISP_RESIZER_PAD_SINK) {
> +		c3_isp_rsz_set_sink_fmt(state, format);
> +	} else if (format->pad == C3_ISP_RESIZER_PAD_SOURCE) {
> +		c3_isp_rsz_set_source_fmt(state, format);
> +	} else {

This can't happen

> +		dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
> +		return -ENOTTY;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state,
> +				    struct v4l2_subdev_selection *sel)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +	struct v4l2_rect *crop;
> +	struct v4l2_rect *cmps;
> +
> +	if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
> +		return -EINVAL;
> +
> +	switch (sel->target) {
> +	case V4L2_SEL_TGT_CROP_BOUNDS:
> +		fmt = v4l2_subdev_state_get_format(state, sel->pad);
> +		sel->r.width = fmt->width;
> +		sel->r.height = fmt->height;
> +		sel->r.left = 0;
> +		sel->r.top = 0;
> +		break;
> +	case V4L2_SEL_TGT_COMPOSE_BOUNDS:
> +		crop = v4l2_subdev_state_get_crop(state, sel->pad);
> +		sel->r.width = crop->width;
> +		sel->r.height = crop->height;
> +		sel->r.left = 0;
> +		sel->r.top = 0;
> +		break;
> +	case V4L2_SEL_TGT_CROP:
> +		crop = v4l2_subdev_state_get_crop(state, sel->pad);
> +		sel->r = *crop;
> +		break;
> +	case V4L2_SEL_TGT_COMPOSE:
> +		cmps = v4l2_subdev_state_get_compose(state, sel->pad);
> +		sel->r = *cmps;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state,
> +				    struct v4l2_subdev_selection *sel)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +	struct v4l2_rect *crop;
> +	struct v4l2_rect *cmps;
> +
> +	if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
> +		return -EINVAL;
> +
> +	switch (sel->target) {
> +	case V4L2_SEL_TGT_CROP:
> +		fmt = v4l2_subdev_state_get_format(state, sel->pad);
> +		crop = v4l2_subdev_state_get_crop(state, sel->pad);
> +
> +		sel->r.left = clamp_t(s32, sel->r.left, 0, fmt->width - 1);
> +		sel->r.top = clamp_t(s32, sel->r.top, 0, fmt->height - 1);
> +		sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH,
> +				     fmt->width - sel->r.left);
> +		sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT,
> +				      fmt->height - sel->r.top);
> +
> +		crop->width = ALIGN(sel->r.width, 2);
> +		crop->height = ALIGN(sel->r.height, 2);
> +		crop->left = sel->r.left;
> +		crop->top = sel->r.top;
> +
> +		sel->r = *crop;
> +		break;
> +	case V4L2_SEL_TGT_COMPOSE:
> +		crop = v4l2_subdev_state_get_crop(state, sel->pad);
> +		cmps = v4l2_subdev_state_get_compose(state, sel->pad);
> +
> +		sel->r.left = 0;
> +		sel->r.top = 0;
> +		sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH, crop->width);
> +		sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT, crop->height);
> +
> +		cmps->width = ALIGN(sel->r.width, 2);
> +		cmps->height = ALIGN(sel->r.height, 2);
> +		cmps->left = sel->r.left;
> +		cmps->top = sel->r.top;
> +
> +		sel->r = *cmps;
> +
> +		fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
> +		fmt->width = cmps->width;
> +		fmt->height = cmps->height;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_rsz_init_state(struct v4l2_subdev *sd,
> +				 struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;
> +	struct v4l2_rect *crop;
> +	struct v4l2_rect *cmps;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> +	sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
> +	sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> +	sink_fmt->field = V4L2_FIELD_NONE;
> +	sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> +	sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> +	sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
> +	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> +	sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> +
> +	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> +	crop->width = C3_ISP_DEFAULT_WIDTH;
> +	crop->height = C3_ISP_DEFAULT_HEIGHT;
> +	crop->left = 0;
> +	crop->top = 0;
> +
> +	cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> +	cmps->width = C3_ISP_DEFAULT_WIDTH;
> +	cmps->height = C3_ISP_DEFAULT_HEIGHT;
> +	cmps->left = 0;
> +	cmps->top = 0;
> +
> +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
> +	*src_fmt = *sink_fmt;
> +
> +	return c3_isp_rsz_init_routing(sd, state);
> +}
> +
> +static const struct v4l2_subdev_pad_ops c3_isp_rsz_pad_ops = {
> +	.enum_mbus_code = c3_isp_rsz_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = c3_isp_rsz_set_fmt,
> +	.get_selection = c3_isp_rsz_get_selection,
> +	.set_selection = c3_isp_rsz_set_selection,
> +	.set_routing = c3_isp_rsz_set_routing,
> +};
> +
> +static const struct v4l2_subdev_ops c3_isp_rsz_subdev_ops = {
> +	.pad = &c3_isp_rsz_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops c3_isp_rsz_internal_ops = {
> +	.init_state = c3_isp_rsz_init_state,
> +};
> +
> +/* Media entity operations */
> +static const struct media_entity_operations c3_isp_rsz_entity_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +static int c3_isp_rsz_register(struct c3_isp_resizer *rsz)
> +{
> +	struct v4l2_subdev *sd = &rsz->sd;
> +	int ret;
> +
> +	v4l2_subdev_init(sd, &c3_isp_rsz_subdev_ops);
> +	sd->owner = THIS_MODULE;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	sd->internal_ops = &c3_isp_rsz_internal_ops;
> +	snprintf(sd->name, sizeof(sd->name), "isp-resizer%u", rsz->id);

maybe "c3-isp_resizer%u"

> +
> +	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
> +	sd->entity.ops = &c3_isp_rsz_entity_ops;
> +
> +	sd->dev = rsz->isp->dev;
> +	v4l2_set_subdevdata(sd, rsz);
> +
> +	rsz->pads[C3_ISP_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +	rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_PAD_MAX, rsz->pads);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret)
> +		goto err_entity_cleanup;
> +
> +	ret = v4l2_device_register_subdev(&rsz->isp->v4l2_dev, sd);
> +	if (ret)
> +		goto err_subdev_cleanup;
> +
> +	return 0;
> +
> +err_subdev_cleanup:
> +	v4l2_subdev_cleanup(sd);
> +err_entity_cleanup:
> +	media_entity_cleanup(&sd->entity);
> +	return ret;
> +}
> +
> +static void c3_isp_rsz_unregister(struct c3_isp_resizer *rsz)
> +{
> +	struct v4l2_subdev *sd = &rsz->sd;
> +
> +	v4l2_device_unregister_subdev(sd);
> +	v4l2_subdev_cleanup(sd);
> +	media_entity_cleanup(&sd->entity);
> +}
> +
> +int c3_isp_resizers_register(struct c3_isp_device *isp)
> +{
> +	u32 i;
> +	int ret;
> +
> +	for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
> +		struct c3_isp_resizer *rsz = &isp->resizers[i];
> +
> +		rsz->id = i;
> +		rsz->isp = isp;
> +
> +		if (rsz->id == C3_ISP_RSZ_0)
> +			rsz->cap = &isp->caps[C3_ISP_CAP_DEV_0];
> +		else if (rsz->id == C3_ISP_RSZ_1)
> +			rsz->cap = &isp->caps[C3_ISP_CAP_DEV_1];
> +		else
> +			rsz->cap = &isp->caps[C3_ISP_CAP_DEV_2];
> +
> +		ret = c3_isp_rsz_register(rsz);
> +		if (ret) {
> +			rsz->isp = NULL;
> +			c3_isp_resizers_unregister(isp);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +void c3_isp_resizers_unregister(struct c3_isp_device *isp)
> +{
> +	u32 i;
> +
> +	for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
> +		struct c3_isp_resizer *rsz = &isp->resizers[i];
> +
> +		if (rsz->isp)
> +			c3_isp_rsz_unregister(rsz);
> +	};
> +}
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
> new file mode 100644
> index 000000000000..72024442d48f
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
> @@ -0,0 +1,488 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +#include "include/uapi/c3-isp-config.h"
> +
> +/* Hardware configuration */
> +
> +static void c3_isp_stats_af_init(struct c3_isp_stats *stats)
> +{
> +	c3_isp_write(stats->isp, ISP_AF_ROI0_WIN01,
> +		     AF_ROI_XYWIN_00_INIT | AF_ROI_XYWIN_01_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AF_ROI0_WIN23,
> +		     AF_ROI_XYWIN_02_INIT | AF_ROI_XYWIN_03_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AF_ROI1_WIN01,
> +		     AF_ROI_XYWIN_10_INIT | AF_ROI_XYWIN_11_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AF_ROI1_WIN23,
> +		     AF_ROI_XYWIN_12_INIT | AF_ROI_XYWIN_13_INIT);
> +
> +	/* 0: old statistics output, 1: new statistics output. */
> +	c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL,
> +			   AF_STAT_SELECT, 0x1 << AF_STAT_SELECT_SHIFT);
> +}
> +
> +static void c3_isp_stats_ae_init(struct c3_isp_stats *stats)
> +{
> +	c3_isp_write(stats->isp, ISP_AE_ROI0_WIN01,
> +		     AE_ROI_XYWIN_00_INIT | AE_ROI_XYWIN_01_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AE_ROI0_WIN23,
> +		     AE_ROI_XYWIN_02_INIT | AE_ROI_XYWIN_03_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AE_ROI1_WIN01,
> +		     AE_ROI_XYWIN_10_INIT | AE_ROI_XYWIN_11_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AE_ROI1_WIN23,
> +		     AE_ROI_XYWIN_12_INIT | AE_ROI_XYWIN_13_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AE_STAT_THD01,
> +		     AE_STAT_THRD0_INIT | AE_STAT_THRD1_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AE_STAT_THD23,
> +		     AE_STAT_THRD2_INIT | AE_STAT_THRD3_INIT);
> +
> +	/* Set 0 when ae_stat_switch is not 0 */
> +	c3_isp_update_bits(stats->isp, ISP_AE_CTRL,
> +			   AE_CTRL_INPUT_2LINE_TOGETHER, 0);
> +
> +	/* Configure ae luma mode */
> +	c3_isp_update_bits(stats->isp, ISP_AE_CTRL, AE_CTRL_LUMA_MODE_MASK,
> +			   AE_CTRL_LUMA_MODE_FILTER << AE_CTRL_LUMA_MODE_SHIFT);
> +}
> +
> +static void c3_isp_stats_awb_init(struct c3_isp_stats *stats)
> +{
> +	/* Initialize the awb statistics rectangle of image */
> +	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN01,
> +		     AWB_ROI_XYWIN_00_INIT | AWB_ROI_XYWIN_01_INIT);
> +	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN23,
> +		     AWB_ROI_XYWIN_02_INIT | AWB_ROI_XYWIN_03_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN01,
> +		     AWB_ROI_XYWIN_10_INIT | AWB_ROI_XYWIN_11_INIT);
> +	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN23,
> +		     AWB_ROI_XYWIN_12_INIT | AWB_ROI_XYWIN_13_INIT);
> +}
> +
> +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
> +{
> +	struct c3_isp_device *isp = stats->isp;
> +	struct c3_isp_stats_info *stats_info = stats->buff->vaddr;
> +	u32 awb_dma_size = sizeof(stats_info->awb_stats);
> +	u32 ae_dma_size = sizeof(stats_info->ae_stats);
> +	u32 awb_dma_addr = stats->buff->paddr;
> +	u32 af_dma_addr;
> +	u32 ae_dma_addr;
> +
> +	ae_dma_addr = awb_dma_addr + awb_dma_size;
> +	af_dma_addr = ae_dma_addr + ae_dma_size;
> +
> +	c3_isp_update_bits(isp, VIU_DMAWR_BADDR0, VIU_DMAWR_AF_BADDR_MASK,
> +			   VIU_DMAWR_AF_BADDR(af_dma_addr));
> +	c3_isp_update_bits(isp, VIU_DMAWR_BADDR1, VIU_DMAWR_AWB_BADDR_MASK,
> +			   VIU_DMAWR_AWB_BADDR(awb_dma_addr));
> +	c3_isp_update_bits(isp, VIU_DMAWR_BADDR2, VIU_DMAWR_AE_BADDR_MASK,
> +			   VIU_DMAWR_AE_BADDR(ae_dma_addr));
> +}
> +
> +static void c3_isp_stats_enable(struct c3_isp_stats *stats)
> +{
> +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> +			   TOP_3A_AE_STAT_EN, TOP_3A_AE_STAT_EN);
> +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> +			   TOP_3A_AWB_STAT_EN, TOP_3A_AWB_STAT_EN);
> +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> +			   TOP_3A_AF_STAT_EN, TOP_3A_AF_STAT_EN);
> +}
> +
> +static void c3_isp_stats_disable(struct c3_isp_stats *stats)
> +{
> +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> +			   TOP_3A_AE_STAT_EN, 0);
> +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> +			   TOP_3A_AWB_STAT_EN, 0);
> +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> +			   TOP_3A_AF_STAT_EN, 0);
> +}
> +
> +/* The unit of dma_size is 16 bytes */
> +static void c3_isp_stats_cfg_dmawr_size(struct c3_isp_stats *stats)
> +{
> +	u32 dma_size;
> +
> +	dma_size = sizeof(struct af_stats_info) / 16;
> +	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0,
> +			   VIU_DMAWR_SIZE_AF_MASK, dma_size);
> +
> +	dma_size = sizeof(struct awb_stats_info) / 16;
> +	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE_AWB_MASK,
> +			   dma_size << VIU_DMAWR_SIZE_AWB_SHIFT);
> +
> +	dma_size = sizeof(struct ae_stats_info) / 16;
> +	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1,
> +			   VIU_DMAWR_SIZE_AE_MASK, dma_size);
> +}
> +
> +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
> +{
> +	stats->buff = list_first_entry_or_null(&stats->pending,
> +					       struct c3_isp_vb2_buffer, list);
> +	if (stats->buff) {
> +		c3_isp_stats_cfg_dmawr_addr(stats);
> +		list_del(&stats->buff->list);
> +	}
> +}
> +
> +static void c3_isp_stats_start(struct c3_isp_stats *stats)
> +{
> +	c3_isp_stats_af_init(stats);
> +	c3_isp_stats_ae_init(stats);
> +	c3_isp_stats_awb_init(stats);
> +
> +	c3_isp_stats_cfg_dmawr_size(stats);
> +	c3_isp_stats_cfg_buff(stats);
> +	c3_isp_stats_enable(stats);
> +
> +	stats->is_streaming = true;
> +}
> +
> +static void c3_isp_stats_stop(struct c3_isp_stats *stats)
> +{
> +	stats->is_streaming = false;
> +
> +	c3_isp_stats_disable(stats);
> +}
> +
> +static void c3_isp_stats_return_buffers(struct c3_isp_stats *stats,
> +					enum vb2_buffer_state state)
> +{
> +	unsigned long flags;
> +	struct c3_isp_vb2_buffer *buff;
> +
> +	spin_lock_irqsave(&stats->buff_lock, flags);
> +
> +	if (stats->buff) {
> +		vb2_buffer_done(&stats->buff->vb.vb2_buf, state);
> +		stats->buff = NULL;
> +	}
> +
> +	while (!list_empty(&stats->pending)) {
> +		buff = list_first_entry(&stats->pending,
> +					struct c3_isp_vb2_buffer, list);
> +		list_del(&buff->list);
> +		vb2_buffer_done(&buff->vb.vb2_buf, state);
> +	}
> +
> +	spin_unlock_irqrestore(&stats->buff_lock, flags);
> +}
> +
> +static int c3_isp_stats_querycap(struct file *file, void *fh,
> +				 struct v4l2_capability *cap)
> +{
> +	strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
> +	strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
> +
> +	return 0;
> +}
> +
> +static int c3_isp_stats_enum_fmt(struct file *file, void *fh,
> +				 struct v4l2_fmtdesc *f)
> +{
> +	struct c3_isp_stats *stats = video_drvdata(file);
> +
> +	if (f->index > 0 || f->type != stats->vb2_q.type)
> +		return -EINVAL;
> +
> +	f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_stats_g_fmt(struct file *file, void *fh,
> +			      struct v4l2_format *f)
> +{
> +	struct c3_isp_stats *stats = video_drvdata(file);
> +
> +	f->fmt.meta = stats->vfmt.fmt.meta;
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
> +	.vidioc_querycap                = c3_isp_stats_querycap,
> +	.vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
> +	.vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
> +	.vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
> +	.vidioc_try_fmt_meta_cap        = c3_isp_stats_g_fmt,
> +	.vidioc_reqbufs	                = vb2_ioctl_reqbufs,
> +	.vidioc_querybuf                = vb2_ioctl_querybuf,
> +	.vidioc_qbuf                    = vb2_ioctl_qbuf,
> +	.vidioc_expbuf                  = vb2_ioctl_expbuf,
> +	.vidioc_dqbuf                   = vb2_ioctl_dqbuf,
> +	.vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
> +	.vidioc_create_bufs             = vb2_ioctl_create_bufs,
> +	.vidioc_streamon                = vb2_ioctl_streamon,
> +	.vidioc_streamoff               = vb2_ioctl_streamoff,
> +	.vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
> +	.vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
> +};
> +
> +static const struct v4l2_file_operations isp_stats_v4l2_fops = {
> +	.open = v4l2_fh_open,
> +	.release = vb2_fop_release,
> +	.poll = vb2_fop_poll,
> +	.unlocked_ioctl = video_ioctl2,
> +	.mmap = vb2_fop_mmap,
> +};
> +
> +static int c3_isp_stats_vb2_queue_setup(struct vb2_queue *q,
> +					unsigned int *num_buffers,
> +					unsigned int *num_planes,
> +					unsigned int sizes[],
> +					struct device *alloc_devs[])
> +{
> +	if (*num_planes) {
> +		if (*num_planes != 1)
> +			return -EINVAL;
> +
> +		if (sizes[0] < sizeof(struct c3_isp_stats_info))
> +			return -EINVAL;
> +	} else {
> +		*num_planes = 1;
> +		sizes[0] = sizeof(struct c3_isp_stats_info);
> +	}
> +
> +	return 0;
> +}
> +
> +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_vb2_buffer *buf =
> +			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> +	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&stats->buff_lock, flags);
> +
> +	list_add_tail(&buf->list, &stats->pending);
> +
> +	spin_unlock_irqrestore(&stats->buff_lock, flags);
> +}
> +
> +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
> +{
> +	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned int size = stats->vfmt.fmt.meta.buffersize;
> +
> +	if (vb2_plane_size(vb, 0) < size) {
> +		dev_err(stats->isp->dev,
> +			"User buffer too small (%ld < %u)\n",
> +			vb2_plane_size(vb, 0), size);
> +		return -EINVAL;
> +	}
> +
> +	vb2_set_plane_payload(vb, 0, size);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_vb2_buffer *buf =
> +			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> +	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> +
> +	buf->vaddr = vb2_plane_vaddr(vb, 0);
> +	buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
> +
> +	memset(buf->vaddr, 0, stats->vfmt.fmt.meta.buffersize);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_stats_vb2_start_streaming(struct vb2_queue *q,
> +					    unsigned int count)
> +{
> +	struct c3_isp_stats *stats = vb2_get_drv_priv(q);
> +	int ret;
> +
> +	guard(mutex)(&stats->isp->lock);
> +
> +	ret = pm_runtime_resume_and_get(stats->isp->dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = video_device_pipeline_start(&stats->vdev, &stats->isp->pipe);
> +	if (ret) {
> +		dev_err(stats->isp->dev,
> +			"Failed to start stats pipeline: %d\n", ret);
> +		goto err_pm_put;
> +	}
> +
> +	if (c3_isp_pipeline_ready(stats->isp)) {
> +		ret = v4l2_subdev_enable_streams(&stats->isp->core.sd,
> +						 C3_ISP_CORE_PAD_SOURCE_STATS,
> +						 BIT(0));
> +		if (ret)
> +			goto err_pipeline_stop;
> +	}
> +
> +	c3_isp_stats_start(stats);
> +
> +	return 0;
> +
> +err_pipeline_stop:
> +	video_device_pipeline_stop(&stats->vdev);
> +err_pm_put:
> +	pm_runtime_put(stats->isp->dev);
> +	c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_QUEUED);
> +	return ret;
> +}
> +
> +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
> +{
> +	struct c3_isp_stats *stats = vb2_get_drv_priv(q);
> +
> +	guard(mutex)(&stats->isp->lock);
> +
> +	c3_isp_stats_stop(stats);
> +	c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_ERROR);
> +
> +	if (stats->isp->pipe.start_count == 1)
> +		v4l2_subdev_disable_streams(&stats->isp->core.sd,
> +					    C3_ISP_CORE_PAD_SOURCE_STATS,
> +					    BIT(0));
> +
> +	video_device_pipeline_stop(&stats->vdev);
> +	pm_runtime_put(stats->isp->dev);
> +}
> +
> +static const struct vb2_ops isp_stats_vb2_ops = {
> +	.queue_setup = c3_isp_stats_vb2_queue_setup,
> +	.buf_queue = c3_isp_stats_vb2_buf_queue,
> +	.buf_prepare = c3_isp_stats_vb2_buf_prepare,
> +	.buf_init = c3_isp_stats_vb2_buf_init,
> +	.wait_prepare = vb2_ops_wait_prepare,
> +	.wait_finish = vb2_ops_wait_finish,
> +	.start_streaming = c3_isp_stats_vb2_start_streaming,
> +	.stop_streaming = c3_isp_stats_vb2_stop_streaming,
> +};
> +
> +int c3_isp_stats_register(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_stats *stats = &isp->stats;
> +	struct video_device *vdev = &stats->vdev;
> +	struct vb2_queue *vb2_q = &stats->vb2_q;
> +	int ret;
> +
> +	memset(stats, 0, sizeof(*stats));
> +	stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
> +	stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_info);
> +	stats->isp = isp;
> +	INIT_LIST_HEAD(&stats->pending);
> +	spin_lock_init(&stats->buff_lock);
> +
> +	mutex_init(&stats->lock);
> +
> +	snprintf(vdev->name, sizeof(vdev->name), "isp-stats");
> +	vdev->fops = &isp_stats_v4l2_fops;
> +	vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
> +	vdev->v4l2_dev = &isp->v4l2_dev;
> +	vdev->lock = &stats->lock;
> +	vdev->minor = -1;
> +	vdev->queue = vb2_q;
> +	vdev->release = video_device_release_empty;
> +	vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
> +	vdev->vfl_dir = VFL_DIR_RX;
> +	video_set_drvdata(vdev, stats);
> +
> +	vb2_q->drv_priv = stats;
> +	vb2_q->mem_ops = &vb2_dma_contig_memops;
> +	vb2_q->ops = &isp_stats_vb2_ops;
> +	vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
> +	vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
> +	vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
> +	vb2_q->dev = isp->dev;
> +	vb2_q->lock = &stats->lock;
> +	vb2_q->min_queued_buffers = 2;
> +
> +	ret = vb2_queue_init(vb2_q);
> +	if (ret)
> +		goto err_destroy;
> +
> +	stats->pad.flags = MEDIA_PAD_FL_SINK;
> +	ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
> +	if (ret)
> +		goto err_queue_release;
> +
> +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +	if (ret) {
> +		dev_err(isp->dev,
> +			"Failed to register %s: %d\n", vdev->name, ret);
> +		goto err_entity_cleanup;
> +	}
> +
> +	return 0;
> +
> +err_entity_cleanup:
> +	media_entity_cleanup(&vdev->entity);
> +err_queue_release:
> +	vb2_queue_release(vb2_q);
> +err_destroy:
> +	mutex_destroy(&stats->lock);
> +	return ret;
> +}
> +
> +void c3_isp_stats_unregister(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_stats *stats = &isp->stats;
> +
> +	vb2_queue_release(&stats->vb2_q);
> +	media_entity_cleanup(&stats->vdev.entity);
> +	video_unregister_device(&stats->vdev);
> +	mutex_destroy(&stats->lock);
> +}
> +
> +int c3_isp_stats_done(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_stats *stats = &isp->stats;
> +	struct c3_isp_vb2_buffer *buff = stats->buff;
> +	unsigned long flags;
> +
> +	if (!stats->is_streaming)
> +		return -EINVAL;
> +
> +	spin_lock_irqsave(&stats->buff_lock, flags);
> +
> +	if (buff) {
> +		buff->vb.sequence = stats->isp->frm_sequence;
> +		buff->vb.vb2_buf.timestamp = ktime_get();
> +		buff->vb.field = V4L2_FIELD_NONE;
> +		vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +	}
> +
> +	c3_isp_stats_cfg_buff(stats);
> +
> +	spin_unlock_irqrestore(&stats->buff_lock, flags);
> +
> +	return 0;
> +}
> diff --git a/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
> new file mode 100644
> index 000000000000..84ff5741357a
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
> @@ -0,0 +1,537 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#ifndef __C3_ISP_CONFIG_H__
> +#define __C3_ISP_CONFIG_H__

When moving to include/uapi/linux/media/amlogic prefix this with
_UAPI_

> +
> +#define AF_STAT_BLKH_NUM             17
> +#define AF_STAT_BLKV_NUM             15
> +#define AF_STAT_BLK_NUM              (AF_STAT_BLKH_NUM * AF_STAT_BLKV_NUM)
> +/* AF stats block size need to be aligned with 2 */
> +#define AF_STAT_BLK_SIZE             ALIGN(AF_STAT_BLK_NUM, 2)
> +#define AE_HISTOGRAM_SIZE	     1024
> +#define AE_STAT_BLKH_NUM             17
> +#define AE_STAT_BLKV_NUM             15
> +#define AE_STAT_BLK_NUM              (AE_STAT_BLKH_NUM * AE_STAT_BLKV_NUM)
> +/* AE stats block size need to be aligned with 2 */
> +#define AE_STAT_BLK_SIZE             ALIGN(AE_STAT_BLK_NUM, 2)
> +#define AE_BLOCK_WT_NUM              255
> +#define AE_BLK_WT_DATA_NUM_OF_GROUP  8
> +#define AWB_STAT_BLKH_NUM            32
> +#define AWB_STAT_BLKV_NUM            24
> +#define AWB_STAT_BLK_NUM             (AWB_STAT_BLKH_NUM * AWB_STAT_BLKV_NUM)
> +/* AWB stats block size need to be aligned with 2 */
> +#define AWB_STAT_BLK_SIZE            ALIGN(AWB_STAT_BLK_NUM, 2)
> +#define AWB_BLOCK_WT_NUM             768
> +#define AWB_BLK_WT_DATA_NUM_OF_GROUP 8
> +#define AWB_STAT_BLC20_NUM           4
> +#define AWB_STAT_GAIN10_NUM          4
> +#define BLC_OFFSET_NUM               5
> +#define GAMMA_LUT_GROUP_NUM          4
> +#define GAMMA_LUT_POINT_NUM          129
> +#define GAMMA_LUT_DATA_NUM_OF_GROUP  2
> +
> +/**
> + * struct awb_zone_stats - AWB statistics of a block
> + *
> + * AWB zone stats is aligned with 8 bytes
> + *
> + * @rg: the ratio of R / G in a zone
> + * @bg: the ratio of B / G in a zone
> + * @pixel_sum: the total number of pixels used in a zone
> + */
> +struct awb_zone_stats {
> +	u16 rg;
> +	u16 bg;
> +	u32 pixel_sum;
> +};
> +
> +/**
> + * struct awb_stats_info - Auto white balance statistics information.
> + *
> + * AWB statistical information of all blocks.
> + *
> + * @awb_stats: array of auto white balance statistics
> + */
> +struct awb_stats_info {
> +	struct awb_zone_stats awb_stats[AWB_STAT_BLK_SIZE];
> +};
> +
> +/**
> + * struct ae_zone_stats - AE statistics of a block
> + *
> + * AE zone stats is aligned with 8 bytes.
> + * This is a 5-bin histogram and the total sum is
> + * normalized to 0xffff.
> + * So hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4)
> + *
> + * @hist0: the global normalized pixel count for bin 0
> + * @hist1: the global normalized pixel count for bin 1
> + * @hist3: the global normalized pixel count for bin 3
> + * @hist4: the global normalized pixel count for bin 4
> + */
> +struct ae_zone_stats {
> +	u16 hist0;

Should you include  <linux/types.h> and use types prefixed with __
(__u16) ?

> +	u16 hist1;
> +	u16 hist3;
> +	u16 hist4;
> +};
> +
> +/**
> + * struct ae_stats_info - Exposure statistics information
> + *
> + * AE statistical information consists of
> + * all blocks information and a 1024-bin histogram.
> + *
> + * @ae_stats: array of auto exposure block statistics
> + * @hist: a 1024-bin histogram for the entire image
> + */
> +struct ae_stats_info {
> +	struct ae_zone_stats ae_stats[AE_STAT_BLK_SIZE];
> +	u32 hist[AE_HISTOGRAM_SIZE];
> +};
> +
> +/**
> + * struct af_zone_stats - AF statistics of a block
> + *
> + * AF block stats is aligned with 8 bytes.
> + * The zonal accumulated contrast metrics are stored
> + * in floating point format with 16 bits mantissa and
> + * 5 or 6 bits exponent.
> + * Apart from contrast metrics we accumulate squared image and
> + * quartic image data over the zone.
> + *
> + * @i2_mat: the mantissa of zonal squared image pixel sum
> + * @i4_mat: the mantissa of zonal quartic image pixel sum
> + * @e4_mat: the mantissa of zonal multi-directional quartic edge sum
> + * @i2_exp: the exponent of zonal squared image pixel sum
> + * @i4_exp: the exponent of zonal quartic image pixel sum
> + * @e4_exp: the exponent of zonal multi-directional quartic edge sum
> + */
> +struct af_zone_stats {
> +	u16 i2_mat;
> +	u16 i4_mat;
> +	u16 e4_mat;
> +	u16 i2_exp: 5;
> +	u16 i4_exp: 6;
> +	u16 e4_exp: 5;
> +};
> +
> +/**
> + * struct af_stats_info - Auto Focus statistics information
> + *
> + * AF statistical information of each block
> + *
> + * @af_stats: array of auto focus block statistics
> + */
> +struct af_stats_info {
> +	struct af_zone_stats af_stats[AF_STAT_BLK_SIZE];
> +};
> +
> +/**
> + * struct c3_isp_stats_info - V4L2_META_FMT_C3ISP_STATS
> + *
> + * Contains ISP statistics
> + *
> + * @awb_stats: auto white balance stats
> + * @ae_stats: auto exposure stats
> + * @af_stats: auto focus stats
> + */
> +struct c3_isp_stats_info {
> +	struct awb_stats_info awb_stats;
> +	struct ae_stats_info ae_stats;
> +	struct af_stats_info af_stats;
> +};
> +
> +/**
> + * enum c3_isp_param_buffer_version -  C3 ISP parameters block versioning
> + *
> + * @C3_ISP_PARAM_BUFFER_V0: First version of C3 ISP parameters block
> + */
> +enum c3_isp_param_buffer_version {
> +	C3_ISP_PARAM_BUFFER_V0,
> +};
> +
> +/**
> + * enum c3_isp_param_block_type - Enumeration of C3 ISP parameter blocks
> + *
> + * Each block configures a specific processing block of the C3 ISP.
> + * The block type allows the driver to correctly interpret
> + * the parameters block data.
> + *
> + * @C3_ISP_PARAM_BLOCK_WB_CHANGE: White balance change parameters
> + * @C3_ISP_PARAM_BLOCK_WB_LUMA: White balance Luma-based parameters
> + * @C3_ISP_PARAM_BLOCK_WB_TRIANGLE: White balance triangle parameters
> + * @C3_ISP_PARAM_BLOCK_AWB_STATS: AWB statistics parameters
> + * @C3_ISP_PARAM_BLOCK_AE_STATS: AE statistics parameters
> + * @C3_ISP_PARAM_BLOCK_AF_STATS: AF statistics parameters
> + * @C3_ISP_PARAM_BLOCK_PST_GAMMA: post gamma parameters
> + * @C3_ISP_PARAM_BLOCK_DMSC: demosaic parameters
> + * @C3_ISP_PARAM_BLOCK_CCM: Color correction matrix parameters
> + * @C3_ISP_PARAM_BLOCK_CSC: Color space conversion parameters
> + * @C3_ISP_PARAM_BLOCK_BLC: Black level correction parameters
> + * @C3_ISP_PARAM_BLOCK_SENTINEL: First non-valid block index
> + */
> +enum c3_isp_param_block_type {
> +	C3_ISP_PARAM_BLOCK_WB_CHANGE,
> +	C3_ISP_PARAM_BLOCK_WB_LUMA,
> +	C3_ISP_PARAM_BLOCK_WB_TRIANGLE,
> +	C3_ISP_PARAM_BLOCK_AWB_STATS,
> +	C3_ISP_PARAM_BLOCK_AE_STATS,
> +	C3_ISP_PARAM_BLOCK_AF_STATS,
> +	C3_ISP_PARAM_BLOCK_PST_GAMMA,
> +	C3_ISP_PARAM_BLOCK_DMSC,
> +	C3_ISP_PARAM_BLOCK_CCM,
> +	C3_ISP_PARAM_BLOCK_CSC,
> +	C3_ISP_PARAM_BLOCK_BLC,
> +	C3_ISP_PARAM_BLOCK_SENTINEL
> +};
> +
> +/**
> + * struct c3_isp_param_block_header - C3 ISP parameter block header
> + *
> + * This structure represents the common part of all the ISP configuration
> + * blocks. Each parameters block shall embed an instance of this structure type
> + * as its first member, followed by the block-specific configuration data. The
> + * driver inspects this common header to discern the block type and its size and
> + * properly handle the block content by casting it to the correct block-specific
> + * type.
> + *
> + * @type: The parameters block type (enum c3_isp_param_block_type)
> + * @enabled: Block enabled/disabled flag
> + * @size: Size (in bytes) of parameters block
> + */
> +
> +struct c3_isp_param_block_header {
> +	enum c3_isp_param_block_type type;
> +	bool enabled;
> +	size_t size;
> +};

What is the size of this structure ? Is it aligned or does the
compiler inserts padding bytes ? In general, I would try to align
everything to 8 bytes to avoid the compiler inserting padding bytes.

A tool that can help you identifies holes is pahole. Just write a
small userspace program that includes types from this header and
declare a variable of each type of defined in this header. Pass the
executable to pahole and it will show the memory layout of each
member.

> +
> +/**
> + * struct wb_change_cfg - White Balance configuration
> + *
> + * @header: The C3 ISP parameters block header
> + * @wb_gain: white balance gain of each color
> + *	wb_gain[0]: Gr gain, range 0~0xfff
> + *	wb_gain[1]: R gain, range 0~0xfff
> + *	wb_gain[2]: B gain, range 0~0xfff
> + *	wb_gain[3]: Gb gain, range 0~0xfff
> + *	wb_gain[4]: Ir gain, range 0~0xfff
> + * @wb_limit: white balance limit of each color
> + *	wb_limit[0]: Gr limit, 16 bits float
> + *	wb_limit[1]: R limit, 16 bits float
> + *	wb_limit[2]: B limit, 16 bits float
> + *	wb_limit[3]: Gb limit, 16 bits float
> + *	wb_limit[4]: Ir limit, 16 bits float
> + * @ae_gain_grbgi: Gain of each color before blending to luma
> + *	ae_gain_grbgi[0]: Gr gain, range 0~255
> + *	ae_gain_grbgi[1]: R gain, range 0~255
> + *	ae_gain_grbgi[2]: B gain, range 0~255
> + *	ae_gain_grbgi[3]: Gb gain, range 0~255
> + *	ae_gain_grbgi[4]: Ir gain, range 0~255
> + * @ae_bl12_grbgi: Offset of each color before ae_gain_grbgi
> + *	ae_bl12_grbgi[0]: Gr offset, range 0~4095
> + *	ae_bl12_grbgi[1]: R offset, range 0~4095
> + *	ae_bl12_grbgi[2]: B offset, range 0~4095
> + *	ae_bl12_grbgi[3]: Gb offset, range 0~4095
> + *	ae_bl12_grbgi[4]: Ir offset, range 0~4095
> + */
> +struct wb_change_cfg {
> +	struct c3_isp_param_block_header header;
> +	u32 wb_gain[5];
> +	u32 wb_limit[5];
> +	u32 ae_gain_grbgi[5];
> +	u32 ae_bl12_grbgi[5];
> +};
> +
> +/**
> + * struct wb_luma_cfg - White Balance Luma-based configuration
> + *
> + * @header: The C3 ISP parameters block header
> + * @awb_stat_blc20: BLC in AWB statistic
> + *	awb_stat_blc20[0]: Gr blc, range 0~0xfffff
> + *	awb_stat_blc20[1]: R blc, range 0~0xfffff
> + *	awb_stat_blc20[2]: B blc, range 0~0xfffff
> + *	awb_stat_blc20[3]: Gb blc, range 0~0xfffff
> + * @awb_stat_gain10: Gain in AWB statistic
> + *	awb_stat_gain10[0]: Gr gain, range 0~1023
> + *	awb_stat_gain10[1]: R gain, range 0~1023
> + *	awb_stat_gain10[2]: B gain, range 0~1023
> + *	awb_stat_gain10[3]: Gb gain, range 0~1023
> + * @awb_stat_satur_low: AWB statistic under-saturation threshold
> + *	value: range 0~65535
> + * @awb_stat_satur_high: AWB statistic over-saturation threshold
> + *	value: range 0~65535
> + */
> +struct wb_luma_cfg {
> +	struct c3_isp_param_block_header header;
> +	u32 awb_stat_blc20[AWB_STAT_BLC20_NUM];
> +	u32 awb_stat_gain10[AWB_STAT_GAIN10_NUM];
> +	u32 awb_stat_satur_low;
> +	u32 awb_stat_satur_high;
> +};
> +
> +/**
> + * struct wb_triangle_cfg - White Balance Triangle
> + *
> + * @header: The C3 ISP parameters block header
> + * @awb_stat_satur_vald: AWB statistic over saturation control
> + *	value: 0: disable, 1: enable
> + * @awb_stat_rg_min: min value of r/g
> + *	value: 0~4095
> + * @awb_stat_rg_max: max value of r/g
> + *	value: 0~4095
> + * @awb_stat_bg_min: min value of b/g
> + *	value: 0~4095
> + * @awb_stat_bg_max: max value of b/g
> + *	value: 0~4095
> + * @awb_stat_rg_low: low value of r/g
> + *	value: 0~4095
> + * @awb_stat_rg_high: high value of r/g
> + *	value: 0~4095
> + * @awb_stat_bg_low: low value of b/g
> + *	value: 0~4095
> + * @awb_stat_bg_high: high value of b/g
> + *	value: 0~4095
> + */
> +struct wb_triangle_cfg {
> +	struct c3_isp_param_block_header header;
> +	u32 awb_stat_satur_vald;
> +	u32 awb_stat_rg_min;
> +	u32 awb_stat_rg_max;
> +	u32 awb_stat_bg_min;
> +	u32 awb_stat_bg_max;
> +	u32 awb_stat_rg_low;
> +	u32 awb_stat_rg_high;
> +	u32 awb_stat_bg_low;
> +	u32 awb_stat_bg_high;
> +};
> +
> +/**
> + * struct awb_stats_cfg - AWB statistics configuration
> + *
> + * This structure contains AWB statistics control information.
> + *
> + * @header: The C3 ISP parameters block header
> + * @awb_stat_switch: the switch of AWB statistics
> + *	value: 0~7
> + * @awb_stat_blk_weight: Array of weights for AWB statistics blocks
> + *	value: 0~15
> + */
> +struct awb_stats_cfg {
> +	struct c3_isp_param_block_header header;
> +	u8 awb_stat_switch;
> +	u32 awb_stat_blk_weight[AWB_BLOCK_WT_NUM];
> +};
> +
> +/**
> + * struct ae_stats_cfg - AE statistics configuration
> + *
> + * This structure contains AE statistics control information.
> + *
> + * @header: The C3 ISP parameters block header
> + * @ae_stat_switch: the switch of AE statistics
> + *	value: 0~3
> + * @ae_stat_blk_weight: Array of weights for AE statistics blocks
> + *	value: 0~15
> + */
> +struct ae_stats_cfg {
> +	struct c3_isp_param_block_header header;
> +	u8 ae_stat_switch;
> +	u32 ae_stat_blk_weight[AE_BLOCK_WT_NUM];
> +};
> +
> +/**
> + * struct af_stats_cfg - AF statistics configuration
> + *
> + * This structure contains AF statistics control information.
> + *
> + * @header: The C3 ISP parameters block header
> + * @af_stat_switch: the switch of AF statistics
> + *	value: 0~3
> + */
> +struct af_stats_cfg {
> +	struct c3_isp_param_block_header header;
> +	u8 af_stat_switch;
> +};
> +
> +/**
> + * struct pst_gamma_cfg - Post gamma configuration
> + *
> + * This structure contains post gamma parameters
> + *
> + * @header: The C3 ISP parameters block header
> + * @pst_gamma_lut: LUT for P-Stitch gamma
> + *	value: 0~65535
> + */
> +struct pst_gamma_cfg {
> +	struct c3_isp_param_block_header header;
> +	u32 pst_gamma_lut[GAMMA_LUT_GROUP_NUM][GAMMA_LUT_POINT_NUM];
> +};
> +
> +/**
> + * struct dmsc_cfg - Demosaic configuration
> + *
> + * This structure contains demosaic parameters
> + *
> + * @header: The C3 ISP parameters block header
> + */
> +struct dmsc_cfg {
> +	struct c3_isp_param_block_header header;
> +};
> +
> +/**
> + * struct ccm_cfg - ISP CCM configuration
> + *
> + * This structure holds the parameters for configuring the CCM,
> + * which is used for color correction.
> + *
> + * @header: The C3 ISP parameters block header
> + * @ccm_4x3matrix: A 3x4 matrix used for color correction
> + *	value: 0~8191
> + */
> +struct ccm_cfg {
> +	struct c3_isp_param_block_header header;
> +	u32 ccm_4x3matrix[3][4];
> +};
> +
> +/**
> + * struct csc_cfg - ISP Color Space Conversion configuration
> + *
> + * This structure contains settings for color space conversion.
> + *
> + * @header: The C3 ISP parameters block header
> + * @cm0_offset_inp: Input offset values for the 0-order color matrix
> + *	value: 0~8191
> + * @cm0_offset_oup: Output offset values for the 0-order color matrix
> + *	value: 0~8191
> + * @cm0_3x3mtrx_rs: matrix right shift for cm0
> + *	value: 0~3
> + * @cm0_3x3matrix: A 3x3 matrix used for the color matrix
> + *	value: 0~8191
> + */
> +struct csc_cfg {
> +	struct c3_isp_param_block_header header;
> +	u32 cm0_offset_inp[3];
> +	u32 cm0_offset_oup[3];
> +	u32 cm0_3x3mtrx_rs;
> +	u32 cm0_3x3matrix[3][3];
> +};
> +
> +/**
> + * struct blc_cfg - ISP Black Level Correction (BLC) configuration
> + *
> + * This structure holds the parameters for BLC in image processing.
> + *
> + * @header: The C3 ISP parameters block header
> + * @fe_bl_ofst: Array of front-end BLC offsets for each color each channels
> + *	fe_bl_ofst[0]: Gr blc offset, range 0~0x1fffff
> + *	fe_bl_ofst[1]: R blc offset, range 0~0x1fffff
> + *	fe_bl_ofst[2]: B blc offset, range 0~0x1fffff
> + *	fe_bl_ofst[3]: Gb blc offset, range 0~0x1fffff
> + *	fe_bl_ofst[4]: Ir blc offset, range 0~0x1fffff
> + * @blc_ofst: Array of LSWB BLC offsets
> + *	blc_ofst[0]: Gr blc offset, 16 bits float
> + *	blc_ofst[1]: R blc offset, 16 bits float
> + *	blc_ofst[2]: B blc offset, 16 bits float
> + *	blc_ofst[3]: Gb blc offset, 16 bits float
> + *	blc_ofst[4]: Ir blc offset, 16 bits float
> + */
> +struct blc_cfg {
> +	struct c3_isp_param_block_header header;
> +	u32 fe_bl_ofst[BLC_OFFSET_NUM];
> +	u32 blc_ofst[BLC_OFFSET_NUM];
> +};
> +
> +/**
> + * define C3_ISP_PARAMS_MAX_SIZE - Maximum size of all C3 ISP Parameters
> + *
> + * Though the parameters for the C3 ISP are passed as optional blocks, the
> + * driver still needs to know the absolute maximum size so that it can allocate
> + * a buffer sized appropriately to accommodate userspace attempting to set all
> + * possible parameters in a single frame.
> + */
> +#define C3_ISP_PARAMS_MAX_SIZE                 \
> +	(sizeof(struct wb_change_cfg) +        \
> +	sizeof(struct wb_luma_cfg)   +         \
> +	sizeof(struct wb_triangle_cfg) +       \
> +	sizeof(struct awb_stats_cfg) +          \
> +	sizeof(struct ae_stats_cfg) +           \
> +	sizeof(struct af_stats_cfg) +           \
> +	sizeof(struct pst_gamma_cfg) +         \
> +	sizeof(struct dmsc_cfg) +              \
> +	sizeof(struct ccm_cfg) +               \
> +	sizeof(struct csc_cfg) +               \
> +	sizeof(struct blc_cfg))
> +
> +/**
> + * struct c3_isp_params_buffer - C3 ISP configuration parameters
> + *
> + * This struct contains the configuration parameters of the C3 ISP
> + * algorithms, serialized by userspace into an opaque data buffer. Each
> + * configuration parameter block is represented by a block-specific structure
> + * which contains a :c:type:`c3_isp_param_block_header` entry as first
> + * member. Userspace populates the @data buffer with configuration parameters
> + * for the blocks that it intends to configure. As a consequence, the data
> + * buffer effective size changes according to the number of ISP blocks that
> + * userspace intends to configure.
> + *
> + * The parameters buffer is versioned by the @version field to allow modifying
> + * and extending its definition. Userspace should populate the @version field to
> + * inform the driver about the version it intends to use. The driver will parse
> + * and handle the @data buffer according to the data layout specific to the
> + * indicated revision and return an error if the desired revision is not
> + * supported.
> + *
> + * For each ISP block that userspace wants to configure, a block-specific
> + * structure is appended to the @data buffer, one after the other without gaps
> + * in between nor overlaps. Userspace shall populate the @total_size field with
> + * the effective size, in bytes, of the @data buffer.
> + *
> + * The expected memory layout of the parameters buffer is::
> + *
> + *	+-------------------- struct c3_isp_params_buffer ------------------+
> + *	| version = C3_ISP_PARAM_BUFFER_V0;                                   |
> + *	| total_size = sizeof(sizeof(struct wb_change_cfg))                   |
> + *	|              sizeof(sizeof(struct wb_luma_cfg));                    |
> + *	| +------------------------- data  ---------------------------------+ |
> + *	| | +------------------ struct wb_change_cfg) --------------------+ | |
> + *	| | | +---------  struct c3_isp_param_block_header header  -----+ | | |
> + *	| | | | type = C3_ISP_PARAM_BLOCK_WB_CHANGE;                    | | | |
> + *	| | | | enabled = true;                                            | | | |
> + *	| | | | size =                                                  | | | |
> + *	| | | |    sizeof(struct c3_isp_param_block_header header);     | | | |
> + *	| | | +---------------------------------------------------------+ | | |
> + *	| | | wb_gain[5] = ...;                                           | | |
> + *	| | | wb_limit[5] = ...;                                          | | |
> + *	| | | ae_gain_grbgi[5] = ...;                                     | | |
> + *	| | | ae_bl12_grbgi[5] = ...;                                     | | |
> + *	| | +------------------ struct wb_luma_cfg -----------------------+ | |
> + *	| | | +---------- struct c3_isp_param_block_header header ------+ | | |
> + *	| | | | type = C3_ISP_PARAM_BLOCK_WB_LUMA;                      | | | |
> + *	| | | | enabled = true;                                            | | | |
> + *	| | | | size = sizeof(struct wb_luma_cfg);                      | | | |
> + *	| | | +---------------------------------------------------------+ | | |
> + *	| | | awb_stat_blc20[4] = ...;                                    | | |
> + *	| | | awb_stat_gain10[4] = ...;                                   | | |
> + *	| | | awb_stat_satur_low = ...;                                   | | |
> + *	| | | awb_stat_satur_high = ...;                                  | | |
> + *	| | +-------------------------------------------------------------+ | |
> + *	| +-----------------------------------------------------------------+ |
> + *	+---------------------------------------------------------------------+
> + *
> + * @version: The C3 ISP parameters buffer version
> + * @total_size: The C3 ISP configuration data effective size,
> + *		excluding this header
> + * @data: The C3 ISP configuration blocks data
> + */
> +struct c3_isp_params_buffer {
> +	enum c3_isp_param_buffer_version version;
> +	size_t total_size;
> +	u8 data[C3_ISP_PARAMS_MAX_SIZE];
> +};
> +
> +#endif
>
> --
> 2.46.1
>
>
>

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

* Re: [PATCH v3 7/9] media: platform: Add c3 ISP driver
  2024-11-07 16:03   ` Jacopo Mondi
@ 2024-11-07 16:15     ` Jacopo Mondi
  2024-11-08 12:38       ` Keke Li
  2024-11-08 12:34     ` Keke Li
  1 sibling, 1 reply; 37+ messages in thread
From: Jacopo Mondi @ 2024-11-07 16:15 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: keke.li, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi again Keke

On Thu, Nov 07, 2024 at 05:03:45PM +0100, Jacopo Mondi wrote:
> Hi Keke
>
>    a first pass of review without going into details about the
> ISP parameters and stats but mostly on architecture.
>
> On Wed, Sep 18, 2024 at 02:07:18PM +0800, Keke Li via B4 Relay wrote:
> > From: Keke Li <keke.li@amlogic.com>
> >
> > The C3 ISP supports multi-camera and muti-exposure
> > high dynamic range (HDR). It brings together some
> > advanced imaging technologies to provide good image quality.
> > This driver mainly responsible for driving ISP pipeline
> > to process raw image.
> >
> > Signed-off-by: Keke Li <keke.li@amlogic.com>
> > ---
> >  drivers/media/platform/amlogic/Kconfig             |   1 +
> >  drivers/media/platform/amlogic/Makefile            |   1 +
> >  drivers/media/platform/amlogic/c3-isp/Kconfig      |  17 +
> >  drivers/media/platform/amlogic/c3-isp/Makefile     |  10 +
> >  .../media/platform/amlogic/c3-isp/c3-isp-capture.c | 759 ++++++++++++++++++
> >  .../media/platform/amlogic/c3-isp/c3-isp-common.h  | 327 ++++++++
> >  .../media/platform/amlogic/c3-isp/c3-isp-core.c    | 675 ++++++++++++++++
> >  drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c | 486 ++++++++++++
> >  .../media/platform/amlogic/c3-isp/c3-isp-params.c  | 857 +++++++++++++++++++++
> >  .../media/platform/amlogic/c3-isp/c3-isp-regs.h    | 683 ++++++++++++++++
> >  .../media/platform/amlogic/c3-isp/c3-isp-resizer.c | 768 ++++++++++++++++++
> >  .../media/platform/amlogic/c3-isp/c3-isp-stats.c   | 488 ++++++++++++
> >  .../amlogic/c3-isp/include/uapi/c3-isp-config.h    | 537 +++++++++++++

Let me add a link to the conversation I had when I introduced
extensible format in RkISP1 about the alignment and padding of uAPI
types:
https://lore.kernel.org/all/20240724085004.82694-1-jacopo.mondi@ideasonboard.com/T/#m5333e7b6ab4230bb32efa063ab6fd73a529fdc29

I think the takeaway is:
- Make sure there are no holes, and if any replace them with
  reserved[] bytes
- Preferably align to 8-bytes

>
> This really should be in include/uapi/linux/media/amlogic
> so that it is made available to userspace.
>
> >  13 files changed, 5609 insertions(+)
> >
> > diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
> > index df09717b28d0..ebda6b7edc2d 100644
> > --- a/drivers/media/platform/amlogic/Kconfig
> > +++ b/drivers/media/platform/amlogic/Kconfig
> > @@ -2,6 +2,7 @@
> >
> >  comment "Amlogic media platform drivers"
> >
> > +source "drivers/media/platform/amlogic/c3-isp/Kconfig"
> >  source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
> >  source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>
> I start wondering if this shouldn't rather be organized in an
> drivers/media/platform/amlogic/c3/ folder with an
> drivers/media/platform/amlogic/c3/isp/ subfolder
>
> Do you know if any of the csi-2-rx, adapter or ISP can
> be used in other SoCs ?
>
> >  source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
> > diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
> > index b370154b090c..d0d9363d4d8d 100644
> > --- a/drivers/media/platform/amlogic/Makefile
> > +++ b/drivers/media/platform/amlogic/Makefile
> > @@ -1,5 +1,6 @@
> >  # SPDX-License-Identifier: GPL-2.0-only
> >
> > +obj-y += c3-isp/
> >  obj-y += c3-mipi-adapter/
> >  obj-y += c3-mipi-csi2/
> >  obj-y += meson-ge2d/
> > diff --git a/drivers/media/platform/amlogic/c3-isp/Kconfig b/drivers/media/platform/amlogic/c3-isp/Kconfig
> > new file mode 100644
> > index 000000000000..e317c1e81750
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-isp/Kconfig
> > @@ -0,0 +1,17 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +
> > +config VIDEO_C3_ISP
> > +	tristate "Amlogic C3 Image Signal Processor (ISP) driver"
> > +	depends on ARCH_MESON || COMPILE_TEST
> > +	depends on VIDEO_DEV
> > +	depends on OF
> > +	select MEDIA_CONTROLLER
> > +	select V4L2_FWNODE
> > +	select VIDEO_V4L2_SUBDEV_API
> > +	select VIDEOBUF2_DMA_CONTIG
> > +	help
> > +	  Video4Linux2 driver for Amlogic C3 ISP pipeline.
> > +	  C3 ISP pipeline mainly for processing raw image
>
>           The C3 ISP is used for processing raw images
> > +	  and output result to memory.
>
> Or something similar, but "C3 ISP pipeline mainly for processing"
> doesn't sound right to be
>
> > +
> > +	  To compile this driver as a module choose m here.
> > diff --git a/drivers/media/platform/amlogic/c3-isp/Makefile b/drivers/media/platform/amlogic/c3-isp/Makefile
> > new file mode 100644
> > index 000000000000..b1b064170b57
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-isp/Makefile
> > @@ -0,0 +1,10 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +
> > +c3-isp-objs := c3-isp-dev.o \
> > +	       c3-isp-params.o \
> > +	       c3-isp-stats.o \
> > +	       c3-isp-capture.o \
> > +	       c3-isp-core.o \
> > +	       c3-isp-resizer.o
> > +
> > +obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
> > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
> > new file mode 100644
> > index 000000000000..ee9a7a17a203
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
> > @@ -0,0 +1,759 @@
> > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > +/*
> > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > + */
> > +
> > +#include <linux/cleanup.h>
> > +#include <linux/pm_runtime.h>
> > +
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-event.h>
> > +#include <media/v4l2-ioctl.h>
> > +#include <media/v4l2-mc.h>
> > +#include <media/videobuf2-dma-contig.h>
> > +
> > +#include "c3-isp-common.h"
> > +#include "c3-isp-regs.h"
> > +
> > +static const struct c3_isp_capture_format cap_formats[] = {
> > +	{
> > +		.mbus_code = MEDIA_BUS_FMT_Y8_1X8,
> > +		.fourcc = V4L2_PIX_FMT_GREY,
> > +		.depth = 8,
> > +	},
> > +	{
> > +		.mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
>
> Does the 5X8 version represents the format on the internal bus between
> the resizers and the capture device ?
>
> How does format propagation work from the ISP to the resizers and the
> capture devices ? I mean, is there an internal bus where the number of
> samples (5X8 vs 2X8) changes depending on the output format ?
>
> > +		.fourcc = V4L2_PIX_FMT_NV12,
> > +		.depth = 12,
> > +	}, {
> > +		.mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
> > +		.fourcc = V4L2_PIX_FMT_NV21,
> > +		.depth = 12,
> > +	}, {
> > +		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> > +		.fourcc = V4L2_PIX_FMT_NV16,
> > +		.depth = 16,
> > +	}, {
> > +		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> > +		.fourcc = V4L2_PIX_FMT_NV61,
> > +		.depth = 16,
> > +	},
> > +};
> > +
> > +/* Hardware configuration */
> > +
> > +/* Set the address of wrmifx3(write memory interface) */
> > +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
> > +{
> > +	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> > +	struct c3_isp_vb2_buffer *buff = cap->buff;
> > +	u32 offset;
> > +
> > +	c3_isp_write(cap->isp,
> > +		     C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
> > +		     WRMIFX3_CH0_BADDR(buff->paddr));
> > +
> > +	if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
> > +	    pix->pixelformat == V4L2_PIX_FMT_NV21 ||
> > +	    pix->pixelformat == V4L2_PIX_FMT_NV16 ||
> > +	    pix->pixelformat == V4L2_PIX_FMT_NV61) {
> > +		offset = pix->width * pix->height;
> > +		c3_isp_write(cap->isp,
> > +			     C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
> > +			     WRMIFX3_CH1_BADDR(buff->paddr + offset));
> > +	}
> > +}
> > +
> > +static void c3_isp_cap_output_size(struct c3_isp_capture *cap)
> > +{
> > +	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> > +
> > +	c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
> > +			   DISP_OUT_VSIZE_MASK, pix->height);
> > +	c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
> > +			   DISP_OUT_HSIZE_MASK, pix->width << DISP_OUT_HSIZE_SHIFT);
> > +}
> > +
> > +static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
> > +{
> > +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> > +	u32 stride;
> > +
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > +			   WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
> > +
> > +	/* Grey has 1 plane*/
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > +			   WRMIFX3_FMT_MTX_PLANE_MASK,
> > +			   WRMIFX3_FMT_MTX_PLANE_X1 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
> > +
> > +	/* Set Y only as output format */
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > +			   WRMIFX3_FMT_MODE_OUT_MASK,
> > +			   WRMIFX3_FMT_MODE_OUT_Y_ONLY << WRMIFX3_FMT_MODE_OUT_SHIFT);
> > +
> > +	/* The unit of stride is 128 bits */
> > +	stride = DIV_ROUND_UP(fmt->width * 8, 128);
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> > +			   WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
> > +
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> > +			   WRMIFX3_CH0_PIX_BITS_MODE_MASK,
> > +			   WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
> > +}
> > +
> > +static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, u32 swap_uv)
> > +{
> > +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> > +	u32 stride;
> > +
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > +			   WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > +			   WRMIFX3_FMT_MTX_UV_SWAP_MASK,
> > +			   swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
> > +
> > +	/* NV12 or NV21 has 2 planes*/
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > +			   WRMIFX3_FMT_MTX_PLANE_MASK,
> > +			   WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
> > +
> > +	/* Set YUV420 as output format */
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > +			   WRMIFX3_FMT_MODE_OUT_MASK,
> > +			   WRMIFX3_FMT_MODE_OUT_YUV420 << WRMIFX3_FMT_MODE_OUT_SHIFT);
> > +
> > +	/* The unit of stride is 128 bits */
> > +	stride = DIV_ROUND_UP(fmt->width * 8, 128);
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> > +			   WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
> > +			   WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
> > +
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> > +			   WRMIFX3_CH0_PIX_BITS_MODE_MASK,
> > +			   WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
> > +			   WRMIFX3_CH1_PIX_BITS_MODE_MASK,
> > +			   WRMIFX3_CH1_PIX_BITS_16BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
> > +}
> > +
> > +static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, u32 swap_uv)
> > +{
> > +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> > +	u32 stride;
> > +
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > +			   WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_16BIT);
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > +			   WRMIFX3_FMT_MTX_UV_SWAP_MASK,
> > +			   swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
> > +
> > +	/* NV16 or NV61 has 2 planes*/
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > +			   WRMIFX3_FMT_MTX_PLANE_MASK,
> > +			   WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
> > +
> > +	/* Set YUV422 as output format */
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > +			   WRMIFX3_FMT_MODE_OUT_MASK,
> > +			   WRMIFX3_FMT_MODE_OUT_YUV422 << WRMIFX3_FMT_MODE_OUT_SHIFT);
> > +
> > +	/* The unit of stride is 128 bits */
> > +	stride = DIV_ROUND_UP(fmt->width * 16, 128);
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> > +			   WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
> > +			   WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
> > +
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> > +			   WRMIFX3_CH0_PIX_BITS_MODE_MASK,
> > +			   WRMIFX3_CH0_PIX_BITS_16BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
> > +			   WRMIFX3_CH1_PIX_BITS_MODE_MASK,
> > +			   WRMIFX3_CH1_PIX_BITS_32BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
> > +}
> > +
> > +static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
> > +{
> > +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> > +
> > +	if (fmt->pixelformat == V4L2_PIX_FMT_GREY) {
> > +		c3_isp_cap_wrmifx3_grey(cap);
> > +	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV12) {
> > +		c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
> > +	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV21) {
> > +		c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
> > +	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV16) {
> > +		c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
> > +	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV61) {
> > +		c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
> > +	} else {
> > +		dev_err(cap->isp->dev, "Invalid pixel format: 0x%x\n", fmt->pixelformat);
> > +		return;
> > +	}
> > +
> > +	c3_isp_write(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id),
> > +		     WRMIFX3_FMT_HSIZE(fmt->width) | WRMIFX3_FMT_VSIZE(fmt->height));
> > +
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
> > +			   WRMIFX3_WIN_LUMA_HEND_MASK,
> > +			   WRMIFX3_WIN_LUMA_HEND(fmt->width) << WRMIFX3_WIN_LUMA_HEND_SHIFT);
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
> > +			   WRMIFX3_WIN_LUMA_VEND_MASK,
> > +			   WRMIFX3_WIN_LUMA_VEND(fmt->height) << WRMIFX3_WIN_LUMA_VEND_SHIFT);
> > +
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_HSIZE, cap->id),
> > +			   WRMIFX3_CROP_HEND_MASK,
> > +			   WRMIFX3_CROP_HEND(fmt->width) << WRMIFX3_CROP_HEND_SHIFT);
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_VSIZE, cap->id),
> > +			   WRMIFX3_CROP_VEND_MASK,
> > +			   WRMIFX3_CROP_VEND(fmt->height) << WRMIFX3_CROP_VEND_SHIFT);
> > +
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
> > +			   WRMIFX3_WIN_CHROM_HEND_MASK,
> > +			   WRMIFX3_WIN_CHROM_HEND(fmt->width) << WRMIFX3_WIN_CHROM_HEND_SHIFT);
> > +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
> > +			   WRMIFX3_WIN_CHROM_VEND_MASK,
> > +			   WRMIFX3_WIN_CHROM_VEND(fmt->height) << WRMIFX3_WIN_CHROM_VEND_SHIFT);
> > +}
> > +
> > +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)
>
> This function is called from two locations, one with the buff_lock
> held, the other one without, if I'm not mistaken.
>
> > +{
> > +	cap->buff = list_first_entry_or_null(&cap->pending,
> > +					     struct c3_isp_vb2_buffer, list);
> > +	if (cap->buff) {
> > +		c3_isp_cap_wrmifx3_buff(cap);
> > +		list_del(&cap->buff->list);
> > +	}
> > +}
> > +
> > +static void c3_isp_cap_start(struct c3_isp_capture *cap)
> > +{
> > +	c3_isp_cap_cfg_buff(cap);
> > +
> > +	c3_isp_cap_output_size(cap);
> > +	c3_isp_cap_wrmifx3_size(cap);
> > +
> > +	c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN,
> > +			   TOP_WRMIF_EN(cap->id), TOP_WRMIF_EN(cap->id));
> > +
> > +	cap->is_streaming = true;
> > +}
> > +
> > +static void c3_isp_cap_stop(struct c3_isp_capture *cap)
> > +{
> > +	cap->is_streaming = false;
> > +
> > +	c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, TOP_WRMIF_EN(cap->id), 0);
> > +}
> > +
> > +static int c3_isp_cap_done(struct c3_isp_capture *cap)
> > +{
> > +	struct c3_isp_vb2_buffer *buff = cap->buff;
> > +	unsigned long flags;
> > +
> > +	if (!cap->is_streaming)
> > +		return -EINVAL;
>
> Is this an error condition or 0 should be returnes ?
>
> > +
> > +	spin_lock_irqsave(&cap->buff_lock, flags);
> > +
> > +	if (buff) {
> > +		buff->vb.sequence = cap->isp->frm_sequence;
> > +		buff->vb.vb2_buf.timestamp = ktime_get();
> > +		buff->vb.field = V4L2_FIELD_NONE;
> > +		vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
> > +	}
> > +
> > +	c3_isp_cap_cfg_buff(cap);
> > +
> > +	spin_unlock_irqrestore(&cap->buff_lock, flags);
> > +
> > +	return 0;
> > +}
> > +
> > +/* V4L2 video operations */
> > +
> > +static const struct c3_isp_capture_format
> > +*c3_cap_find_fmt(u32 fourcc)
> > +{
> > +	unsigned int i;
> > +
> > +	for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
> > +		if (cap_formats[i].fourcc == fourcc)
> > +			return &cap_formats[i];
> > +	}
> > +
> > +	return NULL;
> > +}
> > +
> > +static void c3_cap_try_fmt(struct c3_isp_capture *cap,
> > +			   struct v4l2_pix_format *pix)
> > +{
> > +	const struct c3_isp_capture_format *fmt;
> > +
> > +	fmt = c3_cap_find_fmt(pix->pixelformat);
> > +	if (!fmt)
> > +		fmt = &cap_formats[0];
> > +
> > +	pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> > +	pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> > +	pix->pixelformat = fmt->fourcc;
> > +	pix->field = V4L2_FIELD_NONE;
> > +	pix->colorspace = V4L2_COLORSPACE_SRGB;
> > +	pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
> > +	pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> > +
> > +	/* ISP hardware requires 16 bytes alignment */
> > +	pix->bytesperline = ALIGN(pix->width, 16);
> > +	pix->sizeimage = pix->bytesperline * pix->height * fmt->depth / 8;
> > +}
> > +
> > +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
> > +				      enum vb2_buffer_state state)
> > +{
> > +	unsigned long flags;
> > +	struct c3_isp_vb2_buffer *buff;
> > +
> > +	spin_lock_irqsave(&cap->buff_lock, flags);
> > +
> > +	if (cap->buff) {
> > +		vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
> > +		cap->buff = NULL;
> > +	}
> > +
> > +	while (!list_empty(&cap->pending)) {
> > +		buff = list_first_entry(&cap->pending,
> > +					struct c3_isp_vb2_buffer, list);
> > +		list_del(&buff->list);
> > +		vb2_buffer_done(&buff->vb.vb2_buf, state);
> > +	}
> > +
> > +	spin_unlock_irqrestore(&cap->buff_lock, flags);
> > +}
> > +
> > +static int c3_isp_cap_querycap(struct file *file, void *fh,
> > +			       struct v4l2_capability *cap)
> > +{
> > +	strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
> > +	strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_cap_enum_fmt(struct file *file, void *fh,
> > +			       struct v4l2_fmtdesc *f)
> > +{
> > +	const struct c3_isp_capture_format *fmt;
> > +	unsigned int index = 0;
> > +	unsigned int i;
> > +
> > +	if (!f->mbus_code) {
> > +		if (f->index >= ARRAY_SIZE(cap_formats))
> > +			return -EINVAL;
> > +
> > +		fmt = &cap_formats[f->index];
> > +		f->pixelformat = fmt->fourcc;
> > +		return 0;
> > +	}
> > +
> > +	for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
> > +		fmt = &cap_formats[i];
> > +		if (f->mbus_code != fmt->mbus_code)
> > +			continue;
> > +
> > +		if (index++ == f->index) {
> > +			f->pixelformat = cap_formats[i].fourcc;
> > +			return 0;
> > +		}
> > +	}
> > +
> > +	return -EINVAL;
> > +}
> > +
> > +static int c3_isp_cap_g_fmt(struct file *file, void *fh,
> > +			    struct v4l2_format *f)
> > +{
> > +	struct c3_isp_capture *cap = video_drvdata(file);
> > +
> > +	f->fmt.pix = cap->vfmt.fmt.pix;
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_cap_s_fmt(struct file *file, void *fh,
> > +			    struct v4l2_format *f)
> > +{
> > +	struct c3_isp_capture *cap = video_drvdata(file);
> > +
> > +	c3_cap_try_fmt(cap, &f->fmt.pix);
> > +	cap->vfmt = *f;
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_cap_try_fmt(struct file *file, void *fh,
> > +			      struct v4l2_format *f)
> > +{
> > +	struct c3_isp_capture *cap = video_drvdata(file);
> > +
> > +	c3_cap_try_fmt(cap, &f->fmt.pix);
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
> > +				   struct v4l2_frmsizeenum *fsize)
> > +{
> > +	const struct c3_isp_capture_format *fmt;
> > +
> > +	if (fsize->index)
> > +		return -EINVAL;
> > +
> > +	fmt = c3_cap_find_fmt(fsize->pixel_format);
> > +	if (!fmt)
> > +		return -EINVAL;
> > +
> > +	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> > +	fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
> > +	fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
> > +	fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
> > +	fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
> > +	fsize->stepwise.step_width = 2;
> > +	fsize->stepwise.step_height = 2;
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
> > +	.vidioc_querycap		= c3_isp_cap_querycap,
> > +	.vidioc_enum_fmt_vid_cap	= c3_isp_cap_enum_fmt,
> > +	.vidioc_g_fmt_vid_cap		= c3_isp_cap_g_fmt,
> > +	.vidioc_s_fmt_vid_cap		= c3_isp_cap_s_fmt,
> > +	.vidioc_try_fmt_vid_cap		= c3_isp_cap_try_fmt,
> > +	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
> > +	.vidioc_querybuf		= vb2_ioctl_querybuf,
> > +	.vidioc_qbuf			= vb2_ioctl_qbuf,
> > +	.vidioc_expbuf			= vb2_ioctl_expbuf,
> > +	.vidioc_dqbuf			= vb2_ioctl_dqbuf,
> > +	.vidioc_prepare_buf		= vb2_ioctl_prepare_buf,
> > +	.vidioc_create_bufs		= vb2_ioctl_create_bufs,
> > +	.vidioc_streamon		= vb2_ioctl_streamon,
> > +	.vidioc_streamoff		= vb2_ioctl_streamoff,
> > +	.vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
> > +	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
> > +	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
> > +};
> > +
> > +static const struct v4l2_file_operations isp_cap_v4l2_fops = {
> > +	.open = v4l2_fh_open,
> > +	.release = vb2_fop_release,
> > +	.poll = vb2_fop_poll,
> > +	.unlocked_ioctl = video_ioctl2,
> > +	.mmap = vb2_fop_mmap,
> > +};
> > +
> > +static int c3_isp_cap_link_validate(struct media_link *link)
> > +{
> > +	struct video_device *vdev =
> > +		media_entity_to_video_device(link->sink->entity);
> > +	struct v4l2_subdev *sd =
> > +		media_entity_to_v4l2_subdev(link->source->entity);
> > +	struct c3_isp_capture *cap = video_get_drvdata(vdev);
> > +	struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
> > +	struct v4l2_subdev_format src_fmt = {
> > +		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
> > +		.pad = link->source->index,
> > +	};
> > +	const struct c3_isp_capture_format *cap_fmt =
> > +				c3_cap_find_fmt(pix_fmt->pixelformat);
> > +	int ret;
> > +
> > +	ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
> > +	if (ret)
> > +		return ret;
> > +
> > +	if (src_fmt.format.width != pix_fmt->width ||
> > +	    src_fmt.format.height != pix_fmt->height ||
> > +	    src_fmt.format.code != cap_fmt->mbus_code) {
> > +		dev_err(cap->isp->dev,
> > +			"link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n",
> > +			link->source->entity->name, link->source->index,
> > +			link->sink->entity->name, link->sink->index,
> > +			src_fmt.format.code, src_fmt.format.width,
> > +			src_fmt.format.height, cap_fmt->mbus_code,
> > +			pix_fmt->width, pix_fmt->height);
> > +
> > +		return -EPIPE;
> > +	};
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct media_entity_operations isp_cap_entity_ops = {
> > +	.link_validate = c3_isp_cap_link_validate,
> > +};
> > +
> > +static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
> > +				  unsigned int *num_buffers,
> > +				  unsigned int *num_planes,
> > +				  unsigned int sizes[],
> > +				  struct device *alloc_devs[])
> > +{
> > +	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> > +	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> > +
> > +	if (*num_planes) {
> > +		if (*num_planes != 1)
> > +			return -EINVAL;
> > +
> > +		if (sizes[0] < pix->sizeimage)
> > +			return -EINVAL;
> > +	} else {
> > +		*num_planes = 1;
> > +		sizes[0] = pix->sizeimage;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
> > +{
> > +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > +	struct c3_isp_vb2_buffer *buf =
> > +			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> > +	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&cap->buff_lock, flags);
> > +
> > +	list_add_tail(&buf->list, &cap->pending);
> > +
> > +	spin_unlock_irqrestore(&cap->buff_lock, flags);
> > +}
> > +
> > +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
> > +{
> > +	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> > +	unsigned int size = cap->vfmt.fmt.pix.sizeimage;
> > +
> > +	if (vb2_plane_size(vb, 0) < size) {
> > +		dev_err(cap->isp->dev,
> > +			"User buffer too small (%ld < %u)\n",
> > +			vb2_plane_size(vb, 0), size);
> > +		return -EINVAL;
> > +	}
> > +
> > +	vb2_set_plane_payload(vb, 0, size);
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
> > +{
> > +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > +	struct c3_isp_vb2_buffer *buf =
> > +		container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> > +	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> > +
> > +	buf->vaddr = vb2_plane_vaddr(vb, 0);
> > +	buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
> > +
> > +	memset(buf->vaddr, 0, cap->vfmt.fmt.pix.sizeimage);
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
> > +				      unsigned int count)
> > +{
> > +	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> > +	int ret;
> > +
> > +	guard(mutex)(&cap->isp->lock);
> > +
> > +	ret = pm_runtime_resume_and_get(cap->isp->dev);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
> > +	if (ret) {
> > +		dev_err(cap->isp->dev,
> > +			"Failed to start cap%u pipeline: %d\n", cap->id, ret);
> > +		goto err_pm_put;
> > +	}
> > +
> > +	if (c3_isp_pipeline_ready(cap->isp)) {
> > +		ret = v4l2_subdev_enable_streams(&cap->isp->core.sd,
> > +						 C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > +						 BIT(0));
> > +		if (ret)
> > +			goto err_pipeline_stop;
> > +	}
> > +
> > +	c3_isp_rsz_start(cap->rsz);
> > +	c3_isp_cap_start(cap);
> > +
> > +	return 0;
> > +
> > +err_pipeline_stop:
> > +	video_device_pipeline_stop(&cap->vdev);
> > +err_pm_put:
> > +	pm_runtime_put(cap->isp->dev);
> > +	c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
> > +	return ret;
> > +}
> > +
> > +static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
> > +{
> > +	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> > +
> > +	guard(mutex)(&cap->isp->lock);
> > +
> > +	c3_isp_cap_stop(cap);
> > +	c3_isp_rsz_stop(cap->rsz);
> > +	c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
> > +
> > +	if (cap->isp->pipe.start_count == 1)
> > +		v4l2_subdev_disable_streams(&cap->isp->core.sd,
> > +					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > +					    BIT(0));
> > +
> > +	video_device_pipeline_stop(&cap->vdev);
> > +	pm_runtime_put(cap->isp->dev);
> > +}
> > +
> > +static const struct vb2_ops isp_video_vb2_ops = {
> > +	.queue_setup = c3_isp_vb2_queue_setup,
> > +	.buf_queue = c3_isp_vb2_buf_queue,
> > +	.buf_prepare = c3_isp_vb2_buf_prepare,
> > +	.buf_init = c3_isp_vb2_buf_init,
> > +	.wait_prepare = vb2_ops_wait_prepare,
> > +	.wait_finish = vb2_ops_wait_finish,
> > +	.start_streaming = c3_isp_vb2_start_streaming,
> > +	.stop_streaming = c3_isp_vb2_stop_streaming,
> > +};
> > +
> > +static int c3_isp_register_capture(struct c3_isp_capture *cap)
> > +{
> > +	struct video_device *vdev = &cap->vdev;
> > +	struct vb2_queue *vb2_q = &cap->vb2_q;
> > +	int ret;
> > +
> > +	snprintf(vdev->name, sizeof(vdev->name), "isp-cap%u", cap->id);
> > +	vdev->fops = &isp_cap_v4l2_fops;
> > +	vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
> > +	vdev->v4l2_dev = &cap->isp->v4l2_dev;
> > +	vdev->entity.ops = &isp_cap_entity_ops;
> > +	vdev->lock = &cap->lock;
> > +	vdev->minor = -1;
> > +	vdev->queue = vb2_q;
> > +	vdev->release = video_device_release_empty;
> > +	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> > +	vdev->vfl_dir = VFL_DIR_RX;
> > +	video_set_drvdata(vdev, cap);
> > +
> > +	vb2_q->drv_priv = cap;
> > +	vb2_q->mem_ops = &vb2_dma_contig_memops;
> > +	vb2_q->ops = &isp_video_vb2_ops;
> > +	vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> > +	vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
> > +	vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> > +	vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
> > +	vb2_q->dev = cap->isp->dev;
> > +	vb2_q->lock = &cap->lock;
> > +	vb2_q->min_queued_buffers = 2;
>
> I'm not sure what your plans regarding libcamera are, but be aware
> we're going to get stricter about this value. Ideally, we aim to have
> ISP driver set this value to 0 so that the ISP operates (produce
> statistics) even if no buffer is queued to the capture devices. The
> reason is that we want algorithms to run even if there are no capture
> buffers provided.
>
> In order to allow the driver to operate with no buffers, you could
> probably allocate a scratch buffer in the driver and use it whenever
> you receive a frame completed IRQ and have no buffers available in the
> cap->provided queue.
>
> See rkisp1_dummy_buf_create() in the RkISP1 driver and how dummy_buf
> is used there.
>
> > +
> > +	ret = vb2_queue_init(vb2_q);
> > +	if (ret < 0)
> > +		goto err_destroy;
> > +
> > +	cap->pad.flags = MEDIA_PAD_FL_SINK;
> > +	ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
> > +	if (ret < 0)
> > +		goto err_queue_release;
> > +
> > +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> > +	if (ret < 0) {
> > +		dev_err(cap->isp->dev,
> > +			"Failed to register %s: %d\n", vdev->name, ret);
> > +		goto err_entity_cleanup;
> > +	}
> > +
> > +	return 0;
> > +
> > +err_entity_cleanup:
> > +	media_entity_cleanup(&vdev->entity);
> > +err_queue_release:
> > +	vb2_queue_release(vb2_q);
> > +err_destroy:
> > +	mutex_destroy(&cap->lock);
> > +	return ret;
> > +}
> > +
> > +int c3_isp_captures_register(struct c3_isp_device *isp)
> > +{
> > +	int ret;
> > +	unsigned int i;
> > +	struct c3_isp_capture *cap;
> > +
> > +	for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
> > +		cap = &isp->caps[i];
> > +		memset(cap, 0, sizeof(*cap));
> > +
> > +		cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
> > +		cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
> > +		cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
> > +		cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
> > +		cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
> > +
> > +		c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
> > +
> > +		cap->id = i;
> > +		if (cap->id == C3_ISP_CAP_DEV_0)
> > +			cap->rsz = &isp->resizers[C3_ISP_RSZ_0];
> > +		else if (cap->id == C3_ISP_CAP_DEV_1)
> > +			cap->rsz = &isp->resizers[C3_ISP_RSZ_1];
> > +		else
> > +			cap->rsz = &isp->resizers[C3_ISP_RSZ_2];
> > +
> > +		cap->isp = isp;
> > +		INIT_LIST_HEAD(&cap->pending);
> > +		spin_lock_init(&cap->buff_lock);
> > +		mutex_init(&cap->lock);
> > +
> > +		ret = c3_isp_register_capture(cap);
> > +		if (ret) {
> > +			cap->isp = NULL;
> > +			mutex_destroy(&cap->lock);
> > +			c3_isp_captures_unregister(isp);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +void c3_isp_captures_unregister(struct c3_isp_device *isp)
> > +{
> > +	unsigned int i;
> > +	struct c3_isp_capture *cap;
> > +
> > +	for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
> > +		cap = &isp->caps[i];
> > +
> > +		if (!cap->isp)
> > +			continue;
> > +		vb2_queue_release(&cap->vb2_q);
> > +		media_entity_cleanup(&cap->vdev.entity);
> > +		video_unregister_device(&cap->vdev);
> > +		mutex_destroy(&cap->lock);
> > +	}
> > +}
> > +
> > +void c3_isp_captures_done(struct c3_isp_device *isp)
> > +{
> > +	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
> > +	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
> > +	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
> > +}
> > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
> > new file mode 100644
> > index 000000000000..19f2a3bc29c9
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
> > @@ -0,0 +1,327 @@
> > +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> > +/*
> > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > + */
> > +
> > +#ifndef __C3_ISP_COMMON_H__
> > +#define __C3_ISP_COMMON_H__
> > +
> > +#include <linux/clk.h>
> > +
> > +#include <media/media-device.h>
> > +#include <media/videobuf2-core.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-subdev.h>
> > +#include <media/videobuf2-v4l2.h>
> > +
> > +#define C3_ISP_DRIVER_NAME            "c3-isp"
> > +#define C3_ISP_CLOCK_NUM_MAX          3
> > +
> > +#define C3_ISP_CORE_DEF_SINK_PAD_FMT  MEDIA_BUS_FMT_SRGGB10_1X10
> > +#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUYV8_1_5X8
> > +#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUYV8_1_5X8
> > +#define C3_ISP_DEFAULT_WIDTH          1920
> > +#define C3_ISP_DEFAULT_HEIGHT         1080
> > +#define C3_ISP_MAX_WIDTH              2888
> > +#define C3_ISP_MAX_HEIGHT             2240
> > +#define C3_ISP_MIN_WIDTH              160
> > +#define C3_ISP_MIN_HEIGHT             120
> > +
> > +#define C3_DISP_INTER                 0x400
> > +#define C3_DISP_REG(base, id)         ((base) + (id) * C3_DISP_INTER)
> > +#define C3_WRMIFX3_INTER              0x100
> > +#define C3_WRMIFX3_REG(base, id)      ((base) + (id) * C3_WRMIFX3_INTER)
> > +#define C3_PPS_TAP4_S11_H_NUM         33
> > +#define C3_PPS_LUT_CTYPE_0            0
> > +#define C3_PPS_LUT_CTYPE_2            2
> > +#define C3_SCALE_EN                   1
> > +#define C3_SCALE_DIS                  0
> > +
> > +#define C3_ISP_PHASE_OFFSET_0         0
> > +#define C3_ISP_PHASE_OFFSET_1         1
> > +#define C3_ISP_PHASE_OFFSET_NONE      0xff
> > +
> > +enum c3_isp_core_pads {
> > +	C3_ISP_CORE_PAD_SINK_VIDEO,
> > +	C3_ISP_CORE_PAD_SINK_PARAMS,
> > +	C3_ISP_CORE_PAD_SOURCE_STATS,
> > +	C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > +	C3_ISP_CORE_PAD_MAX
> > +};
> > +
> > +enum c3_isp_resizer_ids {
> > +	C3_ISP_RSZ_0,
> > +	C3_ISP_RSZ_1,
> > +	C3_ISP_RSZ_2,
> > +	C3_ISP_NUM_RSZ
> > +};
> > +
> > +enum c3_isp_resizer_pads {
> > +	C3_ISP_RESIZER_PAD_SINK,
> > +	C3_ISP_RESIZER_PAD_SOURCE,
> > +	C3_ISP_RESIZER_PAD_MAX
> > +};
> > +
> > +enum c3_isp_cap_devs {
> > +	C3_ISP_CAP_DEV_0,
> > +	C3_ISP_CAP_DEV_1,
> > +	C3_ISP_CAP_DEV_2,
> > +	C3_ISP_NUM_CAP_DEVS
> > +};
> > +
> > +/**
> > + * struct c3_isp_pps_io_size - isp scaler input and output size
> > + *
> > + * @thsize: input horizontal size of after preprocessing
> > + * @tvsize: input vertical size of after preprocessing
> > + * @ohsize: output horizontal size
> > + * @ovsize: output vertical size
> > + * @ihsize: input horizontal size
> > + * @max_hsize: maximum horizontal size
> > + */
> > +struct c3_isp_pps_io_size {
> > +	u32 thsize;
> > +	u32 tvsize;
> > +	u32 ohsize;
> > +	u32 ovsize;
> > +	u32 ihsize;
> > +	u32 max_hsize;
> > +};
> > +
> > +/**
> > + * @mbus_code: the mbus code
> > + * @pads: save the pad flag of this mbus_code
> > + * @xofst: horizontal phase offset of hardware
> > + * @yofst: vertical phase offset of hardware
> > + */
> > +struct c3_isp_mbus_format_info {
> > +	u32 mbus_code;
> > +	u32 pads;
> > +	u8 xofst;
> > +	u8 yofst;
> > +};
> > +
> > +/**
> > + * @mbus_code: the mbus code
> > + * @fourcc: pixel format
> > + * @depth: pixel width
> > + */
> > +struct c3_isp_capture_format {
> > +	u32 mbus_code;
> > +	u32 fourcc;
> > +	u8 depth;
> > +};
> > +
> > +/**
> > + * struct c3_isp_vb2_buffer - A container of vb2 buffer
> > + *
> > + * @vb: vb2 buffer
> > + * @vaddr: buffer virtual address
> > + * @paddr: buffer physical address
> > + * @list: entry of the buffer in the queue
> > + */
> > +struct c3_isp_vb2_buffer {
> > +	struct vb2_v4l2_buffer vb;
> > +	void *vaddr;
> > +	dma_addr_t paddr;
> > +	struct list_head list;
> > +};
> > +
> > +/**
> > + * struct c3_isp_core - ISP core subdev
> > + *
> > + * @sd: ISP sub-device
> > + * @pads: ISP sub-device pads
> > + * @src_sd: source sub-device
> > + * @isp: pointer to c3_isp_device
> > + * @src_sd_pad: source sub-device pad
> > + */
> > +struct c3_isp_core {
> > +	struct v4l2_subdev sd;
> > +	struct media_pad pads[C3_ISP_CORE_PAD_MAX];
> > +	struct v4l2_subdev *src_sd;
> > +	u16 src_sd_pad;
> > +	struct c3_isp_device *isp;
> > +};
> > +
> > +/**
> > + * struct c3_isp_resizer - ISP resizer subdev
> > + *
> > + * @id: resizer id
> > + * @sd: resizer sub-device
> > + * @pads: resizer sub-device pads
> > + * @isp: pointer to c3_isp_device
> > + * @cap: pointer to c3_isp_capture
> > + */
> > +struct c3_isp_resizer {
> > +	enum c3_isp_resizer_ids id;
> > +	struct v4l2_subdev sd;
> > +	struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
> > +	struct c3_isp_device *isp;
> > +	struct c3_isp_capture *cap;
> > +};
> > +
> > +/**
> > + * struct c3_isp_stats - ISP statistics device
> > + *
> > + * @vb2_q: vb2 buffer queue
> > + * @vdev: video node
> > + * @vfmt: v4l2_format of the metadata format
> > + * @pad: media pad
> > + * @lock: protects vb2_q, vdev
> > + * @is_streaming: stats status
> > + * @isp: pointer to c3_isp_device
> > + * @buff: in use buffer
> > + * @buff_lock: protects stats buffer
> > + * @pending: stats buffer list head
> > + */
> > +struct c3_isp_stats {
> > +	struct vb2_queue vb2_q;
> > +	struct video_device vdev;
> > +	struct v4l2_format vfmt;
> > +	struct media_pad pad;
> > +
> > +	struct mutex lock; /* Protects vb2_q, vdev */
> > +	bool is_streaming;
> > +	struct c3_isp_device *isp;
> > +
> > +	struct c3_isp_vb2_buffer *buff;
> > +	spinlock_t buff_lock; /* Protects stream buffer */
> > +	struct list_head pending;
> > +};
> > +
> > +/**
> > + * struct c3_isp_params - ISP parameters device
> > + *
> > + * @vb2_q: vb2 buffer queue
> > + * @vdev: video node
> > + * @vfmt: v4l2_format of the metadata format
> > + * @pad: media pad
> > + * @lock: protects vb2_q, vdev
> > + * @isp: pointer to c3_isp_device
> > + * @buff: in use buffer
> > + * @buff_lock: protects stats buffer
> > + * @pending: stats buffer list head
> > + */
> > +struct c3_isp_params {
> > +	struct vb2_queue vb2_q;
> > +	struct video_device vdev;
> > +	struct v4l2_format vfmt;
> > +	struct media_pad pad;
> > +
> > +	struct mutex lock; /* Protects vb2_q, vdev */
> > +	struct c3_isp_device *isp;
> > +
> > +	struct c3_isp_vb2_buffer *buff;
> > +	spinlock_t buff_lock; /* Protects stream buffer */
> > +	struct list_head pending;
> > +};
> > +
> > +/**
> > + * struct c3_isp_capture - ISP capture device
> > + *
> > + * @id: capture device ID
> > + * @vb2_q: vb2 buffer queue
> > + * @vdev: video node
> > + * @vfmt: v4l2_format of the capture format
> > + * @pad: media pad
> > + * @lock: protects vb2_q, vdev
> > + * @is_streaming: capture device status
> > + * @isp: pointer to c3_isp_device
> > + * @rsz: pointer to c3_isp_resizer
> > + * @buff: in use buffer
> > + * @buff_lock: protects capture buffer
> > + * @pending: capture buffer list head
> > + */
> > +struct c3_isp_capture {
> > +	enum c3_isp_cap_devs id;
> > +	struct vb2_queue vb2_q;
> > +	struct video_device vdev;
> > +	struct v4l2_format vfmt;
> > +	struct media_pad pad;
> > +
> > +	struct mutex lock; /* Protects vb2_q, vdev */
> > +	bool is_streaming;
> > +	struct c3_isp_device *isp;
> > +	struct c3_isp_resizer *rsz;
> > +
> > +	struct c3_isp_vb2_buffer *buff;
> > +	spinlock_t buff_lock; /* Protects stream buffer */
> > +	struct list_head pending;
> > +};
> > +
> > +/**
> > + * struct c3_isp_info - ISP information
> > + *
> > + * @clocks: array of ISP clock names
> > + * @clock_rates: array of ISP clock rate
> > + * @clock_num: actual clock number
> > + */
> > +struct c3_isp_info {
> > +	char *clocks[C3_ISP_CLOCK_NUM_MAX];
> > +	u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
> > +	u32 clock_num;
> > +};
> > +
> > +/**
> > + * struct c3_isp_device - ISP platform device
> > + *
> > + * @dev: pointer to the struct device
> > + * @base: base register address
> > + * @clks: array of clocks
> > + * @notifier: notifier to register on the v4l2-async API
> > + * @v4l2_dev: v4l2_device variable
> > + * @media_dev: media device variable
> > + * @pipe: media pipeline
> > + * @core: ISP core subdev
> > + * @resizer: ISP resizer subdev
> > + * @stats: ISP stats device
> > + * @params: ISP params device
> > + * @caps: array of ISP capture device
> > + * @frm_sequence: used to record frame id
> > + * @lock: protect ISP device
> > + * @info: version-specific ISP information
> > + */
> > +struct c3_isp_device {
> > +	struct device *dev;
> > +	void __iomem *base;
> > +	struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
> > +
> > +	struct v4l2_async_notifier notifier;
> > +	struct v4l2_device v4l2_dev;
> > +	struct media_device media_dev;
> > +	struct media_pipeline pipe;
> > +
> > +	struct c3_isp_core core;
> > +	struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
> > +	struct c3_isp_stats stats;
> > +	struct c3_isp_params params;
> > +	struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
> > +
> > +	u32 frm_sequence;
> > +	struct mutex lock; /* Protect ISP device */
> > +	const struct c3_isp_info *info;
> > +};
> > +
> > +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
> > +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
> > +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
> > +bool c3_isp_pipeline_ready(struct c3_isp_device *isp);
> > +
> > +int c3_isp_core_register(struct c3_isp_device *isp);
> > +void c3_isp_core_unregister(struct c3_isp_device *isp);
> > +int c3_isp_resizers_register(struct c3_isp_device *isp);
> > +void c3_isp_resizers_unregister(struct c3_isp_device *isp);
> > +void c3_isp_rsz_start(struct c3_isp_resizer *rsz);
> > +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz);
> > +int c3_isp_captures_register(struct c3_isp_device *isp);
> > +void c3_isp_captures_unregister(struct c3_isp_device *isp);
> > +void c3_isp_captures_done(struct c3_isp_device *isp);
> > +int c3_isp_stats_register(struct c3_isp_device *isp);
> > +void c3_isp_stats_unregister(struct c3_isp_device *isp);
> > +int c3_isp_stats_done(struct c3_isp_device *isp);
> > +int c3_isp_params_register(struct c3_isp_device *isp);
> > +void c3_isp_params_unregister(struct c3_isp_device *isp);
> > +int c3_isp_params_done(struct c3_isp_device *isp);
> > +
> > +#endif
> > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
> > new file mode 100644
> > index 000000000000..d3672aff9fd2
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
> > @@ -0,0 +1,675 @@
> > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > +/*
> > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > + */
> > +
> > +#include <linux/pm_runtime.h>
> > +
> > +#include "c3-isp-common.h"
> > +#include "c3-isp-regs.h"
> > +#include "include/uapi/c3-isp-config.h"
> > +
> > +#define C3_ISP_CORE_SUBDEV_NAME        "isp-core"
> > +
> > +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
> > +	/* RAW formats */
> > +	{
> > +		.mbus_code	= MEDIA_BUS_FMT_SBGGR10_1X10,
> > +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> > +	}, {
> > +		.mbus_code	= MEDIA_BUS_FMT_SGBRG10_1X10,
> > +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> > +	}, {
> > +		.mbus_code	= MEDIA_BUS_FMT_SGRBG10_1X10,
> > +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> > +	}, {
> > +		.mbus_code	= MEDIA_BUS_FMT_SRGGB10_1X10,
> > +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> > +	}, {
> > +		.mbus_code	= MEDIA_BUS_FMT_SBGGR12_1X12,
> > +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> > +	}, {
> > +		.mbus_code	= MEDIA_BUS_FMT_SGBRG12_1X12,
> > +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> > +	}, {
> > +		.mbus_code	= MEDIA_BUS_FMT_SGRBG12_1X12,
> > +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> > +	}, {
> > +		.mbus_code	= MEDIA_BUS_FMT_SRGGB12_1X12,
> > +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> > +	},
> > +	/* YUV formats */
> > +	{
> > +		.mbus_code	= MEDIA_BUS_FMT_Y8_1X8,
> > +		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> > +	}, {
> > +		.mbus_code	= MEDIA_BUS_FMT_YUYV8_1_5X8,
> > +		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> > +	}, {
> > +		.mbus_code	= MEDIA_BUS_FMT_YUYV8_2X8,
> > +		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> > +	},
> > +};
> > +
> > +static const struct c3_isp_mbus_format_info
> > +*core_find_format_by_code(u32 code, u32 pad)
> > +{
> > +	int i;
>
> unsigned
>
> > +
> > +	for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
> > +		const struct c3_isp_mbus_format_info *info =
> > +			&c3_isp_core_mbus_formats[i];
> > +
> > +		if (info->mbus_code == code && info->pads & BIT(pad))
> > +			return info;
> > +	}
> > +
> > +	return NULL;
> > +}
> > +
> > +static const struct c3_isp_mbus_format_info
> > +*core_find_format_by_index(u32 index, u32 pad)
> > +{
> > +	int i;
>
> unsigned
>
> > +
> > +	for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
> > +		const struct c3_isp_mbus_format_info *info =
> > +			&c3_isp_core_mbus_formats[i];
> > +
> > +		if (!(info->pads & BIT(pad)))
> > +			continue;
> > +
> > +		if (!index)
> > +			return info;
> > +
> > +		index--;
> > +	}
> > +
> > +	return NULL;
> > +}
> > +
> > +static void c3_isp_core_enable(struct c3_isp_device *isp)
> > +{
> > +	/* Select the line sync signal */
> > +	c3_isp_update_bits(isp, ISP_TOP_IRQ_LINE_THRD,
> > +			   TOP_IRQ_DIN_HSYNC, TOP_IRQ_DIN_HSYNC);
> > +
> > +	/* Enable frame done and stats error irq */
> > +	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
> > +			   TOP_IRQ_FRAME_DONE, TOP_IRQ_FRAME_DONE);
> > +	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
> > +			   TOP_IRQ_STATS_ERR, TOP_IRQ_STATS_ERR);
> > +
> > +	/* Enable image data to ISP core */
> > +	c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
> > +			   TOP_DATA_PATH_MASK, TOP_PATH_MIPI_TO_CORE);
> > +}
> > +
> > +static void c3_isp_core_disable(struct c3_isp_device *isp)
> > +{
> > +	/* Disable image data to ISP core */
> > +	c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
> > +			   TOP_DATA_PATH_MASK, 0x0);
> > +
> > +	/* Disable all irq */
> > +	c3_isp_write(isp, ISP_TOP_IRQ_EN, 0x0);
> > +}
> > +
> > +/* Set the phase offset of blc, wb and lns */
> > +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
> > +				  u8 xofst, u8 yofst)
> > +{
> > +	c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
> > +			   LSWB_BLC_XPHS_OFST_MASK,
> > +			   xofst << LSWB_BLC_XPHS_OFST_SHIFT);
> > +	c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
> > +			   LSWB_BLC_YPHS_OFST_MASK, yofst);
> > +
> > +	c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
> > +			   LSWB_WB_XPHS_OFST_MASK,
> > +			   xofst << LSWB_WB_XPHS_OFST_SHIFT);
> > +	c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
> > +			   LSWB_WB_YPHS_OFST_MASK, yofst);
> > +
> > +	c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
> > +			   LSWB_LNS_XPHS_OFST_MASK,
> > +			   xofst << LSWB_LNS_XPHS_OFST_SHIFT);
> > +	c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
> > +			   LSWB_LNS_YPHS_OFST_MASK, yofst);
> > +}
> > +
> > +/* Set the phase offset of af, ae and awb */
> > +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
> > +				u8 xofst, u8 yofst)
> > +{
> > +	c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_XPHS_OFST_MASK,
> > +			   xofst << AF_CTRL_XPHS_OFST_SHIFT);
> > +	c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_YPHS_OFST_MASK,
> > +			   yofst << AF_CTRL_YPHS_OFST_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_XPHS_OFST_MASK,
> > +			   xofst << AE_CTRL_XPHS_OFST_SHIFT);
> > +	c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_YPHS_OFST_MASK,
> > +			   yofst << AE_CTRL_YPHS_OFST_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_XPHS_OFST_MASK,
> > +			   xofst << AWB_CTRL_XPHS_OFST_SHIFT);
> > +	c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_YPHS_OFST_MASK, yofst);
> > +}
> > +
> > +/* Set the phase offset of demosaic */
> > +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
> > +				 u8 xofst, u8 yofst)
> > +{
> > +	c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_XPHS_OFST_MASK,
> > +			   xofst << DMS_COMMON_XPHS_OFST_SHIFT);
> > +	c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_YPHS_OFST_MASK, yofst);
> > +}
> > +
> > +static void c3_isp_core_cfg_ofst(struct c3_isp_device *isp,
> > +				 struct v4l2_mbus_framefmt *fmt)
> > +{
> > +	const struct c3_isp_mbus_format_info *isp_fmt =
> > +			core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
> > +
> > +	c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> > +	c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> > +	c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> > +}
> > +
> > +/* Set format of the hardware control module */
> > +static void c3_isp_core_top_fmt(struct c3_isp_device *isp,
> > +				struct v4l2_mbus_framefmt *fmt)
> > +{
> > +	c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
> > +		     TOP_INPUT_HSIZE(fmt->width) | TOP_INPUT_VSIZE(fmt->height));
> > +
> > +	c3_isp_write(isp, ISP_TOP_FRM_SIZE,
> > +		     TOP_FRM_CORE_IHSIZE(fmt->width) | TOP_FRM_CORE_IVSIZE(fmt->height));
> > +
> > +	c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE, TOP_HOLD_HSIZE_MASK,
> > +			   fmt->width << TOP_HOLD_HSIZE_SHIFT);
> > +}
> > +
> > +static void c3_isp_core_af_fmt(struct c3_isp_device *isp,
> > +			       struct v4l2_mbus_framefmt *fmt)
> > +{
> > +	u32 hidx;
> > +	u32 vidx;
> > +	int i;
> > +
> > +	c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, AF_HV_STAT_HBLK_NUM_MASK,
> > +			   AF_STAT_BLKH_NUM << AF_HV_STAT_HBLK_NUM_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM,
> > +			   AF_HV_STAT_VBLK_NUM_MASK, AF_STAT_BLKV_NUM);
> > +
> > +	c3_isp_write(isp, ISP_AF_HV_SIZE,
> > +		     AF_HV_STAT_HSIZE(fmt->width) | AF_HV_STAT_VSIZE(fmt->height));
> > +
> > +	/* Set the index address to 0 position */
> > +	c3_isp_write(isp, ISP_AF_IDX_ADDR, 0);
> > +	/*
> > +	 * Calculate and set the coordinates of points in the grid.
> > +	 * hidx and vidx need to be aligned with 2.
> > +	 */
> > +	for (i = 0; i <= AF_STAT_BLKH_NUM; i++) {
> > +		hidx = i * fmt->width / AF_STAT_BLKH_NUM;
> > +		hidx = ALIGN_DOWN(hidx, 2);
> > +
> > +		vidx = i * fmt->height / AF_STAT_BLKV_NUM;
> > +		vidx = min(vidx, fmt->height);
> > +		vidx = ALIGN_DOWN(vidx, 2);
> > +		c3_isp_write(isp, ISP_AF_IDX_DATA,
> > +			     AF_IDX_HIDX_DATA(hidx) | AF_IDX_VIDX_DATA(vidx));
> > +	}
> > +}
> > +
> > +static void c3_isp_core_ae_fmt(struct c3_isp_device *isp,
> > +			       struct v4l2_mbus_framefmt *fmt)
> > +{
> > +	u32 hidx;
> > +	u32 vidx;
> > +	int i;
> > +
> > +	c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, AE_HV_STAT_HBLK_NUM_MASK,
> > +			   AE_STAT_BLKH_NUM << AE_HV_STAT_HBLK_NUM_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM,
> > +			   AE_HV_STAT_VBLK_NUM_MASK, AE_STAT_BLKV_NUM);
> > +
> > +	c3_isp_write(isp, ISP_AE_HV_SIZE,
> > +		     AE_HV_STAT_HSIZE(fmt->width) | AE_HV_STAT_VSIZE(fmt->height));
> > +
> > +	/* Set the index address to 0 position */
> > +	c3_isp_write(isp, ISP_AE_IDX_ADDR, 0);
> > +	/*
> > +	 * Calculate and set the coordinates of points in the grid.
> > +	 * hidx and vidx need to be aligned with 2.
> > +	 */
> > +	for (i = 0; i <= AE_STAT_BLKH_NUM; i++) {
> > +		hidx = i * fmt->width / AE_STAT_BLKH_NUM;
> > +		hidx = ALIGN_DOWN(hidx, 2);
> > +
> > +		vidx = i * fmt->height / AE_STAT_BLKV_NUM;
> > +		vidx = min(vidx, fmt->height);
> > +		vidx = ALIGN_DOWN(vidx, 2);
> > +
> > +		c3_isp_write(isp, ISP_AE_IDX_DATA,
> > +			     AE_IDX_HIDX_DATA(hidx) | AE_IDX_VIDX_DATA(vidx));
> > +	}
> > +}
> > +
> > +static void c3_isp_core_awb_fmt(struct c3_isp_device *isp,
> > +				struct v4l2_mbus_framefmt *fmt)
> > +{
> > +	u32 hidx;
> > +	u32 vidx;
> > +	int i;
> > +
> > +	c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, AWB_HV_STAT_HBLK_NUM_MASK,
> > +			   AWB_STAT_BLKH_NUM << AWB_HV_STAT_HBLK_NUM_SHIFT);
> > +	c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM,
> > +			   AWB_HV_STAT_VBLK_NUM_MASK, AWB_STAT_BLKV_NUM);
> > +
> > +	c3_isp_write(isp, ISP_AWB_HV_SIZE,
> > +		     AWB_HV_STAT_HSIZE(fmt->width) | AWB_HV_STAT_VSIZE(fmt->height));
> > +
> > +	/* Set the index address to 0 position */
> > +	c3_isp_write(isp, ISP_AWB_IDX_ADDR, 0);
> > +	/*
> > +	 * Calculate and set the coordinates of points in the grid.
> > +	 * hidx and vidx need to be aligned with 2.
> > +	 */
> > +	for (i = 0; i <= AWB_STAT_BLKH_NUM; i++) {
> > +		hidx = i * fmt->width / AWB_STAT_BLKH_NUM;
> > +		hidx = ALIGN_DOWN(hidx, 2);
> > +
> > +		vidx = i * fmt->height / AWB_STAT_BLKV_NUM;
> > +		vidx = min(vidx, fmt->height);
> > +		vidx = ALIGN_DOWN(vidx, 2);
> > +
> > +		c3_isp_write(isp, ISP_AWB_IDX_DATA,
> > +			     AWB_IDX_HIDX_DATA(hidx) | AWB_IDX_VIDX_DATA(vidx));
> > +	}
> > +}
> > +
> > +static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
> > +				   struct v4l2_subdev_state *state)
> > +{
> > +	struct v4l2_mbus_framefmt *fmt;
> > +
> > +	fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> > +
> > +	c3_isp_core_cfg_ofst(isp, fmt);
> > +	c3_isp_core_top_fmt(isp, fmt);
> > +	c3_isp_core_af_fmt(isp, fmt);
> > +	c3_isp_core_ae_fmt(isp, fmt);
> > +	c3_isp_core_awb_fmt(isp, fmt);
> > +}
> > +
> > +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
> > +				      struct v4l2_subdev_state *state,
> > +				      u32 pad, u64 streams_mask)
> > +{
> > +	struct c3_isp_core *core = v4l2_get_subdevdata(sd);
> > +	u64 sink_streams;
> > +	int ret;
> > +
> > +	core->isp->frm_sequence = 0;
> > +	c3_isp_core_cfg_format(core->isp, state);
> > +	c3_isp_core_enable(core->isp);
> > +
> > +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > +						       C3_ISP_CORE_PAD_SINK_VIDEO,
> > +						       &streams_mask);
> > +	ret = v4l2_subdev_enable_streams(core->src_sd,
> > +					 core->src_sd_pad, sink_streams);
> > +	if (ret) {
> > +		c3_isp_core_disable(core->isp);
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
> > +				       struct v4l2_subdev_state *state,
> > +				       u32 pad, u64 streams_mask)
> > +{
> > +	struct c3_isp_core *core = v4l2_get_subdevdata(sd);
> > +	u64 sink_streams;
> > +	int ret;
> > +
> > +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > +						       C3_ISP_CORE_PAD_SINK_VIDEO,
> > +						       &streams_mask);
> > +	ret = v4l2_subdev_disable_streams(core->src_sd,
> > +					  core->src_sd_pad, sink_streams);
> > +	if (ret)
> > +		return ret;
> > +
> > +	c3_isp_core_disable(core->isp);
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
> > +				   struct v4l2_subdev_state *state,
> > +				   struct v4l2_subdev_krouting *routing)
> > +{
> > +	static const struct v4l2_mbus_framefmt format = {
> > +		.width = C3_ISP_DEFAULT_WIDTH,
> > +		.height = C3_ISP_DEFAULT_HEIGHT,
> > +		.code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
> > +		.field = V4L2_FIELD_NONE,
> > +		.colorspace = V4L2_COLORSPACE_SRGB,
> > +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> > +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
> > +		.xfer_func = V4L2_XFER_FUNC_SRGB,
> > +	};
> > +	int ret;
> > +
> > +	ret = v4l2_subdev_routing_validate(sd, routing,
> > +					   V4L2_SUBDEV_ROUTING_NO_SOURCE_STREAM_MIX);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
> > +				    struct v4l2_subdev_state *state)
> > +{
> > +	struct v4l2_subdev_route routes[2];
> > +	struct v4l2_subdev_krouting routing;
> > +
> > +	routes[0].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
> > +	routes[0].sink_stream = 0;
> > +	routes[0].source_pad = C3_ISP_CORE_PAD_SOURCE_STATS;
> > +	routes[0].source_stream = 0;
> > +	routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> > +
> > +	routes[1].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
> > +	routes[1].sink_stream = 0;
> > +	routes[1].source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
> > +	routes[1].source_stream = 0;
> > +	routes[1].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> > +
> > +	routing.num_routes = ARRAY_SIZE(routes);
> > +	routing.routes = routes;
> > +
> > +	return c3_isp_core_cfg_routing(sd, state, &routing);
> > +}
> > +
> > +static int c3_isp_core_set_routing(struct v4l2_subdev *sd,
> > +				   struct v4l2_subdev_state *state,
> > +				   enum v4l2_subdev_format_whence which,
> > +				   struct v4l2_subdev_krouting *routing)
>
> I'm not sure I see a reason for the ISP subdev to implement routing.
> In my understanding it will only receive a single image stream and
> processes it to 3 DMA output nodes, and to control which output is
> enabled you use media links.
>
> > +{
> > +	bool is_streaming = v4l2_subdev_is_streaming(sd);
> > +
> > +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> > +		return -EBUSY;
> > +
> > +	return c3_isp_core_cfg_routing(sd, state, routing);
> > +}
> > +
> > +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
> > +				      struct v4l2_subdev_state *state,
> > +				      struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > +	const struct c3_isp_mbus_format_info *info;
> > +	int ret = 0;
> > +
> > +	switch (code->pad) {
> > +	case C3_ISP_CORE_PAD_SINK_VIDEO:
> > +	case C3_ISP_CORE_PAD_SOURCE_VIDEO:
> > +		info = core_find_format_by_index(code->index, code->pad);
> > +		if (!info)
> > +			ret = -EINVAL;
> > +		else
> > +			code->code = info->mbus_code;
> > +
> > +		break;
> > +	case C3_ISP_CORE_PAD_SINK_PARAMS:
> > +	case C3_ISP_CORE_PAD_SOURCE_STATS:
> > +		if (code->index)
> > +			ret = -EINVAL;
> > +		else
> > +			code->code = MEDIA_BUS_FMT_METADATA_FIXED;
> > +
> > +		break;
> > +	default:
> > +		ret = -EINVAL;
> > +		break;
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static void c3_isp_core_set_sink_fmt(struct v4l2_subdev_state *state,
> > +				     struct v4l2_subdev_format *format)
> > +{
> > +	struct v4l2_mbus_framefmt *sink_fmt;
> > +	const struct c3_isp_mbus_format_info *isp_fmt;
> > +
> > +	sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
> > +
> > +	isp_fmt = core_find_format_by_code(format->format.code, format->pad);
> > +	if (!isp_fmt)
> > +		sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
> > +	else
> > +		sink_fmt->code = format->format.code;
> > +
> > +	sink_fmt->width = clamp_t(u32, format->format.width,
> > +				  C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> > +	sink_fmt->height = clamp_t(u32, format->format.height,
> > +				   C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> > +
> > +	format->format = *sink_fmt;
> > +}
> > +
> > +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
> > +				       struct v4l2_subdev_format *format)
> > +{
> > +	const struct c3_isp_mbus_format_info *isp_fmt;
> > +	struct v4l2_mbus_framefmt *sink_fmt;
> > +	struct v4l2_mbus_framefmt *src_fmt;
> > +
> > +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> > +	src_fmt = v4l2_subdev_state_get_format(state, format->pad);
> > +
> > +	isp_fmt = core_find_format_by_code(format->format.code, format->pad);
> > +	if (!isp_fmt)
> > +		src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
> > +	else
> > +		src_fmt->code = format->format.code;
> > +
> > +	/* The source size must be same with the sink size. */
> > +	src_fmt->width  = sink_fmt->width;
> > +	src_fmt->height = sink_fmt->height;
> > +
> > +	format->format = *src_fmt;
> > +}
> > +
> > +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
> > +			       struct v4l2_subdev_state *state,
> > +			       struct v4l2_subdev_format *format)
> > +{
> > +	struct v4l2_mbus_framefmt *fmt;
> > +
> > +	switch (format->pad) {
> > +	case C3_ISP_CORE_PAD_SINK_VIDEO:
> > +		c3_isp_core_set_sink_fmt(state, format);
> > +		break;
> > +	case C3_ISP_CORE_PAD_SINK_PARAMS:
> > +	case C3_ISP_CORE_PAD_SOURCE_STATS:
> > +		fmt = v4l2_subdev_state_get_format(state, format->pad);
> > +		format->format = *fmt;
> > +		break;
> > +	case C3_ISP_CORE_PAD_SOURCE_VIDEO:
> > +		c3_isp_core_set_source_fmt(state, format);
> > +		break;
> > +	default:
>
> I don't think this can happen. The core validates that format->pad is
> correct.
>
> > +		dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
> > +		return -ENOTTY;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_core_init_state(struct v4l2_subdev *sd,
> > +				  struct v4l2_subdev_state *state)
> > +{
> > +	struct v4l2_mbus_framefmt *sink_fmt;
> > +	struct v4l2_mbus_framefmt *src_fmt;
> > +
> > +	/* Video sink pad */
> > +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> > +	sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
> > +	sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> > +	sink_fmt->field = V4L2_FIELD_NONE;
> > +	sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
> > +	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> > +	sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
> > +	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> > +	sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
> > +
> > +	/* Video source pad */
> > +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
> > +	src_fmt->width = C3_ISP_DEFAULT_WIDTH;
> > +	src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> > +	src_fmt->field = V4L2_FIELD_NONE;
> > +	src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
> > +	src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> > +	src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
> > +	src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> > +	src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> > +
> > +	/* Parameters pad */
> > +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
> > +	sink_fmt->width = 0;
> > +	sink_fmt->height = 0;
> > +	sink_fmt->field = V4L2_FIELD_NONE;
> > +	sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
> > +
> > +	/* Statistics pad */
> > +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
> > +	src_fmt->width = 0;
> > +	src_fmt->height = 0;
> > +	src_fmt->field = V4L2_FIELD_NONE;
> > +	src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
> > +
> > +	return c3_isp_core_init_routing(sd, state);
> > +}
> > +
> > +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
> > +	.enum_mbus_code = c3_isp_core_enum_mbus_code,
> > +	.get_fmt = v4l2_subdev_get_fmt,
> > +	.set_fmt = c3_isp_core_set_fmt,
> > +	.enable_streams = c3_isp_core_enable_streams,
> > +	.disable_streams = c3_isp_core_disable_streams,
> > +	.set_routing = c3_isp_core_set_routing,
> > +};
> > +
> > +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
> > +	.pad = &c3_isp_core_pad_ops,
> > +};
> > +
> > +static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
> > +	.init_state = c3_isp_core_init_state,
> > +};
> > +
> > +static int c3_isp_core_link_validate(struct media_link *link)
> > +{
> > +	if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
> > +		return 0;
> > +
> > +	return v4l2_subdev_link_validate(link);
> > +}
> > +
> > +/* Media entity operations */
> > +static const struct media_entity_operations c3_isp_core_entity_ops = {
> > +	.link_validate = c3_isp_core_link_validate,
> > +};
> > +
> > +int c3_isp_core_register(struct c3_isp_device *isp)
> > +{
> > +	struct c3_isp_core *core = &isp->core;
> > +	struct v4l2_subdev *sd = &core->sd;
> > +	int ret;
> > +
> > +	v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
> > +	sd->owner = THIS_MODULE;
> > +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > +	sd->internal_ops = &c3_isp_core_internal_ops;
> > +	snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
> > +
> > +	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
> > +	sd->entity.ops = &c3_isp_core_entity_ops;
> > +
> > +	core->isp = isp;
> > +	sd->dev = isp->dev;
> > +	v4l2_set_subdevdata(sd, core);
> > +
> > +	core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
> > +	core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
> > +	core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
> > +	core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
> > +	ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = v4l2_subdev_init_finalize(sd);
> > +	if (ret)
> > +		goto err_entity_cleanup;
> > +
> > +	ret = v4l2_device_register_subdev(&isp->v4l2_dev, sd);
> > +	if (ret)
> > +		goto err_subdev_cleanup;
> > +
> > +	return 0;
> > +
> > +err_subdev_cleanup:
> > +	v4l2_subdev_cleanup(sd);
> > +err_entity_cleanup:
> > +	media_entity_cleanup(&sd->entity);
> > +	return ret;
> > +}
> > +
> > +void c3_isp_core_unregister(struct c3_isp_device *isp)
> > +{
> > +	struct c3_isp_core *core = &isp->core;
> > +	struct v4l2_subdev *sd = &core->sd;
> > +
> > +	v4l2_device_unregister_subdev(sd);
> > +	v4l2_subdev_cleanup(sd);
> > +	media_entity_cleanup(&sd->entity);
> > +}
> > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
> > new file mode 100644
> > index 000000000000..a57b9f8dbc3c
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
> > @@ -0,0 +1,486 @@
> > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > +/*
> > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/device.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +
> > +#include <media/v4l2-common.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-mc.h>
> > +
> > +#include "c3-isp-common.h"
> > +#include "c3-isp-regs.h"
> > +
> > +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
> > +{
> > +	return readl(isp->base + reg);
> > +}
> > +
> > +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
> > +{
> > +	writel(val, isp->base + reg);
> > +}
> > +
> > +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
> > +{
> > +	u32 orig, tmp;
> > +
> > +	orig = c3_isp_read(isp, reg);
> > +
> > +	tmp = orig & ~mask;
> > +	tmp |= val & mask;
> > +
> > +	if (tmp != orig)
> > +		c3_isp_write(isp, reg, tmp);
> > +}
> > +
> > +bool c3_isp_pipeline_ready(struct c3_isp_device *isp)
> > +{
> > +	struct media_pipeline_entity_iter iter;
> > +	unsigned int n_video_devices = 0;
> > +	struct media_entity *entity;
> > +	int ret;
> > +
> > +	ret = media_pipeline_entity_iter_init(&isp->pipe, &iter);
> > +	if (ret)
> > +		return ret;
> > +
> > +	media_pipeline_for_each_entity(&isp->pipe, &iter, entity) {
> > +		if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
> > +			n_video_devices++;
> > +	}
> > +
> > +	media_pipeline_entity_iter_cleanup(&iter);
> > +
> > +	return n_video_devices == isp->pipe.start_count;
> > +}
>
> As suggested in the review of the -params.c module, I suggest to
> implement start_streaming on the capture nodes only. From there you
> can call enable_streams on the ISP subdevice. The ISP subdevice can
> count how many links to resizers are enabled, and actually start its
> operation when all the enabled ones have been started.
>
> The idea is to use enabled media links to identify how many capture video
> devices are expected to be used, and only start the ISP (and the
> downstream subdevices like the CSI-2 Adap and RX and the sensor) when
> all of linked ones have been started.
>
> > +
> > +/* PM runtime suspend */
> > +static int __maybe_unused c3_isp_runtime_suspend(struct device *dev)
> > +{
> > +	struct c3_isp_device *isp = dev_get_drvdata(dev);
> > +
> > +	clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
> > +
> > +	return 0;
> > +}
> > +
> > +/* PM runtime resume */
> > +static int __maybe_unused c3_isp_runtime_resume(struct device *dev)
> > +{
> > +	struct c3_isp_device *isp = dev_get_drvdata(dev);
> > +
> > +	return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
> > +}
> > +
> > +static const struct dev_pm_ops c3_isp_pm_ops = {
> > +	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> > +				pm_runtime_force_resume)
> > +	SET_RUNTIME_PM_OPS(c3_isp_runtime_suspend,
> > +			   c3_isp_runtime_resume, NULL)
> > +};
> > +
> > +/* IRQ handling */
> > +static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
> > +{
> > +	struct c3_isp_device *isp = dev;
> > +	u32 status;
> > +
> > +	/* Get irq status and clear irq status */
> > +	status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
> > +	c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
> > +
> > +	if (status & TOP_IRQ_FRAME_DONE) {
> > +		c3_isp_stats_done(isp);
> > +		c3_isp_params_done(isp);
> > +		c3_isp_captures_done(isp);
> > +		isp->frm_sequence++;
> > +	}
> > +
> > +	if (status & TOP_IRQ_STATS_ERR)
> > +		dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +/* Subdev notifier register */
> > +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
> > +			       struct v4l2_subdev *sd,
> > +			       struct v4l2_async_connection *asc)
> > +{
> > +	struct c3_isp_device *isp =
> > +		container_of(notifier, struct c3_isp_device, notifier);
> > +	struct c3_isp_core *core = &isp->core;
> > +	struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
> > +	int ret;
> > +
> > +	ret = media_entity_get_fwnode_pad(&sd->entity,
> > +					  sd->fwnode, MEDIA_PAD_FL_SOURCE);
> > +	if (ret < 0) {
> > +		dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
> > +		return ret;
> > +	}
> > +
> > +	core->src_sd = sd;
> > +	core->src_sd_pad = ret;
> > +
> > +	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
> > +					       MEDIA_LNK_FL_IMMUTABLE);
> > +}
> > +
> > +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
> > +{
> > +	struct c3_isp_device *isp =
> > +		container_of(notifier, struct c3_isp_device, notifier);
> > +	int ret;
> > +
> > +	ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
> > +	if (ret < 0) {
> > +		dev_err(isp->dev,
> > +			"Failed to register subdev nodes: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	dev_info(isp->dev, "notify complete\n");
> > +
> > +	return media_device_register(&isp->media_dev);
> > +}
> > +
> > +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
> > +{
> > +	struct c3_isp_device *isp =
> > +		container_of(asc->notifier, struct c3_isp_device, notifier);
> > +
> > +	media_device_unregister(&isp->media_dev);
> > +}
> > +
> > +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
> > +	.bound = c3_isp_notify_bound,
> > +	.complete = c3_isp_notify_complete,
> > +	.destroy = c3_isp_notify_destroy,
> > +};
> > +
> > +static int c3_isp_async_nf_register(struct c3_isp_device *isp)
> > +{
> > +	struct v4l2_async_connection *asc;
> > +	struct fwnode_handle *ep;
> > +	int ret;
> > +
> > +	v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
> > +
> > +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
> > +					     FWNODE_GRAPH_ENDPOINT_NEXT);
> > +	if (!ep)
> > +		return -ENOTCONN;
> > +
> > +	asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
> > +					      struct v4l2_async_connection);
> > +	if (IS_ERR(asc)) {
> > +		fwnode_handle_put(ep);
> > +		return PTR_ERR(asc);
> > +	}
> > +
> > +	fwnode_handle_put(ep);
> > +
> > +	isp->notifier.ops = &c3_isp_notify_ops;
> > +	ret = v4l2_async_nf_register(&isp->notifier);
> > +	if (ret)
> > +		v4l2_async_nf_cleanup(&isp->notifier);
> > +
> > +	return ret;
> > +}
> > +
> > +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
> > +{
> > +	v4l2_async_nf_unregister(&isp->notifier);
> > +	v4l2_async_nf_cleanup(&isp->notifier);
> > +}
> > +
> > +static int c3_isp_v4l2_register(struct c3_isp_device *isp)
> > +{
> > +	struct media_device *media_dev = &isp->media_dev;
> > +	struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
> > +	int ret;
> > +
> > +	/* Initialize media device */
> > +	strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
> > +		sizeof(media_dev->model));
> > +	media_dev->dev = isp->dev;
> > +
> > +	media_device_init(media_dev);
> > +
> > +	/* Initialize v4l2 device */
> > +	v4l2_dev->mdev = media_dev;
> > +	strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
> > +		sizeof(v4l2_dev->name));
> > +
> > +	ret = v4l2_device_register(isp->dev, v4l2_dev);
> > +	if (ret) {
> > +		media_device_cleanup(media_dev);
> > +		dev_err(isp->dev,
> > +			"Failed to register V4L2 device: %d\n", ret);
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
> > +{
> > +	v4l2_device_unregister(&isp->v4l2_dev);
> > +	media_device_cleanup(&isp->media_dev);
> > +}
> > +
> > +static void c3_isp_remove_links(struct c3_isp_device *isp)
> > +{
> > +	unsigned int i;
> > +
> > +	media_entity_remove_links(&isp->core.sd.entity);
> > +
> > +	for (i = 0; i < C3_ISP_NUM_RSZ; i++)
> > +		media_entity_remove_links(&isp->resizers[i].sd.entity);
> > +
> > +	for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
> > +		media_entity_remove_links(&isp->caps[i].vdev.entity);
> > +}
> > +
> > +static int c3_isp_create_links(struct c3_isp_device *isp)
> > +{
> > +	unsigned int i;
> > +	int ret;
> > +
> > +	for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
> > +		ret = media_create_pad_link(&isp->resizers[i].sd.entity,
> > +					    C3_ISP_RESIZER_PAD_SOURCE,
> > +					    &isp->resizers[i].cap->vdev.entity,
> > +					    0, MEDIA_LNK_FL_ENABLED);
>
> This could be made IMMUTABLE
>
> > +		if (ret) {
> > +			dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
> > +			goto err_remove_links;
> > +		}
> > +
> > +		ret = media_create_pad_link(&isp->core.sd.entity,
> > +					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > +					    &isp->resizers[i].sd.entity,
> > +					    C3_ISP_RESIZER_PAD_SINK,
> > +					    MEDIA_LNK_FL_ENABLED);
> > +		if (ret) {
> > +			dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
> > +			goto err_remove_links;
> > +		}
> > +	}
> > +
> > +	ret = media_create_pad_link(&isp->core.sd.entity,
> > +				    C3_ISP_CORE_PAD_SOURCE_STATS,
> > +				    &isp->stats.vdev.entity,
> > +				    0, MEDIA_LNK_FL_ENABLED);
> > +	if (ret) {
> > +		dev_err(isp->dev, "Failed to link core and stats\n");
> > +		goto err_remove_links;
> > +	}
> > +
> > +	ret = media_create_pad_link(&isp->params.vdev.entity, 0,
> > +				    &isp->core.sd.entity,
> > +				    C3_ISP_CORE_PAD_SINK_PARAMS,
> > +				    MEDIA_LNK_FL_ENABLED);
> > +	if (ret) {
> > +		dev_err(isp->dev, "Failed to link params and core\n");
> > +		goto err_remove_links;
> > +	}
> > +
> > +	return 0;
> > +
> > +err_remove_links:
> > +	c3_isp_remove_links(isp);
> > +	return ret;
> > +}
> > +
> > +static int c3_isp_videos_register(struct c3_isp_device *isp)
> > +{
> > +	int ret;
> > +
> > +	ret = c3_isp_captures_register(isp);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = c3_isp_stats_register(isp);
> > +	if (ret)
> > +		goto err_captures_unregister;
> > +
> > +	ret = c3_isp_params_register(isp);
> > +	if (ret)
> > +		goto err_stats_unregister;
> > +
> > +	ret = c3_isp_create_links(isp);
> > +	if (ret)
> > +		goto err_params_unregister;
> > +
> > +	return 0;
> > +
> > +err_params_unregister:
> > +	c3_isp_params_unregister(isp);
> > +err_stats_unregister:
> > +	c3_isp_stats_unregister(isp);
> > +err_captures_unregister:
> > +	c3_isp_captures_unregister(isp);
> > +	return ret;
> > +}
> > +
> > +static void c3_isp_videos_unregister(struct c3_isp_device *isp)
> > +{
> > +	c3_isp_remove_links(isp);
> > +	c3_isp_params_unregister(isp);
> > +	c3_isp_stats_unregister(isp);
> > +	c3_isp_captures_unregister(isp);
> > +}
> > +
> > +static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
> > +{
> > +	const struct c3_isp_info *info = isp->info;
> > +	int ret;
> > +	u32 i;
> > +
> > +	for (i = 0; i < info->clock_num; i++)
> > +		isp->clks[i].id = info->clocks[i];
> > +
> > +	ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
> > +	if (ret)
> > +		return ret;
> > +
> > +	for (i = 0; i < info->clock_num; i++) {
> > +		if (!info->clock_rates[i])
> > +			continue;
> > +		ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);
> > +		if (ret) {
> > +			dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
> > +				info->clock_rates[i]);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_probe(struct platform_device *pdev)
> > +{
> > +	struct device *dev = &pdev->dev;
> > +	struct c3_isp_device *isp;
> > +	int irq;
> > +	int ret;
> > +
> > +	isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
> > +	if (!isp)
> > +		return -ENOMEM;
> > +
> > +	isp->info = of_device_get_match_data(dev);
> > +	isp->dev = dev;
> > +
> > +	isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
> > +	if (IS_ERR(isp->base))
> > +		return dev_err_probe(dev, PTR_ERR(isp->base),
> > +				     "Failed to ioremap resource\n");
> > +
> > +	irq = platform_get_irq(pdev, 0);
> > +	if (irq < 0)
> > +		return irq;
> > +
> > +	ret = c3_isp_cfg_clocks(isp);
> > +	if (ret)
> > +		return dev_err_probe(dev, ret, "Failed to configure clocks\n");
> > +
> > +	platform_set_drvdata(pdev, isp);
> > +
> > +	pm_runtime_enable(dev);
> > +
> > +	ret = c3_isp_v4l2_register(isp);
> > +	if (ret)
> > +		goto err_runtime_disable;
> > +
> > +	ret = c3_isp_core_register(isp);
> > +	if (ret)
> > +		goto err_v4l2_unregister;
> > +
> > +	ret = c3_isp_resizers_register(isp);
> > +	if (ret)
> > +		goto err_core_unregister;
> > +
> > +	ret = c3_isp_async_nf_register(isp);
> > +	if (ret)
> > +		goto err_resizers_unregister;
> > +
> > +	ret = c3_isp_videos_register(isp);
> > +	if (ret)
> > +		goto err_nf_unregister;
> > +
> > +	ret = devm_request_irq(dev, irq,
> > +			       c3_isp_irq_handler, IRQF_SHARED,
> > +			       dev_driver_string(dev), isp);
> > +	if (ret)
> > +		goto err_streams_unregister;
>
> I would request the IRQ before registering devices to userspace.
>
> > +
> > +	mutex_init(&isp->lock);
> > +
> > +	return 0;
> > +
> > +err_streams_unregister:
> > +	c3_isp_videos_unregister(isp);
> > +err_nf_unregister:
> > +	c3_isp_async_nf_unregister(isp);
> > +err_resizers_unregister:
> > +	c3_isp_resizers_unregister(isp);
> > +err_core_unregister:
> > +	c3_isp_core_unregister(isp);
> > +err_v4l2_unregister:
> > +	c3_isp_v4l2_unregister(isp);
> > +err_runtime_disable:
> > +	pm_runtime_disable(dev);
> > +	return ret;
> > +};
> > +
> > +static void c3_isp_remove(struct platform_device *pdev)
> > +{
> > +	struct c3_isp_device *isp = platform_get_drvdata(pdev);
> > +
> > +	mutex_destroy(&isp->lock);
> > +	c3_isp_videos_unregister(isp);
> > +	c3_isp_async_nf_unregister(isp);
> > +	c3_isp_core_unregister(isp);
> > +	c3_isp_resizers_unregister(isp);
> > +	c3_isp_v4l2_unregister(isp);
> > +	pm_runtime_disable(isp->dev);
> > +};
> > +
> > +static const struct c3_isp_info isp_info = {
> > +	.clocks = {"vapb", "isp0"},
> > +	.clock_rates = {0, 400000000},
> > +	.clock_num = 2
> > +};
> > +
> > +static const struct of_device_id c3_isp_of_match[] = {
> > +	{ .compatible = "amlogic,c3-isp",
> > +	  .data = &isp_info },
> > +	{ },
> > +};
> > +MODULE_DEVICE_TABLE(of, c3_isp_of_match);
> > +
> > +static struct platform_driver c3_isp_driver = {
> > +	.probe = c3_isp_probe,
> > +	.remove = c3_isp_remove,
> > +	.driver = {
> > +		.name = "c3-isp",
> > +		.of_match_table = c3_isp_of_match,
> > +		.pm = &c3_isp_pm_ops,
> > +	},
> > +};
> > +
> > +module_platform_driver(c3_isp_driver);
> > +
> > +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
> > +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
> > new file mode 100644
> > index 000000000000..8a6b7ce86eaf
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
> > @@ -0,0 +1,857 @@
> > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > +/*
> > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > + */
> > +
> > +#include <linux/pm_runtime.h>
> > +
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-event.h>
>
> Do you need these ?
>
> > +#include <media/v4l2-ioctl.h>
> > +#include <media/v4l2-mc.h>
> > +#include <media/videobuf2-dma-contig.h>
>
> See below about using the dma-contig vb2 ops
>
> > +
> > +#include "c3-isp-common.h"
> > +#include "c3-isp-regs.h"
> > +#include "include/uapi/c3-isp-config.h"
> > +
> > +typedef void (*block_handler)(struct c3_isp_device *isp,
> > +			      struct c3_isp_param_block_header *block);
> > +
> > +struct c3_isp_block_handler {
> > +	size_t size;
> > +	block_handler handler;
> > +};
> > +
> > +/* Hardware configuration */
> > +
> > +static void c3_isp_params_cfg_wb_change(struct c3_isp_device *isp,
> > +					struct c3_isp_param_block_header *block)
> > +{
> > +	struct wb_change_cfg *wb = (struct wb_change_cfg *)block;
> > +
> > +	if (!block->enabled) {
> > +		c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
> > +				   TOP_BEO_CTRL_WB_EN, false);
> > +		return;
> > +	}
> > +
> > +	c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
> > +			   TOP_BEO_CTRL_WB_EN, TOP_BEO_CTRL_WB_EN);
> > +
> > +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, LSWB_WB_GAIN0_MASK,
> > +			   wb->wb_gain[0] << LSWB_WB_GAIN0_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0,
> > +			   LSWB_WB_GAIN1_MASK, wb->wb_gain[1]);
> > +
> > +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, LSWB_WB_GAIN2_MASK,
> > +			   wb->wb_gain[2] << LSWB_WB_GAIN2_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1,
> > +			   LSWB_WB_GAIN3_MASK, wb->wb_gain[3]);
> > +
> > +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN2,
> > +			   LSWB_WB_GAIN4_MASK, wb->wb_gain[4]);
> > +
> > +	c3_isp_write(isp, ISP_LSWB_WB_LIMIT0,
> > +		     LSWB_WB_LIMIT0(wb->wb_limit[0]) | LSWB_WB_LIMIT1(wb->wb_limit[1]));
> > +
> > +	c3_isp_write(isp, ISP_LSWB_WB_LIMIT1,
> > +		     LSWB_WB_LIMIT2(wb->wb_limit[2]) | LSWB_WB_LIMIT3(wb->wb_limit[3]));
> > +
> > +	c3_isp_update_bits(isp, ISP_LSWB_WB_LIMIT2, LSWB_WB_LIMIT4_MASK,
> > +			   wb->wb_limit[4]);
> > +
> > +	c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_BL12_GRBGI0_MASK,
> > +			   wb->ae_bl12_grbgi[0]);
> > +
> > +	c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_GAIN_GRBGI0_MASK,
> > +			   wb->ae_gain_grbgi[0] << AE_CRTL2_GAIN_GRBGI0_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_BL12_GRBGI1_MASK,
> > +			   wb->ae_bl12_grbgi[1]);
> > +
> > +	c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_GAIN_GRBGI1_MASK,
> > +			   wb->ae_gain_grbgi[1] << AE_CRTL2_GAIN_GRBGI1_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_BL12_GRBGI2_MASK,
> > +			   wb->ae_bl12_grbgi[2]);
> > +
> > +	c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_GAIN_GRBGI2_MASK,
> > +			   wb->ae_gain_grbgi[2] << AE_CRTL2_GAIN_GRBGI2_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_BL12_GRBGI3_MASK,
> > +			   wb->ae_bl12_grbgi[3]);
> > +
> > +	c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_GAIN_GRBGI3_MASK,
> > +			   wb->ae_gain_grbgi[3] << AE_CRTL2_GAIN_GRBGI3_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_BL12_GRBGI4_MASK,
> > +			   wb->ae_bl12_grbgi[4]);
> > +
> > +	c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_GAIN_GRBGI4_MASK,
> > +			   wb->ae_gain_grbgi[4] << AE_CRTL2_GAIN_GRBGI4_SHIFT);
> > +}
> > +
> > +static void c3_isp_params_cfg_wb_luma(struct c3_isp_device *isp,
> > +				      struct c3_isp_param_block_header *block)
> > +{
> > +	struct wb_luma_cfg *wb_luma = (struct wb_luma_cfg *)block;
> > +
> > +	if (!block->enabled)
> > +		return;
> > +
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_0,
> > +			   AWB_STAT_BLC20_GR_MASK, wb_luma->awb_stat_blc20[0]);
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_1,
> > +			   AWB_STAT_BLC20_R_MASK, wb_luma->awb_stat_blc20[1]);
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_2,
> > +			   AWB_STAT_BLC20_B_MASK, wb_luma->awb_stat_blc20[2]);
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_3,
> > +			   AWB_STAT_BLC20_GB_MASK, wb_luma->awb_stat_blc20[3]);
> > +
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_0,
> > +			   AWB_STAT_GAIN10_GR_MASK, wb_luma->awb_stat_gain10[0]);
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_1,
> > +			   AWB_STAT_GAIN10_R_MASK, wb_luma->awb_stat_gain10[1]);
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_2,
> > +			   AWB_STAT_GAIN10_B_MASK, wb_luma->awb_stat_gain10[2]);
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_3,
> > +			   AWB_STAT_GAIN10_GB_MASK, wb_luma->awb_stat_gain10[3]);
> > +
> > +	c3_isp_write(isp, ISP_AWB_STAT_SATUR_CTRL,
> > +		     AWB_STAT_SATUR_LOW(wb_luma->awb_stat_satur_low) |
> > +		     AWB_STAT_SATUR_HIGH(wb_luma->awb_stat_satur_high));
> > +}
> > +
> > +static void c3_isp_params_cfg_wb_triangle(struct c3_isp_device *isp,
> > +					  struct c3_isp_param_block_header *block)
> > +{
> > +	struct wb_triangle_cfg *wb = (struct wb_triangle_cfg *)block;
> > +
> > +	if (!block->enabled)
> > +		return;
> > +
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_CTRL2, AWB_STAT_SATUR_VALID_MASK,
> > +			   wb->awb_stat_satur_vald);
> > +
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MIN_MASK,
> > +			   wb->awb_stat_rg_min);
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MAX_MASK,
> > +			   wb->awb_stat_rg_max << AWB_STAT_RG_MAX_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MIN_MASK,
> > +			   wb->awb_stat_bg_min);
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MAX_MASK,
> > +			   wb->awb_stat_bg_max << AWB_STAT_BG_MAX_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_LOW_MASK,
> > +			   wb->awb_stat_rg_low);
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_HIGH_MASK,
> > +			   wb->awb_stat_rg_high << AWB_STAT_RG_HIGH_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_LOW_MASK,
> > +			   wb->awb_stat_bg_low);
> > +	c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_HIGH_MASK,
> > +			   wb->awb_stat_bg_high << AWB_STAT_BG_HIGH_SHIFT);
> > +}
> > +
> > +static void c3_isp_params_cfg_awb_stats(struct c3_isp_device *isp,
> > +					struct c3_isp_param_block_header *block)
> > +{
> > +	struct awb_stats_cfg *awb_stats = (struct awb_stats_cfg *)block;
> > +	u32 *weight = awb_stats->awb_stat_blk_weight;
> > +	int idx_base;
> > +	int group;
> > +	int i;
> > +
> > +	if (!block->enabled)
> > +		return;
> > +
> > +	c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AWB_STAT_SWITCH_MASK,
> > +			   awb_stats->awb_stat_switch << TOP_3A_AWB_STAT_SWITCH_SHIFT);
> > +
> > +	/* Calculate the group number */
> > +	group = AWB_BLOCK_WT_NUM / AWB_BLK_WT_DATA_NUM_OF_GROUP;
> > +
> > +	/* Set the weight address to 0 position */
> > +	c3_isp_write(isp, ISP_AWB_BLK_WT_ADDR, 0);
> > +	for (i = 0; i < group; i++) {
>
> you can now use
>         for (unsigned int i = 0; ...)
>
> if 'i' is not needed outside of the loop
>
> > +		idx_base = i * AWB_BLK_WT_DATA_NUM_OF_GROUP;
> > +		c3_isp_write(isp, ISP_AWB_BLK_WT_DATA,
> > +			     AWB_BLK_WT_DATA0(weight[idx_base + 0]) |
> > +			     AWB_BLK_WT_DATA1(weight[idx_base + 1]) |
> > +			     AWB_BLK_WT_DATA2(weight[idx_base + 2]) |
> > +			     AWB_BLK_WT_DATA3(weight[idx_base + 3]) |
> > +			     AWB_BLK_WT_DATA4(weight[idx_base + 4]) |
> > +			     AWB_BLK_WT_DATA5(weight[idx_base + 5]) |
> > +			     AWB_BLK_WT_DATA6(weight[idx_base + 6]) |
> > +			     AWB_BLK_WT_DATA7(weight[idx_base + 7]));
> > +	}
> > +}
> > +
> > +static void c3_isp_params_cfg_ae_stats(struct c3_isp_device *isp,
> > +				       struct c3_isp_param_block_header *block)
> > +{
> > +	struct ae_stats_cfg *ae_stats = (struct ae_stats_cfg *)block;
> > +	u32 *weight = ae_stats->ae_stat_blk_weight;
> > +	int idx_base;
> > +	int group;
> > +	int i;
> > +
> > +	if (!block->enabled)
> > +		return;
> > +
> > +	c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AE_STAT_SWITCH_MASK,
> > +			   ae_stats->ae_stat_switch << TOP_3A_AE_STAT_SWITCH_SHIFT);
> > +
> > +	/* Calculate the group number */
> > +	group = AE_BLOCK_WT_NUM / AE_BLK_WT_DATA_NUM_OF_GROUP;
> > +
> > +	/* Set the weight address to 0 position */
> > +	c3_isp_write(isp, ISP_AE_BLK_WT_ADDR, 0);
> > +	for (i = 0; i < group; i++) {
> > +		idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
> > +		c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
> > +			     AE_BLK_WT_DATA0(weight[idx_base + 0]) |
> > +			     AE_BLK_WT_DATA1(weight[idx_base + 1]) |
> > +			     AE_BLK_WT_DATA2(weight[idx_base + 2]) |
> > +			     AE_BLK_WT_DATA3(weight[idx_base + 3]) |
> > +			     AE_BLK_WT_DATA4(weight[idx_base + 4]) |
> > +			     AE_BLK_WT_DATA5(weight[idx_base + 5]) |
> > +			     AE_BLK_WT_DATA6(weight[idx_base + 6]) |
> > +			     AE_BLK_WT_DATA7(weight[idx_base + 7]));
> > +	}
> > +
> > +	/* Write the last weight data */
> > +	idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
> > +	c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
> > +		     AE_BLK_WT_DATA0(weight[idx_base + 0]) |
> > +		     AE_BLK_WT_DATA1(weight[idx_base + 1]) |
> > +		     AE_BLK_WT_DATA2(weight[idx_base + 2]) |
> > +		     AE_BLK_WT_DATA3(weight[idx_base + 3]) |
> > +		     AE_BLK_WT_DATA4(weight[idx_base + 4]) |
> > +		     AE_BLK_WT_DATA5(weight[idx_base + 5]) |
> > +		     AE_BLK_WT_DATA6(weight[idx_base + 6]));
> > +}
> > +
> > +static void c3_isp_params_cfg_af_stats(struct c3_isp_device *isp,
> > +				       struct c3_isp_param_block_header *block)
> > +{
> > +	struct af_stats_cfg *af_stats = (struct af_stats_cfg *)block;
> > +
> > +	if (!block->enabled)
> > +		return;
> > +
> > +	c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AF_STAT_SWITCH_MASK,
> > +			   af_stats->af_stat_switch << TOP_3A_AF_STAT_SWITCH_SHIFT);
> > +}
> > +
> > +static void c3_isp_params_cfg_pst_gamma(struct c3_isp_device *isp,
> > +					struct c3_isp_param_block_header *block)
> > +{
> > +	struct pst_gamma_cfg *gamma = (struct pst_gamma_cfg *)block;
> > +	int idx_base;
> > +	int i, j;
> > +
> > +	if (!block->enabled) {
> > +		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, false);
> > +		return;
> > +	}
> > +
> > +	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, TOP_BED_GAMMA_EN);
> > +
> > +	for (j = 0; j < GAMMA_LUT_GROUP_NUM; j++) {
> > +		c3_isp_write(isp, ISP_PST_GAMMA_LUT_ADDR, PST_GAMMA_LUT_ADDR(j));
> > +
> > +		/* Calculate the block number */
> > +		for (i = 0; i < GAMMA_LUT_POINT_NUM / GAMMA_LUT_DATA_NUM_OF_GROUP; i++) {
> > +			idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
> > +			c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
> > +				     PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]) |
> > +				     PST_GAMMA_LUT_DATA1(gamma->pst_gamma_lut[j][idx_base + 1]));
> > +		}
> > +
> > +		/* Write the last one lut data of group j */
> > +		idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
> > +		c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
> > +			     PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]));
> > +	}
> > +}
> > +
> > +static void c3_isp_params_cfg_dmsc(struct c3_isp_device *isp,
> > +				   struct c3_isp_param_block_header *block)
> > +{
> > +	if (!block->enabled) {
> > +		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, false);
> > +		return;
> > +	}
> > +
> > +	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, TOP_BED_DMSC_EN);
> > +}
> > +
> > +/* Configure 4 x 3 ccm matrix */
> > +static void c3_isp_params_cfg_ccm(struct c3_isp_device *isp,
> > +				  struct c3_isp_param_block_header *block)
> > +{
> > +	struct ccm_cfg *ccm = (struct ccm_cfg *)block;
> > +
> > +	if (!block->enabled) {
> > +		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, false);
> > +		return;
> > +	}
> > +
> > +	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, TOP_BED_CCM_EN);
> > +
> > +	c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_00_MASK,
> > +			   ccm->ccm_4x3matrix[0][0]);
> > +	c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_01_MASK,
> > +			   ccm->ccm_4x3matrix[0][1] << CCM_MTX_01_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_02_MASK,
> > +			   ccm->ccm_4x3matrix[0][2]);
> > +	c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_03_MASK,
> > +			   ccm->ccm_4x3matrix[0][3] << CCM_MTX_03_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_10_MASK,
> > +			   ccm->ccm_4x3matrix[1][0]);
> > +	c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_11_MASK,
> > +			   ccm->ccm_4x3matrix[1][1] << CCM_MTX_11_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_12_MASK,
> > +			   ccm->ccm_4x3matrix[1][2]);
> > +	c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_13_MASK,
> > +			   ccm->ccm_4x3matrix[1][3] << CCM_MTX_13_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_20_MASK,
> > +			   ccm->ccm_4x3matrix[2][0]);
> > +	c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_21_MASK,
> > +			   ccm->ccm_4x3matrix[2][1] << CCM_MTX_21_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_22_MASK,
> > +			   ccm->ccm_4x3matrix[2][2]);
> > +	c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_23_MASK,
> > +			   ccm->ccm_4x3matrix[2][3] << CCM_MTX_23_SHIFT);
> > +}
> > +
> > +/* Configure color space conversion matrix parameters */
> > +static void c3_isp_params_cfg_csc(struct c3_isp_device *isp,
> > +				  struct c3_isp_param_block_header *block)
> > +{
> > +	struct csc_cfg *csc = (struct csc_cfg *)block;
> > +
> > +	if (!block->enabled) {
> > +		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, false);
> > +		return;
> > +	}
> > +
> > +	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, TOP_BED_CM0_EN);
> > +
> > +	c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST0_MASK,
> > +			   csc->cm0_offset_inp[0]);
> > +	c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST1_MASK,
> > +			   csc->cm0_offset_inp[1] << CM0_INP_OFST1_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_CM0_INP_OFST2, CM0_INP_OFST2_MASK,
> > +			   csc->cm0_offset_inp[2]);
> > +
> > +	c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_00_MASK,
> > +			   csc->cm0_3x3matrix[0][0]);
> > +	c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_01_MASK,
> > +			   csc->cm0_3x3matrix[0][1] << CM0_MTX_01_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_02_MASK,
> > +			   csc->cm0_3x3matrix[0][2]);
> > +	c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_10_MASK,
> > +			   csc->cm0_3x3matrix[1][0] << CM0_MTX_10_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_11_MASK,
> > +			   csc->cm0_3x3matrix[1][1]);
> > +	c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_12_MASK,
> > +			   csc->cm0_3x3matrix[1][2] << CM0_MTX_12_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_20_MASK,
> > +			   csc->cm0_3x3matrix[2][0]);
> > +	c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_21_MASK,
> > +			   csc->cm0_3x3matrix[2][1] << CM0_MTX_21_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_MTX_22_MASK,
> > +			   csc->cm0_3x3matrix[2][2]);
> > +	c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_OFST_OUP0_MASK,
> > +			   csc->cm0_offset_oup[0] << CM0_OFST_OUP0_SHIFT);
> > +
> > +	c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP1_MASK,
> > +			   csc->cm0_offset_oup[1]);
> > +	c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP2_MASK,
> > +			   csc->cm0_offset_oup[2] << CM0_OFST_OUP2_SHIFT);
> > +	c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_MTX_RS_MASK,
> > +			   csc->cm0_3x3mtrx_rs << CM0_MTX_RS_SHIFT);
> > +}
> > +
> > +/* Set blc offset of each color channel */
> > +static void c3_isp_params_cfg_blc(struct c3_isp_device *isp,
> > +				  struct c3_isp_param_block_header *block)
> > +{
> > +	struct blc_cfg *blc = (struct blc_cfg *)block;
> > +
> > +	if (!block->enabled) {
> > +		c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN, false);
> > +		return;
> > +	}
> > +
> > +	c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN,
> > +			   TOP_BEO_CTRL_BLC_EN);
> > +
> > +	c3_isp_write(isp, ISP_FED_BL_OFST_GR, FED_BL_GR_OFST(blc->fe_bl_ofst[0]));
> > +	c3_isp_write(isp, ISP_FED_BL_OFST_R, FED_BL_R_OFST(blc->fe_bl_ofst[1]));
> > +	c3_isp_write(isp, ISP_FED_BL_OFST_B, FED_BL_B_OFST(blc->fe_bl_ofst[2]));
> > +	c3_isp_write(isp, ISP_FED_BL_OFST_GB, FED_BL_GB_OFST(blc->fe_bl_ofst[3]));
> > +	c3_isp_write(isp, ISP_FED_BL_OFST_IR, FED_BL_IR_OFST(blc->fe_bl_ofst[4]));
> > +
> > +	c3_isp_write(isp, ISP_LSWB_BLC_OFST0,
> > +		     LSWB_BLC_OFST0(blc->blc_ofst[0]) | LSWB_BLC_OFST1(blc->blc_ofst[1]));
> > +	c3_isp_write(isp, ISP_LSWB_BLC_OFST1,
> > +		     LSWB_BLC_OFST2(blc->blc_ofst[2]) | LSWB_BLC_OFST3(blc->blc_ofst[3]));
> > +	c3_isp_write(isp, ISP_LSWB_BLC_OFST2, LSWB_BLC_OFST4(blc->blc_ofst[4]));
> > +}
> > +
> > +static const struct c3_isp_block_handler c3_isp_block_handlers[] = {
> > +	[C3_ISP_PARAM_BLOCK_WB_CHANGE] = {
> > +		.size = sizeof(struct wb_change_cfg),
> > +		.handler = c3_isp_params_cfg_wb_change,
> > +	},
> > +	[C3_ISP_PARAM_BLOCK_WB_LUMA] = {
> > +		.size = sizeof(struct wb_luma_cfg),
> > +		.handler = c3_isp_params_cfg_wb_luma,
> > +	},
> > +	[C3_ISP_PARAM_BLOCK_WB_TRIANGLE] = {
> > +		.size = sizeof(struct wb_triangle_cfg),
> > +		.handler = c3_isp_params_cfg_wb_triangle,
> > +	},
> > +	[C3_ISP_PARAM_BLOCK_AWB_STATS] = {
> > +		.size = sizeof(struct awb_stats_cfg),
> > +		.handler = c3_isp_params_cfg_awb_stats,
> > +	},
> > +	[C3_ISP_PARAM_BLOCK_AE_STATS] = {
> > +		.size = sizeof(struct ae_stats_cfg),
> > +		.handler = c3_isp_params_cfg_ae_stats,
> > +	},
> > +	[C3_ISP_PARAM_BLOCK_AF_STATS] = {
> > +		.size = sizeof(struct af_stats_cfg),
> > +		.handler = c3_isp_params_cfg_af_stats,
> > +	},
> > +	[C3_ISP_PARAM_BLOCK_PST_GAMMA] = {
> > +		.size = sizeof(struct pst_gamma_cfg),
> > +		.handler = c3_isp_params_cfg_pst_gamma,
> > +	},
> > +	[C3_ISP_PARAM_BLOCK_DMSC] = {
> > +		.size = sizeof(struct dmsc_cfg),
> > +		.handler = c3_isp_params_cfg_dmsc,
> > +	},
> > +	[C3_ISP_PARAM_BLOCK_CCM] = {
> > +		.size = sizeof(struct ccm_cfg),
> > +		.handler = c3_isp_params_cfg_ccm,
> > +	},
> > +	[C3_ISP_PARAM_BLOCK_CSC] = {
> > +		.size = sizeof(struct csc_cfg),
> > +		.handler = c3_isp_params_cfg_csc,
> > +	},
> > +	[C3_ISP_PARAM_BLOCK_BLC] = {
> > +		.size = sizeof(struct blc_cfg),
> > +		.handler = c3_isp_params_cfg_blc,
> > +	},
> > +};
> > +
> > +static enum vb2_buffer_state
> > +c3_isp_params_cfg_blocks(struct c3_isp_params *params)
> > +{
> > +	struct c3_isp_params_buffer *config = params->buff->vaddr;
> > +	enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
> > +	size_t block_offset = 0;
> > +	size_t max_offset = 0;
> > +
> > +	if (config->total_size > C3_ISP_PARAMS_MAX_SIZE) {
> > +		dev_dbg(params->isp->dev, "Invalid parameters buffer size %lu\n",
> > +			config->total_size);
> > +		state = VB2_BUF_STATE_ERROR;
> > +		goto err_return_state;
> > +	}
>
> I suggest to move validation of the parameters buffer to .buf_prepare
> time.
>
> This function is called in irq context, and it's better to do all
> validation check at buffer queuing time instead.
>
> You can have a look at the rkisp1-params.c module, where
> rkisp1_params_prepare_ext_params does the validation in the
> .buf_prepare call path
>
> > +
> > +	/* Ensure config->data has a full struct c3_isp_param_block_header */
> > +	max_offset = config->total_size - sizeof(struct c3_isp_param_block_header);
> > +
> > +	while (block_offset <= max_offset) {
> > +		const struct c3_isp_block_handler *block_handler;
> > +		struct c3_isp_param_block_header *block;
> > +
> > +		block = (struct c3_isp_param_block_header *)
> > +			 &config->data[block_offset];
> > +
> > +		if (block->type >= C3_ISP_PARAM_BLOCK_SENTINEL) {
> > +			dev_dbg(params->isp->dev, "Invalid parameters block type\n");
> > +			state = VB2_BUF_STATE_ERROR;
> > +			goto err_return_state;
> > +		}
> > +
> > +		block_handler = &c3_isp_block_handlers[block->type];
> > +		if (block->size != block_handler->size) {
> > +			dev_dbg(params->isp->dev, "Invalid parameters block size\n");
> > +			state = VB2_BUF_STATE_ERROR;
> > +			goto err_return_state;
> > +		}
> > +
> > +		block_handler->handler(params->isp, block);
> > +
> > +		block_offset += block->size;
> > +	}
> > +
> > +err_return_state:
> > +	return state;
> > +}
> > +
> > +/* Initialize ISP pipeline */
> > +static int c3_isp_params_start(struct c3_isp_params *params)
> > +{
> > +	enum vb2_buffer_state state;
> > +	unsigned long flags;
> > +
> > +	/* Reset these controllers */
> > +	c3_isp_write(params->isp, ISP_TOP_FEO_CTRL0, TOP_FEO_CTRL0_ALL_DIS);
> > +	c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_0, TOP_FEO_CTRL1_0_ALL_DIS);
> > +	c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_1, TOP_FEO_CTRL1_1_ALL_DIS);
> > +	c3_isp_write(params->isp, ISP_TOP_FED_CTRL, TOP_FED_CTRL_ALL_DIS);
> > +	c3_isp_write(params->isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_ALL_DIS);
> > +	c3_isp_write(params->isp, ISP_TOP_BED_CTRL, TOP_BED_CTRL_ALL_DIS);
> > +
> > +	spin_lock_irqsave(&params->buff_lock, flags);
> > +
> > +	/* Only use the first buffer to initialize ISP */
> > +	params->buff = list_first_entry_or_null(&params->pending,
> > +						struct c3_isp_vb2_buffer, list);
> > +	if (!params->buff) {
> > +		spin_unlock_irqrestore(&params->buff_lock, flags);
> > +		return -EINVAL;
> > +	}
> > +
> > +	state = c3_isp_params_cfg_blocks(params);
> > +
> > +	spin_unlock_irqrestore(&params->buff_lock, flags);
> > +
> > +	return 0;
> > +}
> > +
> > +/* V4L2 video operations */
> > +
> > +static void c3_isp_params_return_buffers(struct c3_isp_params *params,
> > +					 enum vb2_buffer_state state)
> > +{
> > +	unsigned long flags;
> > +	struct c3_isp_vb2_buffer *buff;
> > +
> > +	spin_lock_irqsave(&params->buff_lock, flags);
> > +
> > +	while (!list_empty(&params->pending)) {
> > +		buff = list_first_entry(&params->pending,
> > +					struct c3_isp_vb2_buffer, list);
> > +		list_del(&buff->list);
> > +		vb2_buffer_done(&buff->vb.vb2_buf, state);
> > +	}
> > +
> > +	spin_unlock_irqrestore(&params->buff_lock, flags);
> > +}
> > +
> > +static int c3_isp_params_querycap(struct file *file, void *fh,
> > +				  struct v4l2_capability *cap)
> > +{
> > +	strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
> > +	strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_params_enum_fmt(struct file *file, void *fh,
> > +				  struct v4l2_fmtdesc *f)
> > +{
> > +	if (f->index)
> > +		return -EINVAL;
> > +
> > +	f->pixelformat = V4L2_META_FMT_C3ISP_PARAMS;
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_params_g_fmt(struct file *file, void *fh,
> > +			       struct v4l2_format *f)
> > +{
> > +	struct c3_isp_params *params = video_drvdata(file);
> > +
> > +	f->fmt.meta = params->vfmt.fmt.meta;
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct v4l2_ioctl_ops isp_params_v4l2_ioctl_ops = {
> > +	.vidioc_querycap                = c3_isp_params_querycap,
> > +	.vidioc_enum_fmt_meta_out       = c3_isp_params_enum_fmt,
> > +	.vidioc_g_fmt_meta_out          = c3_isp_params_g_fmt,
> > +	.vidioc_s_fmt_meta_out          = c3_isp_params_g_fmt,
> > +	.vidioc_try_fmt_meta_out        = c3_isp_params_g_fmt,
> > +	.vidioc_reqbufs                 = vb2_ioctl_reqbufs,
> > +	.vidioc_querybuf                = vb2_ioctl_querybuf,
> > +	.vidioc_qbuf                    = vb2_ioctl_qbuf,
> > +	.vidioc_expbuf                  = vb2_ioctl_expbuf,
> > +	.vidioc_dqbuf                   = vb2_ioctl_dqbuf,
> > +	.vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
> > +	.vidioc_create_bufs             = vb2_ioctl_create_bufs,
> > +	.vidioc_streamon                = vb2_ioctl_streamon,
> > +	.vidioc_streamoff               = vb2_ioctl_streamoff,
> > +	.vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
> > +	.vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
> > +};
> > +
> > +static const struct v4l2_file_operations isp_params_v4l2_fops = {
> > +	.open = v4l2_fh_open,
> > +	.release = vb2_fop_release,
> > +	.poll = vb2_fop_poll,
> > +	.unlocked_ioctl = video_ioctl2,
> > +	.mmap = vb2_fop_mmap,
> > +};
> > +
> > +static int c3_isp_params_vb2_queue_setup(struct vb2_queue *q,
> > +					 unsigned int *num_buffers,
> > +					 unsigned int *num_planes,
> > +					 unsigned int sizes[],
> > +					 struct device *alloc_devs[])
> > +{
> > +	if (*num_planes) {
> > +		if (*num_planes != 1)
> > +			return -EINVAL;
> > +
> > +		if (sizes[0] < sizeof(struct c3_isp_params_buffer))
> > +			return -EINVAL;
> > +	} else {
> > +		*num_planes = 1;
> > +		sizes[0] = sizeof(struct c3_isp_params_buffer);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static void c3_isp_params_vb2_buf_queue(struct vb2_buffer *vb)
> > +{
> > +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > +	struct c3_isp_vb2_buffer *buf =
> > +			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> > +	struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&params->buff_lock, flags);
> > +
> > +	list_add_tail(&buf->list, &params->pending);
> > +
> > +	spin_unlock_irqrestore(&params->buff_lock, flags);
> > +}
> > +
> > +static int c3_isp_params_vb2_buf_prepare(struct vb2_buffer *vb)
> > +{
> > +	struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
> > +	unsigned int size = params->vfmt.fmt.meta.buffersize;
> > +
> > +	if (vb2_plane_size(vb, 0) < size) {
>
> How does this work ?
>
> 'size' is
> 	params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
>
> if I got this right
>
> Now, as you're using an extensible parameters implementation,
> userspace is allowed to submit buffers of a smaller size, with only
> the "interesting" blocks there.
>
> This check instead makes sure that userspace always fill the
> paramteres buffer with all blocks, am I wrong ?
>
> This defeates the purpose of extensible formats, where instead
> userspace is allowed to only fill the buffers with a subset of the
> configuration blocks.
>
> Have I missed something ?
>
> > +		dev_err(params->isp->dev,
> > +			"User buffer too small (%ld < %u)\n",
> > +			vb2_plane_size(vb, 0), size);
> > +		return -EINVAL;
> > +	}
> > +
> > +	vb2_set_plane_payload(vb, 0, size);
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_params_vb2_buf_init(struct vb2_buffer *vb)
> > +{
> > +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > +	struct c3_isp_vb2_buffer *buf =
> > +		container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> > +	struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
> > +
> > +	buf->vaddr = vb2_plane_vaddr(vb, 0);
> > +	buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>
> This is not used
>
> > +
> > +	memset(buf->vaddr, 0, params->vfmt.fmt.meta.buffersize);
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_params_vb2_start_streaming(struct vb2_queue *q,
> > +					     unsigned int count)
> > +{
> > +	struct c3_isp_params *params = vb2_get_drv_priv(q);
> > +	int ret;
> > +
> > +	guard(mutex)(&params->isp->lock);
> > +
> > +	ret = pm_runtime_resume_and_get(params->isp->dev);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = video_device_pipeline_start(&params->vdev, &params->isp->pipe);
> > +	if (ret) {
> > +		dev_err(params->isp->dev,
> > +			"Failed to start params pipeline: %d\n", ret);
> > +		goto err_pm_put;
> > +	}
> > +
> > +	if (c3_isp_pipeline_ready(params->isp)) {
>
> I understand this counts how many video devices have been started...
>
>
> > +		ret = v4l2_subdev_enable_streams(&params->isp->core.sd,
> > +						 C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > +						 BIT(0));
>
> ... but does it need to be called by this module ? it's not like there is a
> parameters stream to be enabled in the ISP (see also the suggestion to
> drop routing support from there).
>
>
> > +		if (ret)
> > +			goto err_pipeline_stop;
> > +	}
> > +
> > +	c3_isp_params_start(params);
>
> Does this perform initialization and apply the first parameters from a
> queued buffer ?
>
> I'm wondering if it wouldn't be better to:
>
> - Implement a start_streaming operation on capture nodes only
> - Call the ISP's enable_streams
> - The ISP driver counts how many enabled links to resizers are there
> - If the number of enable_streams calls matches the number of enabled
>   links:
>   - call c3_isp_params_start() and any function that does the stats or
>     params setup
>   - actually start the ISP
>   - propagate the enable_streams call to the downstream subdevs (Adap,
>     then csi2-rx then sensor)
>
> > +
> > +	return 0;
> > +
> > +err_pipeline_stop:
> > +	video_device_pipeline_stop(&params->vdev);
> > +err_pm_put:
> > +	pm_runtime_put(params->isp->dev);
> > +	c3_isp_params_return_buffers(params, VB2_BUF_STATE_QUEUED);
> > +	return ret;
> > +}
> > +
> > +static void c3_isp_params_vb2_stop_streaming(struct vb2_queue *q)
> > +{
> > +	struct c3_isp_params *params = vb2_get_drv_priv(q);
> > +
> > +	guard(mutex)(&params->isp->lock);
> > +
> > +	c3_isp_params_return_buffers(params, VB2_BUF_STATE_ERROR);
>
> Even if you don't implement start_streaming as I've suggested, you
> will need this one to return buffers to userspace.
>
> > +
> > +	if (params->isp->pipe.start_count == 1)
> > +		v4l2_subdev_disable_streams(&params->isp->core.sd,
> > +					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > +					    BIT(0));
> > +
> > +	video_device_pipeline_stop(&params->vdev);
> > +	pm_runtime_put(params->isp->dev);
> > +}
> > +
> > +static const struct vb2_ops isp_params_vb2_ops = {
> > +	.queue_setup = c3_isp_params_vb2_queue_setup,
> > +	.buf_queue = c3_isp_params_vb2_buf_queue,
> > +	.buf_prepare = c3_isp_params_vb2_buf_prepare,
> > +	.buf_init = c3_isp_params_vb2_buf_init,
> > +	.wait_prepare = vb2_ops_wait_prepare,
> > +	.wait_finish = vb2_ops_wait_finish,
> > +	.start_streaming = c3_isp_params_vb2_start_streaming,
> > +	.stop_streaming = c3_isp_params_vb2_stop_streaming,
> > +};
> > +
> > +int c3_isp_params_register(struct c3_isp_device *isp)
> > +{
> > +	struct c3_isp_params *params = &isp->params;
> > +	struct video_device *vdev = &params->vdev;
> > +	struct vb2_queue *vb2_q = &params->vb2_q;
> > +	int ret;
> > +
> > +	memset(params, 0, sizeof(*params));
> > +	params->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_PARAMS;
> > +	params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
> > +	params->isp = isp;
> > +	INIT_LIST_HEAD(&params->pending);
> > +	spin_lock_init(&params->buff_lock);
> > +	mutex_init(&params->lock);
> > +
> > +	snprintf(vdev->name, sizeof(vdev->name), "isp-params");
>
> Here and in all other names, I would prefix them with "c3-"
>
> > +	vdev->fops = &isp_params_v4l2_fops;
> > +	vdev->ioctl_ops = &isp_params_v4l2_ioctl_ops;
> > +	vdev->v4l2_dev = &isp->v4l2_dev;
> > +	vdev->lock = &params->lock;
> > +	vdev->minor = -1;
> > +	vdev->queue = vb2_q;
> > +	vdev->release = video_device_release_empty;
> > +	vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING;
> > +	vdev->vfl_dir = VFL_DIR_TX;
> > +	video_set_drvdata(vdev, params);
> > +
> > +	vb2_q->drv_priv = params;
> > +	vb2_q->mem_ops = &vb2_dma_contig_memops;
>
> Do you need to use DMA ?
>
> This implementation seems to rather receive a buffer of parameters,
> inspect its content and write registers according to values there.
>
> I don't see direct DMA transfers to a memory mapped register area, but
> instead single writes to registers.
>
> Should you use vb2_vmalloc_memops instead ?
> Make sure to change the headers inclusions and Kconfig dependencies
> accordingly.
>
> > +	vb2_q->ops = &isp_params_vb2_ops;
> > +	vb2_q->type = V4L2_BUF_TYPE_META_OUTPUT;
> > +	vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
> > +	vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> > +	vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
> > +	vb2_q->dev = isp->dev;
> > +	vb2_q->lock = &params->lock;
> > +	vb2_q->min_queued_buffers = 1;
> > +
> > +	ret = vb2_queue_init(vb2_q);
> > +	if (ret)
> > +		goto err_detroy;
> > +
> > +	params->pad.flags = MEDIA_PAD_FL_SOURCE;
> > +	ret = media_entity_pads_init(&vdev->entity, 1, &params->pad);
> > +	if (ret)
> > +		goto err_queue_release;
> > +
> > +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> > +	if (ret < 0) {
> > +		dev_err(isp->dev,
> > +			"Failed to register %s: %d\n", vdev->name, ret);
> > +		goto err_entity_cleanup;
> > +	}
> > +
> > +	return 0;
> > +
> > +err_entity_cleanup:
> > +	media_entity_cleanup(&vdev->entity);
> > +err_queue_release:
> > +	vb2_queue_release(vb2_q);
> > +err_detroy:
> > +	mutex_destroy(&params->lock);
> > +	return ret;
> > +}
> > +
> > +void c3_isp_params_unregister(struct c3_isp_device *isp)
> > +{
> > +	struct c3_isp_params *params = &isp->params;
> > +
> > +	vb2_queue_release(&params->vb2_q);
> > +	media_entity_cleanup(&params->vdev.entity);
> > +	video_unregister_device(&params->vdev);
> > +	mutex_destroy(&params->lock);
> > +}
> > +
> > +int c3_isp_params_done(struct c3_isp_device *isp)
> > +{
> > +	struct c3_isp_params *params = &isp->params;
> > +	enum vb2_buffer_state state;
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&params->buff_lock, flags);
> > +
> > +	params->buff = list_first_entry_or_null(&params->pending,
> > +						struct c3_isp_vb2_buffer, list);
> > +	if (!params->buff) {
> > +		spin_unlock_irqrestore(&params->buff_lock, flags);
> > +		return -EINVAL;
> > +	}
> > +
> > +	list_del(&params->buff->list);
> > +
> > +	state = c3_isp_params_cfg_blocks(params);
> > +
> > +	params->buff->vb.sequence = params->isp->frm_sequence;
> > +	params->buff->vb.vb2_buf.timestamp = ktime_get();
> > +	params->buff->vb.field = V4L2_FIELD_NONE;
> > +	vb2_buffer_done(&params->buff->vb.vb2_buf, state);
> > +
> > +	spin_unlock_irqrestore(&params->buff_lock, flags);
> > +
> > +	return 0;
> > +}
> > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
> > new file mode 100644
> > index 000000000000..de1938f7c354
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
> > @@ -0,0 +1,683 @@
> > +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> > +/*
> > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > + */
> > +
> > +#ifndef __C3_ISP_REGS_H__
> > +#define __C3_ISP_REGS_H__
> > +
> > +#define ISP_TOP_INPUT_SIZE                       0x0000
> > +#define TOP_INPUT_HSIZE(x)                       (((x) & 0xffff) << 16)
> > +#define TOP_INPUT_VSIZE(x)                       ((x) & 0xffff)
> > +
> > +#define ISP_TOP_FRM_SIZE                         0x0004
> > +#define TOP_FRM_CORE_IHSIZE(x)                   (((x) & 0xffff) << 16)
> > +#define TOP_FRM_CORE_IVSIZE(x)                   ((x) & 0xffff)
> > +
> > +#define ISP_TOP_HOLD_SIZE                        0x0008
> > +#define TOP_HOLD_HSIZE_MASK                      GENMASK(31, 16)
> > +#define TOP_HOLD_HSIZE_SHIFT                     16
> > +
> > +#define ISP_TOP_PATH_EN                          0x0010
> > +#define TOP_DISP_EN(x)                           BIT((x))
> > +#define TOP_WRMIF_EN(x)                          BIT((x) + 8)
> > +
> > +#define ISP_TOP_PATH_SEL                         0x0014
> > +#define TOP_DATA_PATH_MASK                       GENMASK(18, 16)
> > +#define TOP_PATH_MIPI_TO_CORE                    BIT(16)
> > +
> > +#define ISP_TOP_IRQ_EN                           0x0080
> > +#define TOP_IRQ_FRAME_DONE                       BIT(0)
> > +#define TOP_IRQ_STATS_ERR                        BIT(5)
> > +
> > +#define ISP_TOP_IRQ_CLR                          0x0084
> > +#define ISP_TOP_IRQ_LINE_THRD                    0x0088
> > +#define TOP_IRQ_DIN_HSYNC                        BIT(16)
> > +
> > +#define ISP_TOP_RO_IRQ_STAT                      0x01c4
> > +#define ISP_TOP_MODE_CTRL                        0x0400
> > +#define ISP_TOP_FEO_CTRL0                        0x040c
> > +#define TOP_FEO_CTRL0_ALL_DIS                    0
> > +#define TOP_FEO_INPUT_FMT_EN                     BIT(8)
> > +
> > +#define ISP_TOP_FEO_CTRL1_0                      0x0410
> > +#define TOP_FEO_CTRL1_0_ALL_DIS                  0
> > +
> > +#define ISP_TOP_FEO_CTRL1_1                      0x0414
> > +#define TOP_FEO_CTRL1_1_ALL_DIS                  0
> > +
> > +#define ISP_TOP_FED_CTRL                         0x0418
> > +#define TOP_FED_CTRL_ALL_DIS                     0
> > +
> > +#define ISP_TOP_BEO_CTRL                         0x041c
> > +#define TOP_BEO_CTRL_ALL_DIS                     0
> > +#define TOP_BEO_CTRL_GTM_EN                      BIT(2)
> > +#define TOP_BEO_CTRL_WB_EN                       BIT(6)
> > +#define TOP_BEO_CTRL_BLC_EN                      BIT(7)
> > +#define TOP_BEO_CTRL_IDGAIN_EN                   BIT(8)
> > +#define TOP_BEO_CTRL_EOTF_EN                     BIT(9)
> > +
> > +#define ISP_TOP_BED_CTRL                         0x0420
> > +#define TOP_BED_CTRL_ALL_DIS                     0
> > +#define TOP_BED_CM0_EN                           BIT(14)
> > +#define TOP_BED_GAMMA_EN                         BIT(16)
> > +#define TOP_BED_CCM_EN                           BIT(18)
> > +#define TOP_BED_DMSC_EN                          BIT(19)
> > +
> > +#define ISP_TOP_3A_STAT_CRTL                     0x0424
> > +#define TOP_3A_AE_STAT_EN                        BIT(0)
> > +#define TOP_3A_AWB_STAT_EN                       BIT(1)
> > +#define TOP_3A_AF_STAT_EN                        BIT(2)
> > +#define TOP_3A_AWB_STAT_SWITCH_MASK              GENMASK(6, 4)
> > +#define TOP_3A_AWB_STAT_SWITCH_SHIFT             4
> > +#define TOP_3A_AWB_STAT_SWITCH_BEFORE_WB         2
> > +
> > +#define TOP_3A_AE_STAT_SWITCH_MASK               GENMASK(9, 8)
> > +#define TOP_3A_AE_STAT_SWITCH_SHIFT              8
> > +#define TOP_3A_AE_STAT_SWITCH_FROM_MLS           1
> > +#define TOP_3A_AF_STAT_SWITCH_MASK               GENMASK(13, 12)
> > +#define TOP_3A_AF_STAT_SWITCH_SHIFT              12
> > +#define TOP_3A_AF_STAT_SWITCH_FROM_SNR            0
> > +
> > +#define ISP_FED_BL_OFST_GR                       0x2018
> > +#define FED_BL_GR_OFST(x)                        ((x) & 0x1fffff)
> > +
> > +#define ISP_FED_BL_OFST_R                        0x201c
> > +#define FED_BL_R_OFST(x)                         ((x) & 0x1fffff)
> > +
> > +#define ISP_FED_BL_OFST_B                        0x2020
> > +#define FED_BL_B_OFST(x)                         ((x) & 0x1fffff)
> > +
> > +#define ISP_FED_BL_OFST_GB                       0x2024
> > +#define FED_BL_GB_OFST(x)                        ((x) & 0x1fffff)
> > +
> > +#define ISP_FED_BL_OFST_IR                       0x2028
> > +#define FED_BL_IR_OFST(x)                        ((x) & 0x1fffff)
> > +
> > +#define ISP_LSWB_BLC_OFST0                       0x4028
> > +#define LSWB_BLC_OFST1_MASK                      GENMASK(15, 0)
> > +#define LSWB_BLC_OFST1(x)                        ((x) & 0xffff)
> > +#define LSWB_BLC_OFST1_EXT(x)                    ((x) & 0xffff)
> > +#define LSWB_BLC_OFST0_MASK                      GENMASK(31, 16)
> > +#define LSWB_BLC_OFST0(x)                        (((x) & 0xffff) << 16)
> > +#define LSWB_BLC_OFST0_EXT(x)                    (((x) >> 16) & 0xffff)
> > +
> > +#define ISP_LSWB_BLC_OFST1                       0x402c
> > +#define LSWB_BLC_OFST3_MASK                      GENMASK(15, 0)
> > +#define LSWB_BLC_OFST3(x)                        ((x) & 0xffff)
> > +#define LSWB_BLC_OFST3_EXT(x)                    ((x) & 0xffff)
> > +#define LSWB_BLC_OFST2_MASK                      GENMASK(31, 16)
> > +#define LSWB_BLC_OFST2(x)                        (((x) & 0xffff) << 16)
> > +#define LSWB_BLC_OFST2_EXT(x)                    (((x) >> 16) & 0xffff)
> > +
> > +#define ISP_LSWB_BLC_OFST2                       0x4030
> > +#define LSWB_BLC_OFST4_MASK                      GENMASK(15, 0)
> > +#define LSWB_BLC_OFST4(x)                        ((x) & 0xffff)
> > +#define LSWB_BLC_OFST4_EXT(x)                    ((x) & 0xffff)
> > +
> > +#define ISP_LSWB_BLC_PHSOFST                     0x4034
> > +#define LSWB_BLC_YPHS_OFST_MASK                  GENMASK(1, 0)
> > +#define LSWB_BLC_XPHS_OFST_MASK                  GENMASK(3, 2)
> > +#define LSWB_BLC_XPHS_OFST_SHIFT                 2
> > +
> > +#define ISP_LSWB_WB_GAIN0                        0x4038
> > +#define LSWB_WB_GAIN1_MASK                       GENMASK(11, 0)
> > +#define LSWB_WB_GAIN0_MASK                       GENMASK(27, 16)
> > +#define LSWB_WB_GAIN0_SHIFT                      16
> > +
> > +#define ISP_LSWB_WB_GAIN1                        0x403c
> > +#define LSWB_WB_GAIN3_MASK                       GENMASK(11, 0)
> > +#define LSWB_WB_GAIN2_MASK                       GENMASK(27, 16)
> > +#define LSWB_WB_GAIN2_SHIFT                      16
> > +
> > +#define ISP_LSWB_WB_GAIN2                        0x4040
> > +#define LSWB_WB_GAIN4_MASK                       GENMASK(11, 0)
> > +
> > +#define ISP_LSWB_WB_LIMIT0                       0x4044
> > +#define LSWB_WB_LIMIT1(x)                        ((x) & 0xffff)
> > +#define LSWB_WB_LIMIT0(x)                        (((x) & 0xffff) << 16)
> > +
> > +#define ISP_LSWB_WB_LIMIT1                       0x4048
> > +#define LSWB_WB_LIMIT3(x)                        ((x) & 0xffff)
> > +#define LSWB_WB_LIMIT2(x)                        (((x) & 0xffff) << 16)
> > +
> > +#define ISP_LSWB_WB_LIMIT2                       0x404c
> > +#define LSWB_WB_LIMIT4_MASK                      GENMASK(15, 0)
> > +
> > +#define ISP_LSWB_WB_PHSOFST                      0x4050
> > +#define LSWB_WB_YPHS_OFST_MASK                   GENMASK(1, 0)
> > +#define LSWB_WB_XPHS_OFST_MASK                   GENMASK(3, 2)
> > +#define LSWB_WB_XPHS_OFST_SHIFT                  2
> > +
> > +#define ISP_LSWB_LNS_PHSOFST                     0x4054
> > +#define LSWB_LNS_YPHS_OFST_MASK                  GENMASK(1, 0)
> > +#define LSWB_LNS_XPHS_OFST_MASK                  GENMASK(3, 2)
> > +#define LSWB_LNS_XPHS_OFST_SHIFT                 2
> > +
> > +#define ISP_DMS_COMMON_PARAM0                    0x5000
> > +#define DMS_COMMON_YPHS_OFST_MASK                GENMASK(1, 0)
> > +#define DMS_COMMON_XPHS_OFST_MASK                GENMASK(3, 2)
> > +#define DMS_COMMON_XPHS_OFST_SHIFT               2
> > +
> > +#define ISP_CM0_INP_OFST01                       0x6040
> > +#define CM0_INP_OFST0_MASK                       GENMASK(12, 0)
> > +#define CM0_INP_OFST1_MASK                       GENMASK(28, 16)
> > +#define CM0_INP_OFST1_SHIFT                      16
> > +
> > +#define ISP_CM0_INP_OFST2                        0x6044
> > +#define CM0_INP_OFST2_MASK                       GENMASK(12, 0)
> > +
> > +#define ISP_CM0_COEF00_01                        0x6048
> > +#define CM0_MTX_00_MASK                          GENMASK(12, 0)
> > +#define CM0_MTX_01_MASK                          GENMASK(28, 16)
> > +#define CM0_MTX_01_SHIFT                         16
> > +
> > +#define ISP_CM0_COEF02_10                        0x604c
> > +#define CM0_MTX_02_MASK                          GENMASK(12, 0)
> > +#define CM0_MTX_10_MASK                          GENMASK(28, 16)
> > +#define CM0_MTX_10_SHIFT                         16
> > +
> > +#define ISP_CM0_COEF11_12                        0x6050
> > +#define CM0_MTX_11_MASK                          GENMASK(12, 0)
> > +#define CM0_MTX_12_MASK                          GENMASK(28, 16)
> > +#define CM0_MTX_12_SHIFT                         16
> > +
> > +#define ISP_CM0_COEF20_21                        0x6054
> > +#define CM0_MTX_20_MASK                          GENMASK(12, 0)
> > +#define CM0_MTX_21_MASK                          GENMASK(28, 16)
> > +#define CM0_MTX_21_SHIFT                         16
> > +
> > +#define ISP_CM0_COEF22_OUP_OFST0                 0x6058
> > +#define CM0_MTX_22_MASK                          GENMASK(12, 0)
> > +#define CM0_OFST_OUP0_MASK                       GENMASK(28, 16)
> > +#define CM0_OFST_OUP0_SHIFT                      16
> > +
> > +#define ISP_CM0_OUP_OFST12_RS                    0x605c
> > +#define CM0_OFST_OUP1_MASK                       GENMASK(12, 0)
> > +#define CM0_OFST_OUP2_MASK                       GENMASK(28, 16)
> > +#define CM0_OFST_OUP2_SHIFT                      16
> > +#define CM0_MTX_RS_MASK                          GENMASK(31, 30)
> > +#define CM0_MTX_RS_SHIFT                         30
> > +
> > +#define ISP_CCM_MTX_00_01                        0x6098
> > +#define CCM_MTX_00_MASK                          GENMASK(12, 0)
> > +#define CCM_MTX_01_MASK                          GENMASK(28, 16)
> > +#define CCM_MTX_01_SHIFT                         16
> > +
> > +#define ISP_CCM_MTX_02_03                        0x609c
> > +#define CCM_MTX_02_MASK                          GENMASK(12, 0)
> > +#define CCM_MTX_03_MASK                          GENMASK(28, 16)
> > +#define CCM_MTX_03_SHIFT                         16
> > +
> > +#define ISP_CCM_MTX_10_11                        0x60A0
> > +#define CCM_MTX_10_MASK                          GENMASK(12, 0)
> > +#define CCM_MTX_11_MASK                          GENMASK(28, 16)
> > +#define CCM_MTX_11_SHIFT                         16
> > +
> > +#define ISP_CCM_MTX_12_13                        0x60A4
> > +#define CCM_MTX_12_MASK                          GENMASK(12, 0)
> > +#define CCM_MTX_13_MASK                          GENMASK(28, 16)
> > +#define CCM_MTX_13_SHIFT                         16
> > +
> > +#define ISP_CCM_MTX_20_21                        0x60A8
> > +#define CCM_MTX_20_MASK                          GENMASK(12, 0)
> > +#define CCM_MTX_21_MASK                          GENMASK(28, 16)
> > +#define CCM_MTX_21_SHIFT                         16
> > +
> > +#define ISP_CCM_MTX_22_23_RS                     0x60Ac
> > +#define CCM_MTX_22_MASK                          GENMASK(12, 0)
> > +#define CCM_MTX_23_MASK                          GENMASK(28, 16)
> > +#define CCM_MTX_23_SHIFT                         16
> > +
> > +#define ISP_PST_GAMMA_MODE                       0x60C0
> > +#define PST_GAMMA_MODE                           BIT(0)
> > +#define PST_GAMMA_MODE_EXT(x)                    ((x) & 0x1)
> > +
> > +#define ISP_PST_GAMMA_LUT_ADDR                   0x60cc
> > +#define PST_GAMMA_LUT_ADDR(x)                    ((x) << 7)
> > +
> > +#define ISP_PST_GAMMA_LUT_DATA                   0x60d0
> > +#define PST_GAMMA_LUT_DATA0(x)                   ((x) & 0xffff)
> > +#define PST_GAMMA_LUT_DATA1(x)                   (((x) & 0xffff) << 16)
> > +
> > +#define DISP0_TOP_TOP_CTRL                       0x8000
> > +#define DISP_CRP2_EN                             BIT(5)
> > +
> > +#define DISP0_TOP_CRP2_START                     0x8004
> > +#define DISP_CRP2_VSTART_MASK                    GENMASK(15, 0)
> > +#define DISP_CRP2_VSTART(x)                      ((x) & 0xffff)
> > +#define DISP_CRP2_HSTART_MASK                    GENMASK(31, 16)
> > +#define DISP_CRP2_HSTART(x)                      (((x) & 0xffff) << 16)
> > +
> > +#define DISP0_TOP_CRP2_SIZE                      0x8008
> > +#define DISP_CRP2_VSIZE_MASK                     GENMASK(15, 0)
> > +#define DISP_CRP2_VSIZE(x)                       ((x) & 0xffff)
> > +#define DISP_CRP2_HSIZE_MASK                     GENMASK(31, 16)
> > +#define DISP_CRP2_HSIZE(x)                       (((x) & 0xffff) << 16)
> > +
> > +#define DISP0_TOP_OUT_SIZE                       0x800c
> > +#define DISP_OUT_VSIZE_MASK                      GENMASK(12, 0)
> > +#define DISP_OUT_HSIZE_MASK                      GENMASK(28, 16)
> > +#define DISP_OUT_HSIZE_SHIFT                     16
> > +
> > +#define ISP_DISP0_TOP_IN_SIZE                    0x804c
> > +#define DISP_TOP_IN_VSIZE(x)                     ((x) & 0x1fff)
> > +#define DISP_TOP_IN_HSIZE(x)                     (((x) & 0x1fff) << 16)
> > +
> > +#define DISP0_PPS_SCALE_EN                       0x8200
> > +#define PPS_VSC_TAP_NUM_MASK                     GENMASK(3, 0)
> > +#define PPS_HSC_TAP_NUM_MASK                     GENMASK(7, 4)
> > +#define PPS_HSC_TAP_NUM_SHIFT                    4
> > +#define PPS_HSC_TAP_NUM_INIT                     4
> > +#define PPS_PREVSC_FLT_NUM_MASK                  GENMASK(11, 8)
> > +#define PPS_PREVSC_FLT_NUM_SHIFT                 8
> > +#define PPS_PREHSC_FLT_NUM_MASK                  GENMASK(15, 12)
> > +#define PPS_PREHSC_FLT_NUM_SHIFT                 12
> > +#define PPS_PREHSC_FLT_NUM_INIT                  8
> > +#define PPS_PREVSC_RATE_MASK                     GENMASK(17, 16)
> > +#define PPS_PREVSC_RATE_SHIFT                    16
> > +#define PPS_PREHSC_RATE_MASK                     GENMASK(19, 18)
> > +#define PPS_PREHSC_RATE_SHIFT                    18
> > +#define PPS_HSC_EN_MASK                          BIT(20)
> > +#define PPS_HSC_EN_SHIFT                         20
> > +#define PPS_VSC_EN_MASK                          BIT(21)
> > +#define PPS_VSC_EN_SHIFT                         21
> > +#define PPS_PREVSC_EN_MASK                       BIT(22)
> > +#define PPS_PREVSC_EN_SHIFT                      22
> > +#define PPS_PREHSC_EN_MASK                       BIT(23)
> > +#define PPS_PREHSC_EN_SHIFT                      23
> > +#define PPS_HSC_NOR_RS_BITS_MASK                 GENMASK(27, 24)
> > +#define PPS_HSC_NOR_RS_BITS_SHIFT                24
> > +#define PPS_HSC_NOR_RS_BITS_INIT                 9
> > +#define PPS_VSC_NOR_RS_BITS_MASK                 GENMASK(31, 28)
> > +#define PPS_VSC_NOR_RS_BITS_SHIFT                28
> > +#define PPS_VSC_NOR_RS_BITS_INIT                 9
> > +
> > +#define DISP0_PPS_PRE_HSCALE_COEF_0              0x8204
> > +#define PPS_PREHSC_LUMA_COEF0_MASK               GENMASK(9, 0)
> > +#define PPS_PREHSC_LUMA_COEF0_INIT               128
> > +#define PPS_PREHSC_LUMA_COEF1_MASK               GENMASK(25, 16)
> > +#define PPS_PREHSC_LUMA_COEF1_SHIFT              16
> > +#define PPS_PREHSC_LUMA_COEF1_INIT               128
> > +
> > +#define DISP0_PPS_PRE_HSCALE_COEF_1              0x8208
> > +#define PPS_PREHSC_LUMA_COEF2_MASK               GENMASK(9, 0)
> > +#define PPS_PREHSC_LUMA_COEF2_INIT               32
> > +#define PPS_PREHSC_LUMA_COEF3_MASK               GENMASK(25, 16)
> > +#define PPS_PREHSC_LUMA_COEF3_SHIFT              16
> > +#define PPS_PREHSC_LUMA_COEF3_INIT               32
> > +
> > +#define DISP0_PPS_VSC_START_PHASE_STEP           0x8224
> > +#define PPS_VSC_FRACTION_PART_MASK               GENMASK(23, 0)
> > +#define PPS_VSC_INTEGER_PART_MASK                GENMASK(27, 24)
> > +#define PPS_VSC_INTEGER_PART_SHIFT               24
> > +
> > +#define DISP0_PPS_HSC_START_PHASE_STEP           0x8230
> > +#define PPS_HSC_FRACTION_PART_MASK               GENMASK(23, 0)
> > +#define PPS_HSC_INTEGER_PART_MASK                GENMASK(27, 24)
> > +#define PPS_HSC_INTEGER_PART_SHIFT               24
> > +
> > +#define DISP0_PPS_444TO422                       0x823c
> > +#define PPS_444TO422_EN_MASK                     BIT(0)
> > +
> > +#define ISP_SCALE0_COEF_IDX_LUMA                 0x8240
> > +#define SCALE_LUMA_COEF_S11_MODE                 BIT(9)
> > +#define SCALE_LUMA_CTYPE(x)                      (((x) & 0x7) << 10)
> > +
> > +#define ISP_SCALE0_COEF_LUMA                     0x8244
> > +#define SCALE_COEF_LUMA_DATA1(x)                 ((x) & 0x7ff)
> > +#define SCALE_COEF_LUMA_DATA0(x)                 (((x) & 0x7ff) << 16)
> > +
> > +#define ISP_SCALE0_COEF_IDX_CHRO                 0x8248
> > +#define SCALE_CHRO_COEF_S11_MODE                 BIT(9)
> > +#define SCALE_CHRO_CTYPE(x)                      (((x) & 0x7) << 10)
> > +
> > +#define ISP_SCALE0_COEF_CHRO                     0x824c
> > +#define SCALE_COEF_CHRO_DATA1(x)                 ((x) & 0x7ff)
> > +#define SCALE_COEF_CHRO_DATA0(x)                 (((x) & 0x7ff) << 16)
> > +
> > +#define ISP_AF_ROI0_WIN01                        0xa00c
> > +#define AF_ROI_XYWIN_00_INIT                     (50 & 0xffff)
> > +#define AF_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
> > +
> > +#define ISP_AF_ROI1_WIN01                        0xa010
> > +#define AF_ROI_XYWIN_10_INIT                     (100 & 0xffff)
> > +#define AF_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
> > +
> > +#define ISP_AF_ROI0_WIN23                        0xa014
> > +#define AF_ROI_XYWIN_02_INIT                     (80 & 0xffff)
> > +#define AF_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
> > +
> > +#define ISP_AF_ROI1_WIN23                        0xa018
> > +#define AF_ROI_XYWIN_12_INIT                     (80 & 0xffff)
> > +#define AF_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
> > +
> > +#define ISP_AF_CTRL                              0xa044
> > +#define AF_CTRL_YPHS_OFST_MASK                   GENMASK(15, 14)
> > +#define AF_CTRL_YPHS_OFST_SHIFT                  14
> > +#define AF_CTRL_XPHS_OFST_MASK                   GENMASK(17, 16)
> > +#define AF_CTRL_XPHS_OFST_SHIFT                  16
> > +
> > +#define ISP_AF_HV_SIZE                           0xa04c
> > +#define AF_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
> > +#define AF_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
> > +
> > +#define ISP_AF_HV_BLKNUM                         0xa050
> > +#define AF_HV_STAT_VBLK_NUM_MASK                 GENMASK(5, 0)
> > +#define AF_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x3f)
> > +#define AF_HV_STAT_HBLK_NUM_MASK                 GENMASK(21, 16)
> > +#define AF_HV_STAT_HBLK_NUM_SHIFT                16
> > +
> > +#define ISP_AF_EN_CTRL                           0xa054
> > +#define AF_STAT_SELECT                           BIT(21)
> > +#define AF_STAT_SELECT_SHIFT                     21
> > +
> > +#define ISP_RO_AF_GLB_STAT_PCK0                  0xa0e0
> > +#define ISP_RO_AF_GLB_STAT_PCK1                  0xa0e4
> > +#define ISP_RO_AF_GLB_STAT_PCK2                  0xa0e8
> > +#define ISP_RO_AF_GLB_STAT_PCK3                  0xa0eC
> > +#define ISP_AF_IDX_ADDR                          0xa1c0
> > +#define ISP_AF_IDX_DATA                          0xa1c4
> > +#define AF_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
> > +#define AF_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
> > +
> > +#define ISP_AE_ROI0_WIN01                        0xa40c
> > +#define AE_ROI_XYWIN_00_INIT                     (50 & 0xffff)
> > +#define AE_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
> > +
> > +#define ISP_AE_ROI1_WIN01                        0xa410
> > +#define AE_ROI_XYWIN_10_INIT                     (100 & 0xffff)
> > +#define AE_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
> > +
> > +#define ISP_AE_ROI0_WIN23                        0xa414
> > +#define AE_ROI_XYWIN_02_INIT                     (80 & 0xffff)
> > +#define AE_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
> > +
> > +#define ISP_AE_ROI1_WIN23                        0xa418
> > +#define AE_ROI_XYWIN_12_INIT                     (80 & 0xffff)
> > +#define AE_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
> > +
> > +#define ISP_RO_AE_ROI_STAT_PCK0_0                0xa424
> > +#define ISP_RO_AE_ROI_STAT_PCK1_0                0xa428
> > +#define ISP_RO_AE_ROI_STAT_PCK0_1                0xa42c
> > +#define ISP_RO_AE_ROI_STAT_PCK1_1                0xa430
> > +#define ISP_AE_CTRL                              0xa448
> > +#define AE_CTRL_AE_STAT_LOCAL_MODE               GENMASK(3, 2)
> > +#define AE_CTRL_AE_STAT_LOCAL_MODE_EXT(x)        (((x) >> 2) & 0x3)
> > +#define AE_CTRL_INPUT_2LINE_TOGETHER             BIT(7)
> > +#define AE_CTRL_LUMA_MODE_MASK                   GENMASK(9, 8)
> > +#define AE_CTRL_LUMA_MODE_SHIFT                  8
> > +#define AE_CTRL_LUMA_MODE_FILTER                 2
> > +#define AE_CTRL_YPHS_OFST_MASK                   GENMASK(25, 24)
> > +#define AE_CTRL_YPHS_OFST_SHIFT                  24
> > +#define AE_CTRL_XPHS_OFST_MASK                   GENMASK(27, 26)
> > +#define AE_CTRL_XPHS_OFST_SHIFT                  26
> > +
> > +#define ISP_AE_CRTL2_0                           0xa44c
> > +#define AE_CRTL2_BL12_GRBGI0_MASK                GENMASK(11, 0)
> > +#define AE_CRTL2_GAIN_GRBGI0_MASK                GENMASK(23, 16)
> > +#define AE_CRTL2_GAIN_GRBGI0_SHIFT               16
> > +
> > +#define ISP_AE_CRTL2_1                           0xa450
> > +#define AE_CRTL2_BL12_GRBGI1_MASK                GENMASK(11, 0)
> > +#define AE_CRTL2_GAIN_GRBGI1_MASK                GENMASK(23, 16)
> > +#define AE_CRTL2_GAIN_GRBGI1_SHIFT               16
> > +
> > +#define ISP_AE_CRTL2_2                           0xa454
> > +#define AE_CRTL2_BL12_GRBGI2_MASK                GENMASK(11, 0)
> > +#define AE_CRTL2_GAIN_GRBGI2_MASK                GENMASK(23, 16)
> > +#define AE_CRTL2_GAIN_GRBGI2_SHIFT               16
> > +
> > +#define ISP_AE_CRTL2_3                           0xa458
> > +#define AE_CRTL2_BL12_GRBGI3_MASK                GENMASK(11, 0)
> > +#define AE_CRTL2_GAIN_GRBGI3_MASK                GENMASK(23, 16)
> > +#define AE_CRTL2_GAIN_GRBGI3_SHIFT               16
> > +
> > +#define ISP_AE_CRTL2_4                           0xa45C
> > +#define AE_CRTL2_BL12_GRBGI4_MASK                GENMASK(11, 0)
> > +#define AE_CRTL2_GAIN_GRBGI4_MASK                GENMASK(23, 16)
> > +#define AE_CRTL2_GAIN_GRBGI4_SHIFT               16
> > +
> > +#define ISP_AE_HV_SIZE                           0xa464
> > +#define AE_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
> > +#define AE_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
> > +
> > +#define ISP_AE_HV_BLKNUM                         0xa468
> > +#define AE_HV_STAT_VBLK_NUM_MASK                 GENMASK(6, 0)
> > +#define AE_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x7f)
> > +#define AE_HV_STAT_HBLK_NUM_MASK                 GENMASK(22, 16)
> > +#define AE_HV_STAT_HBLK_NUM_SHIFT                16
> > +
> > +#define ISP_AE_STAT_THD01                        0xa46c
> > +#define AE_STAT_THRD0_INIT                       (13107 & 0xffff)
> > +#define AE_STAT_THRD1_INIT                       ((26214 & 0xffff) << 16)
> > +
> > +#define ISP_AE_STAT_THD23                        0xa470
> > +#define AE_STAT_THRD2_INIT                       (39321 & 0xffff)
> > +#define AE_STAT_THRD3_INIT                       ((52428 & 0xffff) << 16)
> > +
> > +#define ISP_RO_AE_STAT_GPNUM                     0xa49c
> > +#define ISP_AE_IDX_ADDR                          0xa600
> > +#define ISP_AE_IDX_DATA                          0xa604
> > +#define AE_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
> > +#define AE_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
> > +
> > +#define ISP_AE_BLK_WT_ADDR                       0xa608
> > +#define ISP_AE_BLK_WT_DATA                       0xa60c
> > +#define AE_BLK_WT_DATA0(x)                       ((x) & 0xf)
> > +#define AE_BLK_WT_DATA1(x)                       (((x) & 0xf) << 4)
> > +#define AE_BLK_WT_DATA2(x)                       (((x) & 0xf) << 8)
> > +#define AE_BLK_WT_DATA3(x)                       (((x) & 0xf) << 12)
> > +#define AE_BLK_WT_DATA4(x)                       (((x) & 0xf) << 16)
> > +#define AE_BLK_WT_DATA5(x)                       (((x) & 0xf) << 20)
> > +#define AE_BLK_WT_DATA6(x)                       (((x) & 0xf) << 24)
> > +#define AE_BLK_WT_DATA7(x)                       (((x) & 0xf) << 28)
> > +
> > +#define ISP_AWB_CTRL                             0xa834
> > +#define AWB_CTRL_YPHS_OFST_MASK                  GENMASK(1, 0)
> > +#define AWB_CTRL_XPHS_OFST_MASK                  GENMASK(3, 2)
> > +#define AWB_CTRL_XPHS_OFST_SHIFT                 2
> > +#define AWB_CTRL_STAT_RATIO_MODE                 BIT(4)
> > +
> > +#define ISP_AWB_HV_SIZE                          0xa83c
> > +#define AWB_HV_STAT_VSIZE(x)                     ((x) & 0xffff)
> > +#define AWB_HV_STAT_HSIZE(x)                     (((x) & 0xffff) << 16)
> > +
> > +#define ISP_AWB_STAT_ROI0_WIN01                  0xa80c
> > +#define AWB_ROI_XYWIN_00_INIT                    (32 & 0xffff)
> > +#define AWB_ROI_XYWIN_01_INIT                    ((62 & 0xffff) << 16)
> > +
> > +#define ISP_AWB_STAT_ROI1_WIN01                  0xa810
> > +#define AWB_ROI_XYWIN_10_INIT                    (32 & 0xffff)
> > +#define AWB_ROI_XYWIN_11_INIT                    ((82 & 0xffff) << 16)
> > +
> > +#define ISP_AWB_STAT_ROI0_WIN23                  0xa814
> > +#define AWB_ROI_XYWIN_02_INIT                    (32 & 0xffff)
> > +#define AWB_ROI_XYWIN_03_INIT                    ((42 & 0xffff) << 16)
> > +
> > +#define ISP_AWB_STAT_ROI1_WIN23                  0xa818
> > +#define AWB_ROI_XYWIN_12_INIT                    (64 & 0xffff)
> > +#define AWB_ROI_XYWIN_13_INIT                    ((52 & 0xffff) << 16)
> > +
> > +#define ISP_AWB_HV_BLKNUM                        0xa840
> > +#define AWB_HV_STAT_VBLK_NUM_MASK                GENMASK(5, 0)
> > +#define AWB_HV_STAT_VBLK_NUM_EXT(x)              ((x) & 0x3f)
> > +#define AWB_HV_STAT_HBLK_NUM_MASK                GENMASK(21, 16)
> > +#define AWB_HV_STAT_HBLK_NUM_SHIFT               16
> > +
> > +#define ISP_AWB_STAT_RG                          0xa848
> > +#define AWB_STAT_RG_MIN_MASK                     GENMASK(11, 0)
> > +#define AWB_STAT_RG_MAX_MASK                     GENMASK(27, 16)
> > +#define AWB_STAT_RG_MAX_SHIFT                    16
> > +
> > +#define ISP_AWB_STAT_BG                          0xa84c
> > +#define AWB_STAT_BG_MIN_MASK                     GENMASK(11, 0)
> > +#define AWB_STAT_BG_MAX_MASK                     GENMASK(27, 16)
> > +#define AWB_STAT_BG_MAX_SHIFT                    16
> > +
> > +#define ISP_AWB_STAT_RG_HL                       0xa850
> > +#define AWB_STAT_RG_LOW_MASK                     GENMASK(11, 0)
> > +#define AWB_STAT_RG_HIGH_MASK                    GENMASK(27, 16)
> > +#define AWB_STAT_RG_HIGH_SHIFT                   16
> > +
> > +#define ISP_AWB_STAT_BG_HL                       0xa854
> > +#define AWB_STAT_BG_LOW_MASK                     GENMASK(11, 0)
> > +#define AWB_STAT_BG_HIGH_MASK                    GENMASK(27, 16)
> > +#define AWB_STAT_BG_HIGH_SHIFT                   16
> > +
> > +#define ISP_AWB_STAT_CTRL2                       0xa858
> > +#define AWB_STAT_SATUR_VALID_MASK                BIT(0)
> > +#define AWB_STAT_LOCAL_MODE                      BIT(2)
> > +#define AWB_STAT_LOCAL_MODE_EXT(x)               (((x) >> 2) & 0x1)
> > +#define AWB_STAT_LUMA_DIV_MODE                   GENMASK(4, 3)
> > +#define AWB_STAT_LUMA_DIV_MODE_EXT(x)            (((x) >> 3) & 0x3)
> > +
> > +#define ISP_AWB_STAT_BLC20_0                     0xa85c
> > +#define AWB_STAT_BLC20_GR_MASK                   GENMASK(19, 0)
> > +
> > +#define ISP_AWB_STAT_BLC20_1                     0xa860
> > +#define AWB_STAT_BLC20_R_MASK                    GENMASK(19, 0)
> > +
> > +#define ISP_AWB_STAT_BLC20_2                     0xa864
> > +#define AWB_STAT_BLC20_B_MASK                    GENMASK(19, 0)
> > +
> > +#define ISP_AWB_STAT_BLC20_3                     0xa868
> > +#define AWB_STAT_BLC20_GB_MASK                   GENMASK(19, 0)
> > +
> > +#define ISP_AWB_STAT_GAIN10_0                    0xa86c
> > +#define AWB_STAT_GAIN10_GR_MASK                  GENMASK(9, 0)
> > +
> > +#define ISP_AWB_STAT_GAIN10_1                    0xa870
> > +#define AWB_STAT_GAIN10_R_MASK                   GENMASK(9, 0)
> > +
> > +#define ISP_AWB_STAT_GAIN10_2                    0xa874
> > +#define AWB_STAT_GAIN10_B_MASK                   GENMASK(9, 0)
> > +
> > +#define ISP_AWB_STAT_GAIN10_3                    0xa878
> > +#define AWB_STAT_GAIN10_GB_MASK                  GENMASK(9, 0)
> > +
> > +#define ISP_AWB_STAT_SATUR_CTRL                  0xa884
> > +#define AWB_STAT_SATUR_LOW(x)                    ((x) & 0xffff)
> > +#define AWB_STAT_SATUR_HIGH(x)                   (((x) & 0xffff) << 16)
> > +
> > +#define ISP_AWB_IDX_ADDR                         0xaa00
> > +#define ISP_AWB_IDX_DATA                         0xaa04
> > +#define AWB_IDX_VIDX_DATA(x)                     ((x) & 0xffff)
> > +#define AWB_IDX_HIDX_DATA(x)                     (((x) & 0xffff) << 16)
> > +
> > +#define ISP_AWB_BLK_WT_ADDR                      0xaa08
> > +#define ISP_AWB_BLK_WT_DATA                      0xaa0c
> > +#define AWB_BLK_WT_DATA0(x)                      ((x) & 0xf)
> > +#define AWB_BLK_WT_DATA1(x)                      (((x) & 0xf) << 4)
> > +#define AWB_BLK_WT_DATA2(x)                      (((x) & 0xf) << 8)
> > +#define AWB_BLK_WT_DATA3(x)                      (((x) & 0xf) << 12)
> > +#define AWB_BLK_WT_DATA4(x)                      (((x) & 0xf) << 16)
> > +#define AWB_BLK_WT_DATA5(x)                      (((x) & 0xf) << 20)
> > +#define AWB_BLK_WT_DATA6(x)                      (((x) & 0xf) << 24)
> > +#define AWB_BLK_WT_DATA7(x)                      (((x) & 0xf) << 28)
> > +
> > +#define ISP_WRMIFX3_0_CH0_CTRL0                  0xc400
> > +#define WRMIFX3_CH0_STRIDE_MASK                  GENMASK(28, 16)
> > +#define WRMIFX3_CH0_STRIDE_SHIFT                 16
> > +
> > +#define ISP_WRMIFX3_0_CH0_CTRL1                  0xc404
> > +#define WRMIFX3_CH0_PIX_BITS_MODE_MASK           GENMASK(30, 27)
> > +#define WRMIFX3_CH0_PIX_BITS_MODE_SHIFT          27
> > +#define WRMIFX3_CH0_PIX_BITS_8BITS               1
> > +#define WRMIFX3_CH0_PIX_BITS_16BITS              2
> > +
> > +#define ISP_WRMIFX3_0_CH1_CTRL0                  0xc408
> > +#define WRMIFX3_CH1_STRIDE_MASK                  GENMASK(28, 16)
> > +#define WRMIFX3_CH1_STRIDE_SHIFT                 16
> > +
> > +#define ISP_WRMIFX3_0_CH1_CTRL1                  0xc40c
> > +#define WRMIFX3_CH1_PIX_BITS_MODE_MASK           GENMASK(30, 27)
> > +#define WRMIFX3_CH1_PIX_BITS_MODE_SHIFT          27
> > +#define WRMIFX3_CH1_PIX_BITS_16BITS              2
> > +#define WRMIFX3_CH1_PIX_BITS_32BITS              3
> > +
> > +#define ISP_WRMIFX3_0_CH0_BADDR                  0xc440
> > +/* WRMIF base address need 16 bits alignment */
> > +#define WRMIFX3_CH0_BADDR(x)                     (((x) >> 4) & 0xffffffff)
> > +
> > +#define ISP_WRMIFX3_0_CH1_BADDR                  0xc444
> > +#define WRMIFX3_CH1_BADDR(x)                     (((x) >> 4) & 0xffffffff)
> > +
> > +#define ISP_WRMIFX3_0_FMT_SIZE                   0xc464
> > +#define WRMIFX3_FMT_HSIZE(x)                     ((x) & 0xffff)
> > +#define WRMIFX3_FMT_VSIZE(x)                     (((x) & 0xffff) << 16)
> > +
> > +#define ISP_WRMIFX3_0_FMT_CTRL                   0xc468
> > +#define WRMIFX3_FMT_MTX_IBITS_MASK               GENMASK(1, 0)
> > +#define WRMIFX3_FMT_MTX_IBITS_8BIT               0
> > +#define WRMIFX3_FMT_MTX_IBITS_10BIT              1
> > +#define WRMIFX3_FMT_MTX_IBITS_12BIT              2
> > +#define WRMIFX3_FMT_MTX_IBITS_16BIT              3
> > +#define WRMIFX3_FMT_MTX_UV_SWAP_MASK             BIT(2)
> > +#define WRMIFX3_FMT_MTX_UV_SWAP_SHIFT            2
> > +#define WRMIFX3_FMT_MTX_UV_SWAP_VU               0
> > +#define WRMIFX3_FMT_MTX_UV_SWAP_UV               1
> > +#define WRMIFX3_FMT_MTX_PLANE_MASK               GENMASK(5, 4)
> > +#define WRMIFX3_FMT_MTX_PLANE_SHIFT              4
> > +#define WRMIFX3_FMT_MTX_PLANE_X1                 0
> > +#define WRMIFX3_FMT_MTX_PLANE_X2                 1
> > +#define WRMIFX3_FMT_MODE_OUT_MASK                GENMASK(18, 16)
> > +#define WRMIFX3_FMT_MODE_OUT_SHIFT               16
> > +#define WRMIFX3_FMT_MODE_OUT_YUV422              1
> > +#define WRMIFX3_FMT_MODE_OUT_YUV420              2
> > +#define WRMIFX3_FMT_MODE_OUT_Y_ONLY              3
> > +
> > +#define ISP_WRMIFX3_0_WIN_LUMA_H                 0xc420
> > +#define WRMIFX3_WIN_LUMA_HEND_MASK               GENMASK(28, 16)
> > +#define WRMIFX3_WIN_LUMA_HEND_SHIFT              16
> > +#define WRMIFX3_WIN_LUMA_HEND(x)                 ((x) - 1)
> > +
> > +#define ISP_WRMIFX3_0_WIN_LUMA_V                 0xc424
> > +#define WRMIFX3_WIN_LUMA_VEND_MASK               GENMASK(28, 16)
> > +#define WRMIFX3_WIN_LUMA_VEND_SHIFT              16
> > +#define WRMIFX3_WIN_LUMA_VEND(x)                 ((x) - 1)
> > +
> > +#define ISP_WRMIFX3_0_WIN_CHROM_H                0xc428
> > +#define WRMIFX3_WIN_CHROM_HEND_MASK              GENMASK(28, 16)
> > +#define WRMIFX3_WIN_CHROM_HEND_SHIFT             16
> > +#define WRMIFX3_WIN_CHROM_HEND(x)                (((x) >> 1) - 1)
> > +
> > +#define ISP_WRMIFX3_0_WIN_CHROM_V                0xc42c
> > +#define WRMIFX3_WIN_CHROM_VEND_MASK              GENMASK(28, 16)
> > +#define WRMIFX3_WIN_CHROM_VEND_SHIFT             16
> > +#define WRMIFX3_WIN_CHROM_VEND(x)                (((x) >> 1) - 1)
> > +
> > +#define ISP_WRMIFX3_0_CRP_HSIZE                  0xc48c
> > +#define WRMIFX3_CROP_HEND_MASK                   GENMASK(31, 16)
> > +#define WRMIFX3_CROP_HEND_SHIFT                  16
> > +#define WRMIFX3_CROP_HEND(x)                     ((x) - 1)
> > +
> > +#define ISP_WRMIFX3_0_CRP_VSIZE                  0xc490
> > +#define WRMIFX3_CROP_VEND_MASK                   GENMASK(31, 16)
> > +#define WRMIFX3_CROP_VEND_SHIFT                  16
> > +#define WRMIFX3_CROP_VEND(x)                     ((x) - 1)
> > +
> > +#define VIU_DMAWR_BADDR0                         0xc840
> > +#define VIU_DMAWR_AF_BADDR_MASK                  GENMASK(27, 0)
> > +/* AF base address need 16 bits alignment */
> > +#define VIU_DMAWR_AF_BADDR(x)                    ((x) >> 4)
> > +
> > +#define VIU_DMAWR_BADDR1                         0xc844
> > +#define VIU_DMAWR_AWB_BADDR_MASK                  GENMASK(27, 0)
> > +/* AWB base address need 16 bits alignment */
> > +#define VIU_DMAWR_AWB_BADDR(x)                    ((x) >> 4)
> > +
> > +#define VIU_DMAWR_BADDR2                         0xc848
> > +#define VIU_DMAWR_AE_BADDR_MASK                  GENMASK(27, 0)
> > +/* AE base address need 16 bits alignment */
> > +#define VIU_DMAWR_AE_BADDR(x)                    ((x) >> 4)
> > +
> > +#define VIU_DMAWR_SIZE0                          0xc854
> > +#define VIU_DMAWR_SIZE_AF_MASK                   GENMASK(15, 0)
> > +#define VIU_DMAWR_SIZE_AWB_MASK                  GENMASK(31, 16)
> > +#define VIU_DMAWR_SIZE_AWB_SHIFT                 16
> > +
> > +#define VIU_DMAWR_SIZE1                          0xc858
> > +#define VIU_DMAWR_SIZE_AE_MASK                   GENMASK(15, 0)
> > +
> > +#endif
> > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
> > new file mode 100644
> > index 000000000000..01d99b66cb32
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
> > @@ -0,0 +1,768 @@
> > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > +/*
> > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > + */
> > +
> > +#include <linux/pm_runtime.h>
> > +
> > +#include "c3-isp-common.h"
> > +#include "c3-isp-regs.h"
> > +
> > +static const struct c3_isp_mbus_format_info c3_isp_rsz_mbus_formats[] = {
> > +	/* YUV formats */
> > +	{
> > +		.mbus_code	= MEDIA_BUS_FMT_Y8_1X8,
> > +		.pads		= BIT(C3_ISP_RESIZER_PAD_SINK)
> > +				| BIT(C3_ISP_RESIZER_PAD_SOURCE),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> > +	}, {
> > +		.mbus_code	= MEDIA_BUS_FMT_YUYV8_1_5X8,
> > +		.pads		= BIT(C3_ISP_RESIZER_PAD_SINK)
> > +				| BIT(C3_ISP_RESIZER_PAD_SOURCE),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> > +	}, {
> > +		.mbus_code	= MEDIA_BUS_FMT_YUYV8_2X8,
>
> These mbus_codes come from the ISP, I presume after debayering.
> Is the different samples number (5X8, 2X8) a representation of the
> format on the internal bus between the ISP and the resizers ?
>
> > +		.pads		= BIT(C3_ISP_RESIZER_PAD_SINK)
> > +				| BIT(C3_ISP_RESIZER_PAD_SOURCE),
> > +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> > +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> > +	},
> > +};
> > +
> > +/* The normal parameters of pps module */
> > +static const int pps_lut_tap4_s11[C3_PPS_TAP4_S11_H_NUM][4] =  {
> > +	{  0, 511,   0,   0}, { -5, 511,   5,   0}, {-10, 511,  11,   0},
> > +	{-14, 510,  17,  -1}, {-18, 508,  23,  -1}, {-22, 506,  29,  -1},
> > +	{-25, 503,  36,  -2}, {-28, 500,  43,  -3}, {-32, 496,  51,  -3},
> > +	{-34, 491,  59,  -4}, {-37, 487,  67,  -5}, {-39, 482,  75,  -6},
> > +	{-41, 476,  84,  -7}, {-42, 470,  92,  -8}, {-44, 463, 102,  -9},
> > +	{-45, 456, 111, -10}, {-45, 449, 120, -12}, {-47, 442, 130, -13},
> > +	{-47, 434, 140, -15}, {-47, 425, 151, -17}, {-47, 416, 161, -18},
> > +	{-47, 407, 172, -20}, {-47, 398, 182, -21}, {-47, 389, 193, -23},
> > +	{-46, 379, 204, -25}, {-45, 369, 215, -27}, {-44, 358, 226, -28},
> > +	{-43, 348, 237, -30}, {-43, 337, 249, -31}, {-41, 326, 260, -33},
> > +	{-40, 316, 271, -35}, {-39, 305, 282, -36}, {-37, 293, 293, -37}
> > +};
> > +
> > +static const struct c3_isp_mbus_format_info
> > +*rsz_find_format_by_code(u32 code, u32 pad)
> > +{
> > +	int i;
>
> unsigned
>
> > +
> > +	for (i = 0; i < ARRAY_SIZE(c3_isp_rsz_mbus_formats); i++) {
> > +		const struct c3_isp_mbus_format_info *info =
> > +			&c3_isp_rsz_mbus_formats[i];
> > +
> > +		if (info->mbus_code == code && info->pads & BIT(pad))
> > +			return info;
> > +	}
> > +
> > +	return NULL;
> > +}
> > +
> > +static void c3_isp_rsz_cfg_fmt(struct c3_isp_resizer *rsz,
> > +			       struct v4l2_subdev_state *state)
> > +{
> > +	struct v4l2_mbus_framefmt *fmt;
> > +
> > +	fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> > +
> > +	c3_isp_write(rsz->isp, C3_DISP_REG(ISP_DISP0_TOP_IN_SIZE, rsz->id),
> > +		     DISP_TOP_IN_HSIZE(fmt->width) | DISP_TOP_IN_VSIZE(fmt->height));
> > +}
> > +
> > +static void c3_isp_rsz_crop_enable(struct c3_isp_resizer *rsz,
> > +				   struct v4l2_subdev_state *state)
> > +{
> > +	struct v4l2_rect *crop;
> > +
> > +	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> > +
> > +	c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_START, rsz->id),
> > +		     DISP_CRP2_VSTART(crop->top) | DISP_CRP2_HSTART(crop->left));
> > +	c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_SIZE, rsz->id),
> > +		     DISP_CRP2_VSIZE(crop->height) | DISP_CRP2_HSIZE(crop->width));
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
> > +			   DISP_CRP2_EN, DISP_CRP2_EN);
> > +}
> > +
> > +static void c3_isp_rsz_pps_size(struct c3_isp_resizer *rsz,
> > +				struct c3_isp_pps_io_size *io_size)
> > +{
> > +	int thsize = io_size->thsize;
> > +	int tvsize = io_size->tvsize;
> > +	u32 ohsize = io_size->ohsize;
> > +	u32 ovsize = io_size->ovsize;
> > +	u32 ihsize = io_size->ihsize;
> > +	u32 max_hsize = io_size->max_hsize;
> > +	int step_h_integer, step_v_integer;
> > +	int step_h_fraction, step_v_fraction;
> > +	int yuv444to422_en;
> > +
> > +	/* Calculate the integer part of horizonal scaler step */
> > +	step_h_integer = thsize / ohsize;
> > +
> > +	/* Calculate the vertical part of horizonal scaler step */
> > +	step_v_integer = tvsize / ovsize;
> > +
> > +	/*
> > +	 * Calculate the fraction part of horizonal scaler step.
> > +	 * step_h_fraction = (source / dest) * 2^24,
> > +	 * so step_h_fraction = ((source << 12) / dest) << 12.
> > +	 */
> > +	step_h_fraction = ((thsize << 12) / ohsize) << 12;
> > +
> > +	/*
> > +	 * Calculate the fraction part of vertical scaler step
> > +	 * step_v_fraction = (source / dest) * 2^24,
> > +	 * so step_v_fraction = ((source << 12) / dest) << 12.
> > +	 */
> > +	step_v_fraction = ((tvsize << 12) / ovsize) << 12;
> > +
> > +	yuv444to422_en = ihsize > (max_hsize / 2) ? 1 : 0;
> > +
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_444TO422, rsz->id),
> > +			   PPS_444TO422_EN_MASK, yuv444to422_en);
> > +
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
> > +			   PPS_VSC_FRACTION_PART_MASK, step_v_fraction);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
> > +			   PPS_VSC_INTEGER_PART_MASK,
> > +			   step_v_integer << PPS_VSC_INTEGER_PART_SHIFT);
> > +
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
> > +			   PPS_HSC_FRACTION_PART_MASK, step_h_fraction);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
> > +			   PPS_HSC_INTEGER_PART_MASK,
> > +			   step_h_integer << PPS_HSC_INTEGER_PART_SHIFT);
> > +
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
> > +			   PPS_PREHSC_LUMA_COEF0_MASK, PPS_PREHSC_LUMA_COEF0_INIT);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
> > +			   PPS_PREHSC_LUMA_COEF1_MASK,
> > +			   PPS_PREHSC_LUMA_COEF1_INIT << PPS_PREHSC_LUMA_COEF1_SHIFT);
> > +
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
> > +			   PPS_PREHSC_LUMA_COEF2_MASK, PPS_PREHSC_LUMA_COEF2_INIT);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
> > +			   PPS_PREHSC_LUMA_COEF3_MASK,
> > +			   PPS_PREHSC_LUMA_COEF3_INIT << PPS_PREHSC_LUMA_COEF3_SHIFT);
> > +}
> > +
> > +static void c3_isp_rsz_pps_lut(struct c3_isp_resizer *rsz, u32 ctype)
> > +{
> > +	int i;
>
> unsigned
>
> > +
> > +	/*
> > +	 * Default value of this register is 0,
> > +	 * so only need to set SCALE_LUMA_COEF_S11_MODE
> > +	 * and SCALE_LUMA_CTYPE.
>
> You can fit this in 2 lines ?
>
> > +	 * This register needs to be written in one time.
> > +	 */
> > +	c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_LUMA, rsz->id),
> > +		     SCALE_LUMA_COEF_S11_MODE | SCALE_LUMA_CTYPE(ctype));
> > +
> > +	for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
> > +		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
> > +			     SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
> > +			     SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
> > +		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
> > +			     SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
> > +			     SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][3]));
> > +	}
> > +
> > +	/*
> > +	 * Default value of this register is 0,
> > +	 * so only need to set SCALE_CHRO_COEF_S11_MODE
> > +	 * and SCALE_CHRO_CTYPE.
>
> same
>
> > +	 * This register needs to be written in one time.
> > +	 */
> > +	c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_CHRO, rsz->id),
> > +		     SCALE_CHRO_COEF_S11_MODE | SCALE_CHRO_CTYPE(ctype));
> > +
> > +	for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
> > +		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
> > +			     SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
> > +			     SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
> > +		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
> > +			     SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
> > +			     SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][3]));
> > +	}
> > +}
> > +
> > +static void c3_isp_rsz_pps_disable(struct c3_isp_resizer *rsz)
> > +{
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_HSC_EN_MASK, 0);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_VSC_EN_MASK, 0);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_PREVSC_EN_MASK, 0);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_PREHSC_EN_MASK, 0);
> > +}
> > +
> > +static int c3_isp_rsz_pps_enable(struct c3_isp_resizer *rsz,
> > +				 struct v4l2_subdev_state *state)
> > +{
> > +	struct v4l2_rect *crop;
> > +	struct v4l2_rect *cmps;
> > +	int max_hsize;
> > +	int hsc_en, vsc_en;
> > +	int preh_en, prev_en;
> > +	u32 reg_prehsc_rate;
> > +	u32 reg_prevsc_flt_num;
> > +	int pre_vscale_max_hsize;
> > +	u32 ihsize_after_pre_hsc;
> > +	u32 ihsize_after_pre_hsc_alt;
> > +	u32 reg_vsc_tap_num_alt;
> > +	u32 ihsize;
> > +	u32 ivsize;
> > +	struct c3_isp_pps_io_size io_size;
> > +
> > +	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> > +	cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> > +
> > +	ihsize = crop->width;
> > +	ivsize = crop->height;
> > +
> > +	hsc_en = (ihsize == cmps->width) ? C3_SCALE_DIS : C3_SCALE_EN;
> > +	vsc_en = (ivsize == cmps->height) ? C3_SCALE_DIS : C3_SCALE_EN;
> > +
> > +	/* Disable pps when there no need to use pps */
> > +	if (!hsc_en && !vsc_en) {
> > +		c3_isp_rsz_pps_disable(rsz);
> > +		return 0;
> > +	}
> > +
> > +	/*
> > +	 * Pre-scale needs to be enable
> > +	 * if the down scaling factor exceeds 4.
> > +	 */
> > +	preh_en = (ihsize > cmps->width * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
> > +	prev_en = (ivsize > cmps->height * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
> > +
> > +	if (rsz->id == C3_ISP_RSZ_2) {
> > +		max_hsize = C3_ISP_MAX_WIDTH;
> > +		/*
> > +		 * Set vertical tap number and
> > +		 * the max hsize of pre-vertical scale.
> > +		 */
> > +		reg_prevsc_flt_num = 4;
> > +		pre_vscale_max_hsize = max_hsize / 2;
> > +	} else {
> > +		max_hsize = C3_ISP_DEFAULT_WIDTH;
> > +		preh_en  = (ihsize > max_hsize) ? C3_SCALE_EN : C3_SCALE_DIS;
> > +		/*
> > +		 * Set vertical tap number and
> > +		 * the max hsize of pre-vertical scale.
> > +		 */
> > +		if (ihsize > (max_hsize / 2) &&
> > +		    ihsize <= max_hsize && prev_en) {
> > +			reg_prevsc_flt_num = 2;
> > +			pre_vscale_max_hsize = max_hsize;
> > +		} else {
> > +			reg_prevsc_flt_num = 4;
> > +			pre_vscale_max_hsize = max_hsize / 2;
> > +		}
> > +	}
> > +
> > +	/*
> > +	 * Set pre-horizonal scale rate and
> > +	 * the hsize of after pre-horizonal scale.
> > +	 */
> > +	if (preh_en) {
> > +		reg_prehsc_rate = 1;
> > +		ihsize_after_pre_hsc = DIV_ROUND_UP(ihsize, 2);
> > +	} else {
> > +		reg_prehsc_rate = 0;
> > +		ihsize_after_pre_hsc = ihsize;
> > +	}
> > +
> > +	/* Change pre-horizonal scale rate */
> > +	if (prev_en && ihsize_after_pre_hsc >= pre_vscale_max_hsize)
> > +		reg_prehsc_rate += 1;
> > +
> > +	/* Set the actual hsize of after pre-horizonal scale */
> > +	if (preh_en)
> > +		ihsize_after_pre_hsc_alt =
> > +			DIV_ROUND_UP(ihsize, 1 << reg_prehsc_rate);
> > +	else
> > +		ihsize_after_pre_hsc_alt = ihsize;
> > +
> > +	/* Set vertical scaler bank length */
> > +	if (ihsize_after_pre_hsc_alt <= (max_hsize / 2))
> > +		reg_vsc_tap_num_alt = 4;
> > +	else if (ihsize_after_pre_hsc_alt <= max_hsize)
> > +		reg_vsc_tap_num_alt = prev_en ? 2 : 4;
> > +	else
> > +		reg_vsc_tap_num_alt = prev_en ? 4 : 2;
> > +
> > +	io_size.thsize = ihsize_after_pre_hsc_alt;
> > +	io_size.tvsize = prev_en ? DIV_ROUND_UP(ivsize, 2) : ivsize;
> > +	io_size.ohsize = cmps->width;
> > +	io_size.ovsize = cmps->height;
> > +	io_size.ihsize = ihsize;
> > +	io_size.max_hsize = max_hsize;
> > +
> > +	c3_isp_rsz_pps_size(rsz, &io_size);
> > +	c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_0);
> > +	c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_2);
> > +
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_VSC_TAP_NUM_MASK, reg_vsc_tap_num_alt);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_HSC_TAP_NUM_MASK,
> > +			   PPS_HSC_TAP_NUM_INIT << PPS_HSC_TAP_NUM_SHIFT);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_PREVSC_FLT_NUM_MASK,
> > +			   reg_prevsc_flt_num << PPS_PREVSC_FLT_NUM_SHIFT);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_PREHSC_FLT_NUM_MASK,
> > +			   PPS_PREHSC_FLT_NUM_INIT << PPS_PREHSC_FLT_NUM_SHIFT);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_PREVSC_RATE_MASK, prev_en << PPS_PREVSC_RATE_SHIFT);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_PREHSC_RATE_MASK, reg_prehsc_rate << PPS_PREHSC_RATE_SHIFT);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_HSC_EN_MASK, hsc_en << PPS_HSC_EN_SHIFT);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_VSC_EN_MASK, vsc_en << PPS_VSC_EN_SHIFT);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_PREVSC_EN_MASK, prev_en << PPS_PREVSC_EN_SHIFT);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_PREHSC_EN_MASK, preh_en << PPS_PREHSC_EN_SHIFT);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_HSC_NOR_RS_BITS_MASK,
> > +			   PPS_HSC_NOR_RS_BITS_INIT << PPS_HSC_NOR_RS_BITS_SHIFT);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > +			   PPS_VSC_NOR_RS_BITS_MASK,
> > +			   PPS_VSC_NOR_RS_BITS_INIT << PPS_VSC_NOR_RS_BITS_SHIFT);
> > +
> > +	return 0;
> > +}
> > +
> > +void c3_isp_rsz_start(struct c3_isp_resizer *rsz)
> > +{
> > +	struct v4l2_subdev_state *state;
> > +
> > +	state = v4l2_subdev_lock_and_get_active_state(&rsz->sd);
> > +
> > +	c3_isp_rsz_cfg_fmt(rsz, state);
> > +	c3_isp_rsz_crop_enable(rsz, state);
> > +	c3_isp_rsz_pps_enable(rsz, state);
> > +
> > +	c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN,
> > +			   TOP_DISP_EN(rsz->id), TOP_DISP_EN(rsz->id));
> > +
> > +	v4l2_subdev_unlock_state(state);
> > +}
> > +
> > +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
> > +{
> > +	c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, TOP_DISP_EN(rsz->id), 0);
> > +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
> > +			   DISP_CRP2_EN, 0x0);
> > +
> > +	c3_isp_rsz_pps_disable(rsz);
> > +}
> > +
> > +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
> > +				  struct v4l2_subdev_state *state,
> > +				  struct v4l2_subdev_krouting *routing)
> > +{
> > +	static const struct v4l2_mbus_framefmt format = {
> > +		.width = C3_ISP_DEFAULT_WIDTH,
> > +		.height = C3_ISP_DEFAULT_HEIGHT,
> > +		.code = C3_ISP_RSZ_DEF_PAD_FMT,
> > +		.field = V4L2_FIELD_NONE,
> > +		.colorspace = V4L2_COLORSPACE_SRGB,
> > +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> > +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
> > +		.xfer_func = V4L2_XFER_FUNC_SRGB,
> > +	};
> > +	int ret;
> > +
> > +	ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
> > +				   struct v4l2_subdev_state *state)
> > +{
> > +	struct v4l2_subdev_route routes;
> > +	struct v4l2_subdev_krouting routing;
> > +
> > +	routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
> > +	routes.sink_stream = 0;
> > +	routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
> > +	routes.source_stream = 0;
> > +	routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> > +
> > +	routing.num_routes = 1;
> > +	routing.routes = &routes;
> > +
> > +	return c3_isp_rsz_cfg_routing(sd, state, &routing);
> > +}
> > +
> > +static int c3_isp_rsz_set_routing(struct v4l2_subdev *sd,
> > +				  struct v4l2_subdev_state *state,
> > +				  enum v4l2_subdev_format_whence which,
> > +				  struct v4l2_subdev_krouting *routing)
> > +{
> > +	bool is_streaming = v4l2_subdev_is_streaming(sd);
> > +
> > +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> > +		return -EBUSY;
> > +
> > +	return c3_isp_rsz_cfg_routing(sd, state, routing);
> > +}
>
> Unless there are reasons I missed, I would drop routing support from
> the resizers
>
> > +
> > +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
> > +				     struct v4l2_subdev_state *state,
> > +				     struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > +	if (code->index >= ARRAY_SIZE(c3_isp_rsz_mbus_formats))
> > +		return -EINVAL;
> > +
> > +	code->code = c3_isp_rsz_mbus_formats[code->index].mbus_code;
> > +
> > +	return 0;
> > +}
> > +
> > +static void c3_isp_rsz_set_sink_fmt(struct v4l2_subdev_state *state,
> > +				    struct v4l2_subdev_format *format)
> > +{
> > +	struct v4l2_mbus_framefmt *sink_fmt;
> > +	const struct c3_isp_mbus_format_info *isp_fmt;
> > +	struct v4l2_rect *sink_crop;
> > +	struct v4l2_rect *sink_cmps;
> > +
> > +	sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
> > +	sink_crop = v4l2_subdev_state_get_crop(state, format->pad);
> > +	sink_cmps = v4l2_subdev_state_get_compose(state, format->pad);
> > +
> > +	isp_fmt = rsz_find_format_by_code(format->format.code, format->pad);
> > +	if (!isp_fmt)
> > +		sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> > +	else
> > +		sink_fmt->code = format->format.code;
> > +
> > +	sink_fmt->width = clamp_t(u32, format->format.width,
> > +				  C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> > +	sink_fmt->height = clamp_t(u32, format->format.height,
> > +				   C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> > +
> > +	sink_crop->width = sink_fmt->width;
> > +	sink_crop->height = sink_fmt->height;
> > +	sink_crop->left = 0;
> > +	sink_crop->top = 0;
> > +
> > +	sink_cmps->width = sink_crop->width;
> > +	sink_cmps->height = sink_crop->height;
> > +	sink_cmps->left = 0;
> > +	sink_cmps->top = 0;
> > +
> > +	format->format = *sink_fmt;
> > +}
> > +
> > +static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state,
> > +				      struct v4l2_subdev_format *format)
> > +{
> > +	const struct c3_isp_mbus_format_info *rsz_fmt;
> > +	struct v4l2_mbus_framefmt *src_fmt;
> > +	struct v4l2_rect *sink_crop;
> > +	struct v4l2_rect *sink_cmps;
> > +
> > +	src_fmt = v4l2_subdev_state_get_format(state, format->pad);
> > +	sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> > +	sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> > +
> > +	rsz_fmt = rsz_find_format_by_code(format->format.code, format->pad);
> > +	if (!rsz_fmt)
> > +		src_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> > +	else
> > +		src_fmt->code = format->format.code;
> > +
> > +	src_fmt->width = clamp_t(u32, format->format.width,
> > +				 C3_ISP_MIN_WIDTH, sink_crop->width);
> > +	src_fmt->height = clamp_t(u32, format->format.height,
> > +				  C3_ISP_MIN_HEIGHT, sink_crop->height);
> > +
> > +	/* The sink compose size must be same with the source size. */
> > +	sink_cmps->width = src_fmt->width;
> > +	sink_cmps->height = src_fmt->height;
>
> Shouldn't it be the other way around ? The source sizes should always
> match the sink compose rectangle sizes ?
>
> > +
> > +	format->format = *src_fmt;
> > +}
> > +
> > +static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd,
> > +			      struct v4l2_subdev_state *state,
> > +			      struct v4l2_subdev_format *format)
> > +{
> > +	if (format->pad == C3_ISP_RESIZER_PAD_SINK) {
> > +		c3_isp_rsz_set_sink_fmt(state, format);
> > +	} else if (format->pad == C3_ISP_RESIZER_PAD_SOURCE) {
> > +		c3_isp_rsz_set_source_fmt(state, format);
> > +	} else {
>
> This can't happen
>
> > +		dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
> > +		return -ENOTTY;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd,
> > +				    struct v4l2_subdev_state *state,
> > +				    struct v4l2_subdev_selection *sel)
> > +{
> > +	struct v4l2_mbus_framefmt *fmt;
> > +	struct v4l2_rect *crop;
> > +	struct v4l2_rect *cmps;
> > +
> > +	if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
> > +		return -EINVAL;
> > +
> > +	switch (sel->target) {
> > +	case V4L2_SEL_TGT_CROP_BOUNDS:
> > +		fmt = v4l2_subdev_state_get_format(state, sel->pad);
> > +		sel->r.width = fmt->width;
> > +		sel->r.height = fmt->height;
> > +		sel->r.left = 0;
> > +		sel->r.top = 0;
> > +		break;
> > +	case V4L2_SEL_TGT_COMPOSE_BOUNDS:
> > +		crop = v4l2_subdev_state_get_crop(state, sel->pad);
> > +		sel->r.width = crop->width;
> > +		sel->r.height = crop->height;
> > +		sel->r.left = 0;
> > +		sel->r.top = 0;
> > +		break;
> > +	case V4L2_SEL_TGT_CROP:
> > +		crop = v4l2_subdev_state_get_crop(state, sel->pad);
> > +		sel->r = *crop;
> > +		break;
> > +	case V4L2_SEL_TGT_COMPOSE:
> > +		cmps = v4l2_subdev_state_get_compose(state, sel->pad);
> > +		sel->r = *cmps;
> > +		break;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd,
> > +				    struct v4l2_subdev_state *state,
> > +				    struct v4l2_subdev_selection *sel)
> > +{
> > +	struct v4l2_mbus_framefmt *fmt;
> > +	struct v4l2_rect *crop;
> > +	struct v4l2_rect *cmps;
> > +
> > +	if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
> > +		return -EINVAL;
> > +
> > +	switch (sel->target) {
> > +	case V4L2_SEL_TGT_CROP:
> > +		fmt = v4l2_subdev_state_get_format(state, sel->pad);
> > +		crop = v4l2_subdev_state_get_crop(state, sel->pad);
> > +
> > +		sel->r.left = clamp_t(s32, sel->r.left, 0, fmt->width - 1);
> > +		sel->r.top = clamp_t(s32, sel->r.top, 0, fmt->height - 1);
> > +		sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH,
> > +				     fmt->width - sel->r.left);
> > +		sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT,
> > +				      fmt->height - sel->r.top);
> > +
> > +		crop->width = ALIGN(sel->r.width, 2);
> > +		crop->height = ALIGN(sel->r.height, 2);
> > +		crop->left = sel->r.left;
> > +		crop->top = sel->r.top;
> > +
> > +		sel->r = *crop;
> > +		break;
> > +	case V4L2_SEL_TGT_COMPOSE:
> > +		crop = v4l2_subdev_state_get_crop(state, sel->pad);
> > +		cmps = v4l2_subdev_state_get_compose(state, sel->pad);
> > +
> > +		sel->r.left = 0;
> > +		sel->r.top = 0;
> > +		sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH, crop->width);
> > +		sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT, crop->height);
> > +
> > +		cmps->width = ALIGN(sel->r.width, 2);
> > +		cmps->height = ALIGN(sel->r.height, 2);
> > +		cmps->left = sel->r.left;
> > +		cmps->top = sel->r.top;
> > +
> > +		sel->r = *cmps;
> > +
> > +		fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
> > +		fmt->width = cmps->width;
> > +		fmt->height = cmps->height;
> > +		break;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_rsz_init_state(struct v4l2_subdev *sd,
> > +				 struct v4l2_subdev_state *state)
> > +{
> > +	struct v4l2_mbus_framefmt *sink_fmt;
> > +	struct v4l2_mbus_framefmt *src_fmt;
> > +	struct v4l2_rect *crop;
> > +	struct v4l2_rect *cmps;
> > +
> > +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> > +	sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
> > +	sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> > +	sink_fmt->field = V4L2_FIELD_NONE;
> > +	sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> > +	sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> > +	sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
> > +	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> > +	sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> > +
> > +	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> > +	crop->width = C3_ISP_DEFAULT_WIDTH;
> > +	crop->height = C3_ISP_DEFAULT_HEIGHT;
> > +	crop->left = 0;
> > +	crop->top = 0;
> > +
> > +	cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> > +	cmps->width = C3_ISP_DEFAULT_WIDTH;
> > +	cmps->height = C3_ISP_DEFAULT_HEIGHT;
> > +	cmps->left = 0;
> > +	cmps->top = 0;
> > +
> > +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
> > +	*src_fmt = *sink_fmt;
> > +
> > +	return c3_isp_rsz_init_routing(sd, state);
> > +}
> > +
> > +static const struct v4l2_subdev_pad_ops c3_isp_rsz_pad_ops = {
> > +	.enum_mbus_code = c3_isp_rsz_enum_mbus_code,
> > +	.get_fmt = v4l2_subdev_get_fmt,
> > +	.set_fmt = c3_isp_rsz_set_fmt,
> > +	.get_selection = c3_isp_rsz_get_selection,
> > +	.set_selection = c3_isp_rsz_set_selection,
> > +	.set_routing = c3_isp_rsz_set_routing,
> > +};
> > +
> > +static const struct v4l2_subdev_ops c3_isp_rsz_subdev_ops = {
> > +	.pad = &c3_isp_rsz_pad_ops,
> > +};
> > +
> > +static const struct v4l2_subdev_internal_ops c3_isp_rsz_internal_ops = {
> > +	.init_state = c3_isp_rsz_init_state,
> > +};
> > +
> > +/* Media entity operations */
> > +static const struct media_entity_operations c3_isp_rsz_entity_ops = {
> > +	.link_validate = v4l2_subdev_link_validate,
> > +};
> > +
> > +static int c3_isp_rsz_register(struct c3_isp_resizer *rsz)
> > +{
> > +	struct v4l2_subdev *sd = &rsz->sd;
> > +	int ret;
> > +
> > +	v4l2_subdev_init(sd, &c3_isp_rsz_subdev_ops);
> > +	sd->owner = THIS_MODULE;
> > +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > +	sd->internal_ops = &c3_isp_rsz_internal_ops;
> > +	snprintf(sd->name, sizeof(sd->name), "isp-resizer%u", rsz->id);
>
> maybe "c3-isp_resizer%u"
>
> > +
> > +	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
> > +	sd->entity.ops = &c3_isp_rsz_entity_ops;
> > +
> > +	sd->dev = rsz->isp->dev;
> > +	v4l2_set_subdevdata(sd, rsz);
> > +
> > +	rsz->pads[C3_ISP_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> > +	rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> > +	ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_PAD_MAX, rsz->pads);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = v4l2_subdev_init_finalize(sd);
> > +	if (ret)
> > +		goto err_entity_cleanup;
> > +
> > +	ret = v4l2_device_register_subdev(&rsz->isp->v4l2_dev, sd);
> > +	if (ret)
> > +		goto err_subdev_cleanup;
> > +
> > +	return 0;
> > +
> > +err_subdev_cleanup:
> > +	v4l2_subdev_cleanup(sd);
> > +err_entity_cleanup:
> > +	media_entity_cleanup(&sd->entity);
> > +	return ret;
> > +}
> > +
> > +static void c3_isp_rsz_unregister(struct c3_isp_resizer *rsz)
> > +{
> > +	struct v4l2_subdev *sd = &rsz->sd;
> > +
> > +	v4l2_device_unregister_subdev(sd);
> > +	v4l2_subdev_cleanup(sd);
> > +	media_entity_cleanup(&sd->entity);
> > +}
> > +
> > +int c3_isp_resizers_register(struct c3_isp_device *isp)
> > +{
> > +	u32 i;
> > +	int ret;
> > +
> > +	for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
> > +		struct c3_isp_resizer *rsz = &isp->resizers[i];
> > +
> > +		rsz->id = i;
> > +		rsz->isp = isp;
> > +
> > +		if (rsz->id == C3_ISP_RSZ_0)
> > +			rsz->cap = &isp->caps[C3_ISP_CAP_DEV_0];
> > +		else if (rsz->id == C3_ISP_RSZ_1)
> > +			rsz->cap = &isp->caps[C3_ISP_CAP_DEV_1];
> > +		else
> > +			rsz->cap = &isp->caps[C3_ISP_CAP_DEV_2];
> > +
> > +		ret = c3_isp_rsz_register(rsz);
> > +		if (ret) {
> > +			rsz->isp = NULL;
> > +			c3_isp_resizers_unregister(isp);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +void c3_isp_resizers_unregister(struct c3_isp_device *isp)
> > +{
> > +	u32 i;
> > +
> > +	for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
> > +		struct c3_isp_resizer *rsz = &isp->resizers[i];
> > +
> > +		if (rsz->isp)
> > +			c3_isp_rsz_unregister(rsz);
> > +	};
> > +}
> > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
> > new file mode 100644
> > index 000000000000..72024442d48f
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
> > @@ -0,0 +1,488 @@
> > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > +/*
> > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > + */
> > +
> > +#include <linux/cleanup.h>
> > +#include <linux/pm_runtime.h>
> > +
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-event.h>
> > +#include <media/v4l2-ioctl.h>
> > +#include <media/v4l2-mc.h>
> > +#include <media/videobuf2-dma-contig.h>
> > +
> > +#include "c3-isp-common.h"
> > +#include "c3-isp-regs.h"
> > +#include "include/uapi/c3-isp-config.h"
> > +
> > +/* Hardware configuration */
> > +
> > +static void c3_isp_stats_af_init(struct c3_isp_stats *stats)
> > +{
> > +	c3_isp_write(stats->isp, ISP_AF_ROI0_WIN01,
> > +		     AF_ROI_XYWIN_00_INIT | AF_ROI_XYWIN_01_INIT);
> > +
> > +	c3_isp_write(stats->isp, ISP_AF_ROI0_WIN23,
> > +		     AF_ROI_XYWIN_02_INIT | AF_ROI_XYWIN_03_INIT);
> > +
> > +	c3_isp_write(stats->isp, ISP_AF_ROI1_WIN01,
> > +		     AF_ROI_XYWIN_10_INIT | AF_ROI_XYWIN_11_INIT);
> > +
> > +	c3_isp_write(stats->isp, ISP_AF_ROI1_WIN23,
> > +		     AF_ROI_XYWIN_12_INIT | AF_ROI_XYWIN_13_INIT);
> > +
> > +	/* 0: old statistics output, 1: new statistics output. */
> > +	c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL,
> > +			   AF_STAT_SELECT, 0x1 << AF_STAT_SELECT_SHIFT);
> > +}
> > +
> > +static void c3_isp_stats_ae_init(struct c3_isp_stats *stats)
> > +{
> > +	c3_isp_write(stats->isp, ISP_AE_ROI0_WIN01,
> > +		     AE_ROI_XYWIN_00_INIT | AE_ROI_XYWIN_01_INIT);
> > +
> > +	c3_isp_write(stats->isp, ISP_AE_ROI0_WIN23,
> > +		     AE_ROI_XYWIN_02_INIT | AE_ROI_XYWIN_03_INIT);
> > +
> > +	c3_isp_write(stats->isp, ISP_AE_ROI1_WIN01,
> > +		     AE_ROI_XYWIN_10_INIT | AE_ROI_XYWIN_11_INIT);
> > +
> > +	c3_isp_write(stats->isp, ISP_AE_ROI1_WIN23,
> > +		     AE_ROI_XYWIN_12_INIT | AE_ROI_XYWIN_13_INIT);
> > +
> > +	c3_isp_write(stats->isp, ISP_AE_STAT_THD01,
> > +		     AE_STAT_THRD0_INIT | AE_STAT_THRD1_INIT);
> > +
> > +	c3_isp_write(stats->isp, ISP_AE_STAT_THD23,
> > +		     AE_STAT_THRD2_INIT | AE_STAT_THRD3_INIT);
> > +
> > +	/* Set 0 when ae_stat_switch is not 0 */
> > +	c3_isp_update_bits(stats->isp, ISP_AE_CTRL,
> > +			   AE_CTRL_INPUT_2LINE_TOGETHER, 0);
> > +
> > +	/* Configure ae luma mode */
> > +	c3_isp_update_bits(stats->isp, ISP_AE_CTRL, AE_CTRL_LUMA_MODE_MASK,
> > +			   AE_CTRL_LUMA_MODE_FILTER << AE_CTRL_LUMA_MODE_SHIFT);
> > +}
> > +
> > +static void c3_isp_stats_awb_init(struct c3_isp_stats *stats)
> > +{
> > +	/* Initialize the awb statistics rectangle of image */
> > +	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN01,
> > +		     AWB_ROI_XYWIN_00_INIT | AWB_ROI_XYWIN_01_INIT);
> > +	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN23,
> > +		     AWB_ROI_XYWIN_02_INIT | AWB_ROI_XYWIN_03_INIT);
> > +
> > +	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN01,
> > +		     AWB_ROI_XYWIN_10_INIT | AWB_ROI_XYWIN_11_INIT);
> > +	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN23,
> > +		     AWB_ROI_XYWIN_12_INIT | AWB_ROI_XYWIN_13_INIT);
> > +}
> > +
> > +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
> > +{
> > +	struct c3_isp_device *isp = stats->isp;
> > +	struct c3_isp_stats_info *stats_info = stats->buff->vaddr;
> > +	u32 awb_dma_size = sizeof(stats_info->awb_stats);
> > +	u32 ae_dma_size = sizeof(stats_info->ae_stats);
> > +	u32 awb_dma_addr = stats->buff->paddr;
> > +	u32 af_dma_addr;
> > +	u32 ae_dma_addr;
> > +
> > +	ae_dma_addr = awb_dma_addr + awb_dma_size;
> > +	af_dma_addr = ae_dma_addr + ae_dma_size;
> > +
> > +	c3_isp_update_bits(isp, VIU_DMAWR_BADDR0, VIU_DMAWR_AF_BADDR_MASK,
> > +			   VIU_DMAWR_AF_BADDR(af_dma_addr));
> > +	c3_isp_update_bits(isp, VIU_DMAWR_BADDR1, VIU_DMAWR_AWB_BADDR_MASK,
> > +			   VIU_DMAWR_AWB_BADDR(awb_dma_addr));
> > +	c3_isp_update_bits(isp, VIU_DMAWR_BADDR2, VIU_DMAWR_AE_BADDR_MASK,
> > +			   VIU_DMAWR_AE_BADDR(ae_dma_addr));
> > +}
> > +
> > +static void c3_isp_stats_enable(struct c3_isp_stats *stats)
> > +{
> > +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> > +			   TOP_3A_AE_STAT_EN, TOP_3A_AE_STAT_EN);
> > +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> > +			   TOP_3A_AWB_STAT_EN, TOP_3A_AWB_STAT_EN);
> > +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> > +			   TOP_3A_AF_STAT_EN, TOP_3A_AF_STAT_EN);
> > +}
> > +
> > +static void c3_isp_stats_disable(struct c3_isp_stats *stats)
> > +{
> > +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> > +			   TOP_3A_AE_STAT_EN, 0);
> > +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> > +			   TOP_3A_AWB_STAT_EN, 0);
> > +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> > +			   TOP_3A_AF_STAT_EN, 0);
> > +}
> > +
> > +/* The unit of dma_size is 16 bytes */
> > +static void c3_isp_stats_cfg_dmawr_size(struct c3_isp_stats *stats)
> > +{
> > +	u32 dma_size;
> > +
> > +	dma_size = sizeof(struct af_stats_info) / 16;
> > +	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0,
> > +			   VIU_DMAWR_SIZE_AF_MASK, dma_size);
> > +
> > +	dma_size = sizeof(struct awb_stats_info) / 16;
> > +	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE_AWB_MASK,
> > +			   dma_size << VIU_DMAWR_SIZE_AWB_SHIFT);
> > +
> > +	dma_size = sizeof(struct ae_stats_info) / 16;
> > +	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1,
> > +			   VIU_DMAWR_SIZE_AE_MASK, dma_size);
> > +}
> > +
> > +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
> > +{
> > +	stats->buff = list_first_entry_or_null(&stats->pending,
> > +					       struct c3_isp_vb2_buffer, list);
> > +	if (stats->buff) {
> > +		c3_isp_stats_cfg_dmawr_addr(stats);
> > +		list_del(&stats->buff->list);
> > +	}
> > +}
> > +
> > +static void c3_isp_stats_start(struct c3_isp_stats *stats)
> > +{
> > +	c3_isp_stats_af_init(stats);
> > +	c3_isp_stats_ae_init(stats);
> > +	c3_isp_stats_awb_init(stats);
> > +
> > +	c3_isp_stats_cfg_dmawr_size(stats);
> > +	c3_isp_stats_cfg_buff(stats);
> > +	c3_isp_stats_enable(stats);
> > +
> > +	stats->is_streaming = true;
> > +}
> > +
> > +static void c3_isp_stats_stop(struct c3_isp_stats *stats)
> > +{
> > +	stats->is_streaming = false;
> > +
> > +	c3_isp_stats_disable(stats);
> > +}
> > +
> > +static void c3_isp_stats_return_buffers(struct c3_isp_stats *stats,
> > +					enum vb2_buffer_state state)
> > +{
> > +	unsigned long flags;
> > +	struct c3_isp_vb2_buffer *buff;
> > +
> > +	spin_lock_irqsave(&stats->buff_lock, flags);
> > +
> > +	if (stats->buff) {
> > +		vb2_buffer_done(&stats->buff->vb.vb2_buf, state);
> > +		stats->buff = NULL;
> > +	}
> > +
> > +	while (!list_empty(&stats->pending)) {
> > +		buff = list_first_entry(&stats->pending,
> > +					struct c3_isp_vb2_buffer, list);
> > +		list_del(&buff->list);
> > +		vb2_buffer_done(&buff->vb.vb2_buf, state);
> > +	}
> > +
> > +	spin_unlock_irqrestore(&stats->buff_lock, flags);
> > +}
> > +
> > +static int c3_isp_stats_querycap(struct file *file, void *fh,
> > +				 struct v4l2_capability *cap)
> > +{
> > +	strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
> > +	strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_stats_enum_fmt(struct file *file, void *fh,
> > +				 struct v4l2_fmtdesc *f)
> > +{
> > +	struct c3_isp_stats *stats = video_drvdata(file);
> > +
> > +	if (f->index > 0 || f->type != stats->vb2_q.type)
> > +		return -EINVAL;
> > +
> > +	f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_stats_g_fmt(struct file *file, void *fh,
> > +			      struct v4l2_format *f)
> > +{
> > +	struct c3_isp_stats *stats = video_drvdata(file);
> > +
> > +	f->fmt.meta = stats->vfmt.fmt.meta;
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
> > +	.vidioc_querycap                = c3_isp_stats_querycap,
> > +	.vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
> > +	.vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
> > +	.vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
> > +	.vidioc_try_fmt_meta_cap        = c3_isp_stats_g_fmt,
> > +	.vidioc_reqbufs	                = vb2_ioctl_reqbufs,
> > +	.vidioc_querybuf                = vb2_ioctl_querybuf,
> > +	.vidioc_qbuf                    = vb2_ioctl_qbuf,
> > +	.vidioc_expbuf                  = vb2_ioctl_expbuf,
> > +	.vidioc_dqbuf                   = vb2_ioctl_dqbuf,
> > +	.vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
> > +	.vidioc_create_bufs             = vb2_ioctl_create_bufs,
> > +	.vidioc_streamon                = vb2_ioctl_streamon,
> > +	.vidioc_streamoff               = vb2_ioctl_streamoff,
> > +	.vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
> > +	.vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
> > +};
> > +
> > +static const struct v4l2_file_operations isp_stats_v4l2_fops = {
> > +	.open = v4l2_fh_open,
> > +	.release = vb2_fop_release,
> > +	.poll = vb2_fop_poll,
> > +	.unlocked_ioctl = video_ioctl2,
> > +	.mmap = vb2_fop_mmap,
> > +};
> > +
> > +static int c3_isp_stats_vb2_queue_setup(struct vb2_queue *q,
> > +					unsigned int *num_buffers,
> > +					unsigned int *num_planes,
> > +					unsigned int sizes[],
> > +					struct device *alloc_devs[])
> > +{
> > +	if (*num_planes) {
> > +		if (*num_planes != 1)
> > +			return -EINVAL;
> > +
> > +		if (sizes[0] < sizeof(struct c3_isp_stats_info))
> > +			return -EINVAL;
> > +	} else {
> > +		*num_planes = 1;
> > +		sizes[0] = sizeof(struct c3_isp_stats_info);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
> > +{
> > +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > +	struct c3_isp_vb2_buffer *buf =
> > +			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> > +	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&stats->buff_lock, flags);
> > +
> > +	list_add_tail(&buf->list, &stats->pending);
> > +
> > +	spin_unlock_irqrestore(&stats->buff_lock, flags);
> > +}
> > +
> > +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
> > +{
> > +	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> > +	unsigned int size = stats->vfmt.fmt.meta.buffersize;
> > +
> > +	if (vb2_plane_size(vb, 0) < size) {
> > +		dev_err(stats->isp->dev,
> > +			"User buffer too small (%ld < %u)\n",
> > +			vb2_plane_size(vb, 0), size);
> > +		return -EINVAL;
> > +	}
> > +
> > +	vb2_set_plane_payload(vb, 0, size);
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
> > +{
> > +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > +	struct c3_isp_vb2_buffer *buf =
> > +			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> > +	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> > +
> > +	buf->vaddr = vb2_plane_vaddr(vb, 0);
> > +	buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
> > +
> > +	memset(buf->vaddr, 0, stats->vfmt.fmt.meta.buffersize);
> > +
> > +	return 0;
> > +}
> > +
> > +static int c3_isp_stats_vb2_start_streaming(struct vb2_queue *q,
> > +					    unsigned int count)
> > +{
> > +	struct c3_isp_stats *stats = vb2_get_drv_priv(q);
> > +	int ret;
> > +
> > +	guard(mutex)(&stats->isp->lock);
> > +
> > +	ret = pm_runtime_resume_and_get(stats->isp->dev);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = video_device_pipeline_start(&stats->vdev, &stats->isp->pipe);
> > +	if (ret) {
> > +		dev_err(stats->isp->dev,
> > +			"Failed to start stats pipeline: %d\n", ret);
> > +		goto err_pm_put;
> > +	}
> > +
> > +	if (c3_isp_pipeline_ready(stats->isp)) {
> > +		ret = v4l2_subdev_enable_streams(&stats->isp->core.sd,
> > +						 C3_ISP_CORE_PAD_SOURCE_STATS,
> > +						 BIT(0));
> > +		if (ret)
> > +			goto err_pipeline_stop;
> > +	}
> > +
> > +	c3_isp_stats_start(stats);
> > +
> > +	return 0;
> > +
> > +err_pipeline_stop:
> > +	video_device_pipeline_stop(&stats->vdev);
> > +err_pm_put:
> > +	pm_runtime_put(stats->isp->dev);
> > +	c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_QUEUED);
> > +	return ret;
> > +}
> > +
> > +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
> > +{
> > +	struct c3_isp_stats *stats = vb2_get_drv_priv(q);
> > +
> > +	guard(mutex)(&stats->isp->lock);
> > +
> > +	c3_isp_stats_stop(stats);
> > +	c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_ERROR);
> > +
> > +	if (stats->isp->pipe.start_count == 1)
> > +		v4l2_subdev_disable_streams(&stats->isp->core.sd,
> > +					    C3_ISP_CORE_PAD_SOURCE_STATS,
> > +					    BIT(0));
> > +
> > +	video_device_pipeline_stop(&stats->vdev);
> > +	pm_runtime_put(stats->isp->dev);
> > +}
> > +
> > +static const struct vb2_ops isp_stats_vb2_ops = {
> > +	.queue_setup = c3_isp_stats_vb2_queue_setup,
> > +	.buf_queue = c3_isp_stats_vb2_buf_queue,
> > +	.buf_prepare = c3_isp_stats_vb2_buf_prepare,
> > +	.buf_init = c3_isp_stats_vb2_buf_init,
> > +	.wait_prepare = vb2_ops_wait_prepare,
> > +	.wait_finish = vb2_ops_wait_finish,
> > +	.start_streaming = c3_isp_stats_vb2_start_streaming,
> > +	.stop_streaming = c3_isp_stats_vb2_stop_streaming,
> > +};
> > +
> > +int c3_isp_stats_register(struct c3_isp_device *isp)
> > +{
> > +	struct c3_isp_stats *stats = &isp->stats;
> > +	struct video_device *vdev = &stats->vdev;
> > +	struct vb2_queue *vb2_q = &stats->vb2_q;
> > +	int ret;
> > +
> > +	memset(stats, 0, sizeof(*stats));
> > +	stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
> > +	stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_info);
> > +	stats->isp = isp;
> > +	INIT_LIST_HEAD(&stats->pending);
> > +	spin_lock_init(&stats->buff_lock);
> > +
> > +	mutex_init(&stats->lock);
> > +
> > +	snprintf(vdev->name, sizeof(vdev->name), "isp-stats");
> > +	vdev->fops = &isp_stats_v4l2_fops;
> > +	vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
> > +	vdev->v4l2_dev = &isp->v4l2_dev;
> > +	vdev->lock = &stats->lock;
> > +	vdev->minor = -1;
> > +	vdev->queue = vb2_q;
> > +	vdev->release = video_device_release_empty;
> > +	vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
> > +	vdev->vfl_dir = VFL_DIR_RX;
> > +	video_set_drvdata(vdev, stats);
> > +
> > +	vb2_q->drv_priv = stats;
> > +	vb2_q->mem_ops = &vb2_dma_contig_memops;
> > +	vb2_q->ops = &isp_stats_vb2_ops;
> > +	vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
> > +	vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
> > +	vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> > +	vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
> > +	vb2_q->dev = isp->dev;
> > +	vb2_q->lock = &stats->lock;
> > +	vb2_q->min_queued_buffers = 2;
> > +
> > +	ret = vb2_queue_init(vb2_q);
> > +	if (ret)
> > +		goto err_destroy;
> > +
> > +	stats->pad.flags = MEDIA_PAD_FL_SINK;
> > +	ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
> > +	if (ret)
> > +		goto err_queue_release;
> > +
> > +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> > +	if (ret) {
> > +		dev_err(isp->dev,
> > +			"Failed to register %s: %d\n", vdev->name, ret);
> > +		goto err_entity_cleanup;
> > +	}
> > +
> > +	return 0;
> > +
> > +err_entity_cleanup:
> > +	media_entity_cleanup(&vdev->entity);
> > +err_queue_release:
> > +	vb2_queue_release(vb2_q);
> > +err_destroy:
> > +	mutex_destroy(&stats->lock);
> > +	return ret;
> > +}
> > +
> > +void c3_isp_stats_unregister(struct c3_isp_device *isp)
> > +{
> > +	struct c3_isp_stats *stats = &isp->stats;
> > +
> > +	vb2_queue_release(&stats->vb2_q);
> > +	media_entity_cleanup(&stats->vdev.entity);
> > +	video_unregister_device(&stats->vdev);
> > +	mutex_destroy(&stats->lock);
> > +}
> > +
> > +int c3_isp_stats_done(struct c3_isp_device *isp)
> > +{
> > +	struct c3_isp_stats *stats = &isp->stats;
> > +	struct c3_isp_vb2_buffer *buff = stats->buff;
> > +	unsigned long flags;
> > +
> > +	if (!stats->is_streaming)
> > +		return -EINVAL;
> > +
> > +	spin_lock_irqsave(&stats->buff_lock, flags);
> > +
> > +	if (buff) {
> > +		buff->vb.sequence = stats->isp->frm_sequence;
> > +		buff->vb.vb2_buf.timestamp = ktime_get();
> > +		buff->vb.field = V4L2_FIELD_NONE;
> > +		vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
> > +	}
> > +
> > +	c3_isp_stats_cfg_buff(stats);
> > +
> > +	spin_unlock_irqrestore(&stats->buff_lock, flags);
> > +
> > +	return 0;
> > +}
> > diff --git a/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
> > new file mode 100644
> > index 000000000000..84ff5741357a
> > --- /dev/null
> > +++ b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
> > @@ -0,0 +1,537 @@
> > +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> > +/*
> > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > + */
> > +
> > +#ifndef __C3_ISP_CONFIG_H__
> > +#define __C3_ISP_CONFIG_H__
>
> When moving to include/uapi/linux/media/amlogic prefix this with
> _UAPI_
>
> > +
> > +#define AF_STAT_BLKH_NUM             17
> > +#define AF_STAT_BLKV_NUM             15
> > +#define AF_STAT_BLK_NUM              (AF_STAT_BLKH_NUM * AF_STAT_BLKV_NUM)
> > +/* AF stats block size need to be aligned with 2 */
> > +#define AF_STAT_BLK_SIZE             ALIGN(AF_STAT_BLK_NUM, 2)
> > +#define AE_HISTOGRAM_SIZE	     1024
> > +#define AE_STAT_BLKH_NUM             17
> > +#define AE_STAT_BLKV_NUM             15
> > +#define AE_STAT_BLK_NUM              (AE_STAT_BLKH_NUM * AE_STAT_BLKV_NUM)
> > +/* AE stats block size need to be aligned with 2 */
> > +#define AE_STAT_BLK_SIZE             ALIGN(AE_STAT_BLK_NUM, 2)
> > +#define AE_BLOCK_WT_NUM              255
> > +#define AE_BLK_WT_DATA_NUM_OF_GROUP  8
> > +#define AWB_STAT_BLKH_NUM            32
> > +#define AWB_STAT_BLKV_NUM            24
> > +#define AWB_STAT_BLK_NUM             (AWB_STAT_BLKH_NUM * AWB_STAT_BLKV_NUM)
> > +/* AWB stats block size need to be aligned with 2 */
> > +#define AWB_STAT_BLK_SIZE            ALIGN(AWB_STAT_BLK_NUM, 2)
> > +#define AWB_BLOCK_WT_NUM             768
> > +#define AWB_BLK_WT_DATA_NUM_OF_GROUP 8
> > +#define AWB_STAT_BLC20_NUM           4
> > +#define AWB_STAT_GAIN10_NUM          4
> > +#define BLC_OFFSET_NUM               5
> > +#define GAMMA_LUT_GROUP_NUM          4
> > +#define GAMMA_LUT_POINT_NUM          129
> > +#define GAMMA_LUT_DATA_NUM_OF_GROUP  2
> > +
> > +/**
> > + * struct awb_zone_stats - AWB statistics of a block
> > + *
> > + * AWB zone stats is aligned with 8 bytes
> > + *
> > + * @rg: the ratio of R / G in a zone
> > + * @bg: the ratio of B / G in a zone
> > + * @pixel_sum: the total number of pixels used in a zone
> > + */
> > +struct awb_zone_stats {
> > +	u16 rg;
> > +	u16 bg;
> > +	u32 pixel_sum;
> > +};
> > +
> > +/**
> > + * struct awb_stats_info - Auto white balance statistics information.
> > + *
> > + * AWB statistical information of all blocks.
> > + *
> > + * @awb_stats: array of auto white balance statistics
> > + */
> > +struct awb_stats_info {
> > +	struct awb_zone_stats awb_stats[AWB_STAT_BLK_SIZE];
> > +};
> > +
> > +/**
> > + * struct ae_zone_stats - AE statistics of a block
> > + *
> > + * AE zone stats is aligned with 8 bytes.
> > + * This is a 5-bin histogram and the total sum is
> > + * normalized to 0xffff.
> > + * So hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4)
> > + *
> > + * @hist0: the global normalized pixel count for bin 0
> > + * @hist1: the global normalized pixel count for bin 1
> > + * @hist3: the global normalized pixel count for bin 3
> > + * @hist4: the global normalized pixel count for bin 4
> > + */
> > +struct ae_zone_stats {
> > +	u16 hist0;
>
> Should you include  <linux/types.h> and use types prefixed with __
> (__u16) ?
>
> > +	u16 hist1;
> > +	u16 hist3;
> > +	u16 hist4;
> > +};
> > +
> > +/**
> > + * struct ae_stats_info - Exposure statistics information
> > + *
> > + * AE statistical information consists of
> > + * all blocks information and a 1024-bin histogram.
> > + *
> > + * @ae_stats: array of auto exposure block statistics
> > + * @hist: a 1024-bin histogram for the entire image
> > + */
> > +struct ae_stats_info {
> > +	struct ae_zone_stats ae_stats[AE_STAT_BLK_SIZE];
> > +	u32 hist[AE_HISTOGRAM_SIZE];
> > +};
> > +
> > +/**
> > + * struct af_zone_stats - AF statistics of a block
> > + *
> > + * AF block stats is aligned with 8 bytes.
> > + * The zonal accumulated contrast metrics are stored
> > + * in floating point format with 16 bits mantissa and
> > + * 5 or 6 bits exponent.
> > + * Apart from contrast metrics we accumulate squared image and
> > + * quartic image data over the zone.
> > + *
> > + * @i2_mat: the mantissa of zonal squared image pixel sum
> > + * @i4_mat: the mantissa of zonal quartic image pixel sum
> > + * @e4_mat: the mantissa of zonal multi-directional quartic edge sum
> > + * @i2_exp: the exponent of zonal squared image pixel sum
> > + * @i4_exp: the exponent of zonal quartic image pixel sum
> > + * @e4_exp: the exponent of zonal multi-directional quartic edge sum
> > + */
> > +struct af_zone_stats {
> > +	u16 i2_mat;
> > +	u16 i4_mat;
> > +	u16 e4_mat;
> > +	u16 i2_exp: 5;
> > +	u16 i4_exp: 6;
> > +	u16 e4_exp: 5;
> > +};
> > +
> > +/**
> > + * struct af_stats_info - Auto Focus statistics information
> > + *
> > + * AF statistical information of each block
> > + *
> > + * @af_stats: array of auto focus block statistics
> > + */
> > +struct af_stats_info {
> > +	struct af_zone_stats af_stats[AF_STAT_BLK_SIZE];
> > +};
> > +
> > +/**
> > + * struct c3_isp_stats_info - V4L2_META_FMT_C3ISP_STATS
> > + *
> > + * Contains ISP statistics
> > + *
> > + * @awb_stats: auto white balance stats
> > + * @ae_stats: auto exposure stats
> > + * @af_stats: auto focus stats
> > + */
> > +struct c3_isp_stats_info {
> > +	struct awb_stats_info awb_stats;
> > +	struct ae_stats_info ae_stats;
> > +	struct af_stats_info af_stats;
> > +};
> > +
> > +/**
> > + * enum c3_isp_param_buffer_version -  C3 ISP parameters block versioning
> > + *
> > + * @C3_ISP_PARAM_BUFFER_V0: First version of C3 ISP parameters block
> > + */
> > +enum c3_isp_param_buffer_version {
> > +	C3_ISP_PARAM_BUFFER_V0,
> > +};
> > +
> > +/**
> > + * enum c3_isp_param_block_type - Enumeration of C3 ISP parameter blocks
> > + *
> > + * Each block configures a specific processing block of the C3 ISP.
> > + * The block type allows the driver to correctly interpret
> > + * the parameters block data.
> > + *
> > + * @C3_ISP_PARAM_BLOCK_WB_CHANGE: White balance change parameters
> > + * @C3_ISP_PARAM_BLOCK_WB_LUMA: White balance Luma-based parameters
> > + * @C3_ISP_PARAM_BLOCK_WB_TRIANGLE: White balance triangle parameters
> > + * @C3_ISP_PARAM_BLOCK_AWB_STATS: AWB statistics parameters
> > + * @C3_ISP_PARAM_BLOCK_AE_STATS: AE statistics parameters
> > + * @C3_ISP_PARAM_BLOCK_AF_STATS: AF statistics parameters
> > + * @C3_ISP_PARAM_BLOCK_PST_GAMMA: post gamma parameters
> > + * @C3_ISP_PARAM_BLOCK_DMSC: demosaic parameters
> > + * @C3_ISP_PARAM_BLOCK_CCM: Color correction matrix parameters
> > + * @C3_ISP_PARAM_BLOCK_CSC: Color space conversion parameters
> > + * @C3_ISP_PARAM_BLOCK_BLC: Black level correction parameters
> > + * @C3_ISP_PARAM_BLOCK_SENTINEL: First non-valid block index
> > + */
> > +enum c3_isp_param_block_type {
> > +	C3_ISP_PARAM_BLOCK_WB_CHANGE,
> > +	C3_ISP_PARAM_BLOCK_WB_LUMA,
> > +	C3_ISP_PARAM_BLOCK_WB_TRIANGLE,
> > +	C3_ISP_PARAM_BLOCK_AWB_STATS,
> > +	C3_ISP_PARAM_BLOCK_AE_STATS,
> > +	C3_ISP_PARAM_BLOCK_AF_STATS,
> > +	C3_ISP_PARAM_BLOCK_PST_GAMMA,
> > +	C3_ISP_PARAM_BLOCK_DMSC,
> > +	C3_ISP_PARAM_BLOCK_CCM,
> > +	C3_ISP_PARAM_BLOCK_CSC,
> > +	C3_ISP_PARAM_BLOCK_BLC,
> > +	C3_ISP_PARAM_BLOCK_SENTINEL
> > +};
> > +
> > +/**
> > + * struct c3_isp_param_block_header - C3 ISP parameter block header
> > + *
> > + * This structure represents the common part of all the ISP configuration
> > + * blocks. Each parameters block shall embed an instance of this structure type
> > + * as its first member, followed by the block-specific configuration data. The
> > + * driver inspects this common header to discern the block type and its size and
> > + * properly handle the block content by casting it to the correct block-specific
> > + * type.
> > + *
> > + * @type: The parameters block type (enum c3_isp_param_block_type)
> > + * @enabled: Block enabled/disabled flag
> > + * @size: Size (in bytes) of parameters block
> > + */
> > +
> > +struct c3_isp_param_block_header {
> > +	enum c3_isp_param_block_type type;
> > +	bool enabled;
> > +	size_t size;
> > +};
>
> What is the size of this structure ? Is it aligned or does the
> compiler inserts padding bytes ? In general, I would try to align
> everything to 8 bytes to avoid the compiler inserting padding bytes.
>
> A tool that can help you identifies holes is pahole. Just write a
> small userspace program that includes types from this header and
> declare a variable of each type of defined in this header. Pass the
> executable to pahole and it will show the memory layout of each
> member.
>
> > +
> > +/**
> > + * struct wb_change_cfg - White Balance configuration
> > + *
> > + * @header: The C3 ISP parameters block header
> > + * @wb_gain: white balance gain of each color
> > + *	wb_gain[0]: Gr gain, range 0~0xfff
> > + *	wb_gain[1]: R gain, range 0~0xfff
> > + *	wb_gain[2]: B gain, range 0~0xfff
> > + *	wb_gain[3]: Gb gain, range 0~0xfff
> > + *	wb_gain[4]: Ir gain, range 0~0xfff
> > + * @wb_limit: white balance limit of each color
> > + *	wb_limit[0]: Gr limit, 16 bits float
> > + *	wb_limit[1]: R limit, 16 bits float
> > + *	wb_limit[2]: B limit, 16 bits float
> > + *	wb_limit[3]: Gb limit, 16 bits float
> > + *	wb_limit[4]: Ir limit, 16 bits float
> > + * @ae_gain_grbgi: Gain of each color before blending to luma
> > + *	ae_gain_grbgi[0]: Gr gain, range 0~255
> > + *	ae_gain_grbgi[1]: R gain, range 0~255
> > + *	ae_gain_grbgi[2]: B gain, range 0~255
> > + *	ae_gain_grbgi[3]: Gb gain, range 0~255
> > + *	ae_gain_grbgi[4]: Ir gain, range 0~255
> > + * @ae_bl12_grbgi: Offset of each color before ae_gain_grbgi
> > + *	ae_bl12_grbgi[0]: Gr offset, range 0~4095
> > + *	ae_bl12_grbgi[1]: R offset, range 0~4095
> > + *	ae_bl12_grbgi[2]: B offset, range 0~4095
> > + *	ae_bl12_grbgi[3]: Gb offset, range 0~4095
> > + *	ae_bl12_grbgi[4]: Ir offset, range 0~4095
> > + */
> > +struct wb_change_cfg {
> > +	struct c3_isp_param_block_header header;
> > +	u32 wb_gain[5];
> > +	u32 wb_limit[5];
> > +	u32 ae_gain_grbgi[5];
> > +	u32 ae_bl12_grbgi[5];
> > +};
> > +
> > +/**
> > + * struct wb_luma_cfg - White Balance Luma-based configuration
> > + *
> > + * @header: The C3 ISP parameters block header
> > + * @awb_stat_blc20: BLC in AWB statistic
> > + *	awb_stat_blc20[0]: Gr blc, range 0~0xfffff
> > + *	awb_stat_blc20[1]: R blc, range 0~0xfffff
> > + *	awb_stat_blc20[2]: B blc, range 0~0xfffff
> > + *	awb_stat_blc20[3]: Gb blc, range 0~0xfffff
> > + * @awb_stat_gain10: Gain in AWB statistic
> > + *	awb_stat_gain10[0]: Gr gain, range 0~1023
> > + *	awb_stat_gain10[1]: R gain, range 0~1023
> > + *	awb_stat_gain10[2]: B gain, range 0~1023
> > + *	awb_stat_gain10[3]: Gb gain, range 0~1023
> > + * @awb_stat_satur_low: AWB statistic under-saturation threshold
> > + *	value: range 0~65535
> > + * @awb_stat_satur_high: AWB statistic over-saturation threshold
> > + *	value: range 0~65535
> > + */
> > +struct wb_luma_cfg {
> > +	struct c3_isp_param_block_header header;
> > +	u32 awb_stat_blc20[AWB_STAT_BLC20_NUM];
> > +	u32 awb_stat_gain10[AWB_STAT_GAIN10_NUM];
> > +	u32 awb_stat_satur_low;
> > +	u32 awb_stat_satur_high;
> > +};
> > +
> > +/**
> > + * struct wb_triangle_cfg - White Balance Triangle
> > + *
> > + * @header: The C3 ISP parameters block header
> > + * @awb_stat_satur_vald: AWB statistic over saturation control
> > + *	value: 0: disable, 1: enable
> > + * @awb_stat_rg_min: min value of r/g
> > + *	value: 0~4095
> > + * @awb_stat_rg_max: max value of r/g
> > + *	value: 0~4095
> > + * @awb_stat_bg_min: min value of b/g
> > + *	value: 0~4095
> > + * @awb_stat_bg_max: max value of b/g
> > + *	value: 0~4095
> > + * @awb_stat_rg_low: low value of r/g
> > + *	value: 0~4095
> > + * @awb_stat_rg_high: high value of r/g
> > + *	value: 0~4095
> > + * @awb_stat_bg_low: low value of b/g
> > + *	value: 0~4095
> > + * @awb_stat_bg_high: high value of b/g
> > + *	value: 0~4095
> > + */
> > +struct wb_triangle_cfg {
> > +	struct c3_isp_param_block_header header;
> > +	u32 awb_stat_satur_vald;
> > +	u32 awb_stat_rg_min;
> > +	u32 awb_stat_rg_max;
> > +	u32 awb_stat_bg_min;
> > +	u32 awb_stat_bg_max;
> > +	u32 awb_stat_rg_low;
> > +	u32 awb_stat_rg_high;
> > +	u32 awb_stat_bg_low;
> > +	u32 awb_stat_bg_high;
> > +};
> > +
> > +/**
> > + * struct awb_stats_cfg - AWB statistics configuration
> > + *
> > + * This structure contains AWB statistics control information.
> > + *
> > + * @header: The C3 ISP parameters block header
> > + * @awb_stat_switch: the switch of AWB statistics
> > + *	value: 0~7
> > + * @awb_stat_blk_weight: Array of weights for AWB statistics blocks
> > + *	value: 0~15
> > + */
> > +struct awb_stats_cfg {
> > +	struct c3_isp_param_block_header header;
> > +	u8 awb_stat_switch;
> > +	u32 awb_stat_blk_weight[AWB_BLOCK_WT_NUM];
> > +};
> > +
> > +/**
> > + * struct ae_stats_cfg - AE statistics configuration
> > + *
> > + * This structure contains AE statistics control information.
> > + *
> > + * @header: The C3 ISP parameters block header
> > + * @ae_stat_switch: the switch of AE statistics
> > + *	value: 0~3
> > + * @ae_stat_blk_weight: Array of weights for AE statistics blocks
> > + *	value: 0~15
> > + */
> > +struct ae_stats_cfg {
> > +	struct c3_isp_param_block_header header;
> > +	u8 ae_stat_switch;
> > +	u32 ae_stat_blk_weight[AE_BLOCK_WT_NUM];
> > +};
> > +
> > +/**
> > + * struct af_stats_cfg - AF statistics configuration
> > + *
> > + * This structure contains AF statistics control information.
> > + *
> > + * @header: The C3 ISP parameters block header
> > + * @af_stat_switch: the switch of AF statistics
> > + *	value: 0~3
> > + */
> > +struct af_stats_cfg {
> > +	struct c3_isp_param_block_header header;
> > +	u8 af_stat_switch;
> > +};
> > +
> > +/**
> > + * struct pst_gamma_cfg - Post gamma configuration
> > + *
> > + * This structure contains post gamma parameters
> > + *
> > + * @header: The C3 ISP parameters block header
> > + * @pst_gamma_lut: LUT for P-Stitch gamma
> > + *	value: 0~65535
> > + */
> > +struct pst_gamma_cfg {
> > +	struct c3_isp_param_block_header header;
> > +	u32 pst_gamma_lut[GAMMA_LUT_GROUP_NUM][GAMMA_LUT_POINT_NUM];
> > +};
> > +
> > +/**
> > + * struct dmsc_cfg - Demosaic configuration
> > + *
> > + * This structure contains demosaic parameters
> > + *
> > + * @header: The C3 ISP parameters block header
> > + */
> > +struct dmsc_cfg {
> > +	struct c3_isp_param_block_header header;
> > +};
> > +
> > +/**
> > + * struct ccm_cfg - ISP CCM configuration
> > + *
> > + * This structure holds the parameters for configuring the CCM,
> > + * which is used for color correction.
> > + *
> > + * @header: The C3 ISP parameters block header
> > + * @ccm_4x3matrix: A 3x4 matrix used for color correction
> > + *	value: 0~8191
> > + */
> > +struct ccm_cfg {
> > +	struct c3_isp_param_block_header header;
> > +	u32 ccm_4x3matrix[3][4];
> > +};
> > +
> > +/**
> > + * struct csc_cfg - ISP Color Space Conversion configuration
> > + *
> > + * This structure contains settings for color space conversion.
> > + *
> > + * @header: The C3 ISP parameters block header
> > + * @cm0_offset_inp: Input offset values for the 0-order color matrix
> > + *	value: 0~8191
> > + * @cm0_offset_oup: Output offset values for the 0-order color matrix
> > + *	value: 0~8191
> > + * @cm0_3x3mtrx_rs: matrix right shift for cm0
> > + *	value: 0~3
> > + * @cm0_3x3matrix: A 3x3 matrix used for the color matrix
> > + *	value: 0~8191
> > + */
> > +struct csc_cfg {
> > +	struct c3_isp_param_block_header header;
> > +	u32 cm0_offset_inp[3];
> > +	u32 cm0_offset_oup[3];
> > +	u32 cm0_3x3mtrx_rs;
> > +	u32 cm0_3x3matrix[3][3];
> > +};
> > +
> > +/**
> > + * struct blc_cfg - ISP Black Level Correction (BLC) configuration
> > + *
> > + * This structure holds the parameters for BLC in image processing.
> > + *
> > + * @header: The C3 ISP parameters block header
> > + * @fe_bl_ofst: Array of front-end BLC offsets for each color each channels
> > + *	fe_bl_ofst[0]: Gr blc offset, range 0~0x1fffff
> > + *	fe_bl_ofst[1]: R blc offset, range 0~0x1fffff
> > + *	fe_bl_ofst[2]: B blc offset, range 0~0x1fffff
> > + *	fe_bl_ofst[3]: Gb blc offset, range 0~0x1fffff
> > + *	fe_bl_ofst[4]: Ir blc offset, range 0~0x1fffff
> > + * @blc_ofst: Array of LSWB BLC offsets
> > + *	blc_ofst[0]: Gr blc offset, 16 bits float
> > + *	blc_ofst[1]: R blc offset, 16 bits float
> > + *	blc_ofst[2]: B blc offset, 16 bits float
> > + *	blc_ofst[3]: Gb blc offset, 16 bits float
> > + *	blc_ofst[4]: Ir blc offset, 16 bits float
> > + */
> > +struct blc_cfg {
> > +	struct c3_isp_param_block_header header;
> > +	u32 fe_bl_ofst[BLC_OFFSET_NUM];
> > +	u32 blc_ofst[BLC_OFFSET_NUM];
> > +};
> > +
> > +/**
> > + * define C3_ISP_PARAMS_MAX_SIZE - Maximum size of all C3 ISP Parameters
> > + *
> > + * Though the parameters for the C3 ISP are passed as optional blocks, the
> > + * driver still needs to know the absolute maximum size so that it can allocate
> > + * a buffer sized appropriately to accommodate userspace attempting to set all
> > + * possible parameters in a single frame.
> > + */
> > +#define C3_ISP_PARAMS_MAX_SIZE                 \
> > +	(sizeof(struct wb_change_cfg) +        \
> > +	sizeof(struct wb_luma_cfg)   +         \
> > +	sizeof(struct wb_triangle_cfg) +       \
> > +	sizeof(struct awb_stats_cfg) +          \
> > +	sizeof(struct ae_stats_cfg) +           \
> > +	sizeof(struct af_stats_cfg) +           \
> > +	sizeof(struct pst_gamma_cfg) +         \
> > +	sizeof(struct dmsc_cfg) +              \
> > +	sizeof(struct ccm_cfg) +               \
> > +	sizeof(struct csc_cfg) +               \
> > +	sizeof(struct blc_cfg))
> > +
> > +/**
> > + * struct c3_isp_params_buffer - C3 ISP configuration parameters
> > + *
> > + * This struct contains the configuration parameters of the C3 ISP
> > + * algorithms, serialized by userspace into an opaque data buffer. Each
> > + * configuration parameter block is represented by a block-specific structure
> > + * which contains a :c:type:`c3_isp_param_block_header` entry as first
> > + * member. Userspace populates the @data buffer with configuration parameters
> > + * for the blocks that it intends to configure. As a consequence, the data
> > + * buffer effective size changes according to the number of ISP blocks that
> > + * userspace intends to configure.
> > + *
> > + * The parameters buffer is versioned by the @version field to allow modifying
> > + * and extending its definition. Userspace should populate the @version field to
> > + * inform the driver about the version it intends to use. The driver will parse
> > + * and handle the @data buffer according to the data layout specific to the
> > + * indicated revision and return an error if the desired revision is not
> > + * supported.
> > + *
> > + * For each ISP block that userspace wants to configure, a block-specific
> > + * structure is appended to the @data buffer, one after the other without gaps
> > + * in between nor overlaps. Userspace shall populate the @total_size field with
> > + * the effective size, in bytes, of the @data buffer.
> > + *
> > + * The expected memory layout of the parameters buffer is::
> > + *
> > + *	+-------------------- struct c3_isp_params_buffer ------------------+
> > + *	| version = C3_ISP_PARAM_BUFFER_V0;                                   |
> > + *	| total_size = sizeof(sizeof(struct wb_change_cfg))                   |
> > + *	|              sizeof(sizeof(struct wb_luma_cfg));                    |
> > + *	| +------------------------- data  ---------------------------------+ |
> > + *	| | +------------------ struct wb_change_cfg) --------------------+ | |
> > + *	| | | +---------  struct c3_isp_param_block_header header  -----+ | | |
> > + *	| | | | type = C3_ISP_PARAM_BLOCK_WB_CHANGE;                    | | | |
> > + *	| | | | enabled = true;                                            | | | |
> > + *	| | | | size =                                                  | | | |
> > + *	| | | |    sizeof(struct c3_isp_param_block_header header);     | | | |
> > + *	| | | +---------------------------------------------------------+ | | |
> > + *	| | | wb_gain[5] = ...;                                           | | |
> > + *	| | | wb_limit[5] = ...;                                          | | |
> > + *	| | | ae_gain_grbgi[5] = ...;                                     | | |
> > + *	| | | ae_bl12_grbgi[5] = ...;                                     | | |
> > + *	| | +------------------ struct wb_luma_cfg -----------------------+ | |
> > + *	| | | +---------- struct c3_isp_param_block_header header ------+ | | |
> > + *	| | | | type = C3_ISP_PARAM_BLOCK_WB_LUMA;                      | | | |
> > + *	| | | | enabled = true;                                            | | | |
> > + *	| | | | size = sizeof(struct wb_luma_cfg);                      | | | |
> > + *	| | | +---------------------------------------------------------+ | | |
> > + *	| | | awb_stat_blc20[4] = ...;                                    | | |
> > + *	| | | awb_stat_gain10[4] = ...;                                   | | |
> > + *	| | | awb_stat_satur_low = ...;                                   | | |
> > + *	| | | awb_stat_satur_high = ...;                                  | | |
> > + *	| | +-------------------------------------------------------------+ | |
> > + *	| +-----------------------------------------------------------------+ |
> > + *	+---------------------------------------------------------------------+
> > + *
> > + * @version: The C3 ISP parameters buffer version
> > + * @total_size: The C3 ISP configuration data effective size,
> > + *		excluding this header
> > + * @data: The C3 ISP configuration blocks data
> > + */
> > +struct c3_isp_params_buffer {
> > +	enum c3_isp_param_buffer_version version;
> > +	size_t total_size;
> > +	u8 data[C3_ISP_PARAMS_MAX_SIZE];
> > +};
> > +
> > +#endif
> >
> > --
> > 2.46.1
> >
> >
> >

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

* Re: [PATCH v3 4/9] media: platform: Add c3 mipi adapter driver
  2024-11-07  8:48       ` Jacopo Mondi
@ 2024-11-08  7:48         ` Keke Li
  0 siblings, 0 replies; 37+ messages in thread
From: Keke Li @ 2024-11-08  7:48 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi Jacopo

Thanks very much for  your reply.

On 2024/11/7 16:48, Jacopo Mondi wrote:
> [ EXTERNAL EMAIL ]
>
> Hi Keke
>
> On Wed, Nov 06, 2024 at 01:32:58PM +0800, Keke Li wrote:
>> Hi Jacopo
>>
>> Thanks very much for your reply.
>>
>> On 2024/11/5 19:24, Jacopo Mondi wrote:
>>> [You don't often get email from jacopo.mondi@ideasonboard.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>>>
>>> [ EXTERNAL EMAIL ]
>>>
>>> Hi Keke
>>>
>>> On Wed, Sep 18, 2024 at 02:07:15PM +0800, Keke Li via B4 Relay wrote:
>>>> From: Keke Li <keke.li@amlogic.com>
>>>>
>>>> This driver mainly responsible for organizing
>>>> MIPI data and sending raw data to ISP pipeline.
>>> A few questions on the design of this subdevice.
>>>
>>> In my understanding the adapter provides several functionalities:
>>>
>>> - Data alignement to match the ISP size constraints
>>> - Read from DDR memory or from the CSI-2 RX (direct path)
>>> - Write to DDR memory or to the ISP (direct path)
>>> - Split data streams to DDR or direct path based on CSI-2 Virtual
>>>     Channel and Data type.
>>
>> Yes, you are right.
>>
>>> As I understand the current implementation only supports direct-path
>>> to ISP of data sent on VC#0.
>>
>> Read image from DDR and wirte image to DDR  are used in WDR mode.
>>
>> Due to the lack of WDR requirement,  This driver only support direct-path.
>>
>>> Do you plan to expand the adapter subdev with support for mulitple
>>> paths ? Is this why you are supporting already a routing table ?
>> If there is a demand in the future, I will support multiple paths.
>>
>> But now I attend to only support one path and will drop the set_routing()
>> interface.
>>
>>
>>>> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
>>>> Signed-off-by: Keke Li <keke.li@amlogic.com>
>>>> ---
>>>>    MAINTAINERS                                        |   7 +
>>>>    drivers/media/platform/amlogic/Kconfig             |   1 +
>>>>    drivers/media/platform/amlogic/Makefile            |   1 +
>>>>    .../media/platform/amlogic/c3-mipi-adapter/Kconfig |  16 +
>>>>    .../platform/amlogic/c3-mipi-adapter/Makefile      |   3 +
>>>>    .../amlogic/c3-mipi-adapter/c3-mipi-adap.c         | 913 +++++++++++++++++++++
>>>>    6 files changed, 941 insertions(+)
>>>>
>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>> index 9e75874a6e69..31168c05f304 100644
>>>> --- a/MAINTAINERS
>>>> +++ b/MAINTAINERS
>>>> @@ -1209,6 +1209,13 @@ F:     Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
>>>>    F:   drivers/perf/amlogic/
>>>>    F:   include/soc/amlogic/
>>>>
>>>> +AMLOGIC MIPI ADAPTER DRIVER
>>>> +M:   Keke Li <keke.li@amlogic.com>
>>>> +L:   linux-media@vger.kernel.org
>>>> +S:   Maintained
>>>> +F:   Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml
>>>> +F:   drivers/media/platform/amlogic/c3-mipi-adapter/
>>>> +
>>>>    AMLOGIC MIPI CSI2 DRIVER
>>>>    M:   Keke Li <keke.li@amlogic.com>
>>>>    L:   linux-media@vger.kernel.org
>>>> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
>>>> index b7c2de14848b..df09717b28d0 100644
>>>> --- a/drivers/media/platform/amlogic/Kconfig
>>>> +++ b/drivers/media/platform/amlogic/Kconfig
>>>> @@ -2,5 +2,6 @@
>>>>
>>>>    comment "Amlogic media platform drivers"
>>>>
>>>> +source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
>>>>    source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>>>>    source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
>>>> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
>>>> index 4f571ce5d13e..b370154b090c 100644
>>>> --- a/drivers/media/platform/amlogic/Makefile
>>>> +++ b/drivers/media/platform/amlogic/Makefile
>>>> @@ -1,4 +1,5 @@
>>>>    # SPDX-License-Identifier: GPL-2.0-only
>>>>
>>>> +obj-y += c3-mipi-adapter/
>>>>    obj-y += c3-mipi-csi2/
>>>>    obj-y += meson-ge2d/
>>>> diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig b/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig
>>>> new file mode 100644
>>>> index 000000000000..bf19059b3543
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig
>>>> @@ -0,0 +1,16 @@
>>>> +# SPDX-License-Identifier: GPL-2.0-only
>>>> +
>>>> +config VIDEO_C3_MIPI_ADAPTER
>>>> +     tristate "Amlogic C3 MIPI adapter"
>>>> +     depends on ARCH_MESON || COMPILE_TEST
>>>> +     depends on VIDEO_DEV
>>>> +     depends on OF
>>>> +     select MEDIA_CONTROLLER
>>>> +     select V4L2_FWNODE
>>>> +     select VIDEO_V4L2_SUBDEV_API
>>>> +     help
>>>> +       Video4Linux2 driver for Amlogic C3 MIPI adapter.
>>>> +       C3 MIPI adapter mainly responsible for organizing
>>>> +       MIPI data and sending raw data to ISP pipeline.
>>>> +
>>>> +       To compile this driver as a module choose m here.
>>>> diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile b/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile
>>>> new file mode 100644
>>>> index 000000000000..216fc310c5b4
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile
>>>> @@ -0,0 +1,3 @@
>>>> +# SPDX-License-Identifier: GPL-2.0-only
>>>> +
>>>> +obj-$(CONFIG_VIDEO_C3_MIPI_ADAPTER) += c3-mipi-adap.o
>>>> diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c b/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c
>>>> new file mode 100644
>>>> index 000000000000..b64eb417c2e2
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c
>>>> @@ -0,0 +1,913 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#include <linux/cleanup.h>
>>>> +#include <linux/clk.h>
>>>> +#include <linux/device.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/mutex.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +
>>>> +#include <media/v4l2-async.h>
>>>> +#include <media/v4l2-common.h>
>>>> +#include <media/v4l2-device.h>
>>>> +#include <media/v4l2-fwnode.h>
>>>> +#include <media/v4l2-mc.h>
>>>> +#include <media/v4l2-subdev.h>
>>>> +
>>>> +/* C3 adapter submodule definition */
>>>> +enum {
>>>> +     SUBMD_TOP,
>>>> +     SUBMD_FD,
>>>> +     SUBMD_RD,
>>>> +};
>>>> +
>>>> +#define ADAP_SUBMD_MASK             GENMASK(17, 16)
>>>> +#define ADAP_SUBMD_SHIFT            16
>>>> +#define ADAP_SUBMD(x)               (((x) & (ADAP_SUBMD_MASK)) >> (ADAP_SUBMD_SHIFT))
>>>> +#define ADAP_REG_ADDR_MASK          GENMASK(15, 0)
>>>> +#define ADAP_REG_ADDR(x)            ((x) & (ADAP_REG_ADDR_MASK))
>>>> +#define ADAP_REG_T(x)               ((SUBMD_TOP << ADAP_SUBMD_SHIFT) | (x))
>>>> +#define ADAP_REG_F(x)               ((SUBMD_FD << ADAP_SUBMD_SHIFT) | (x))
>>>> +#define ADAP_REG_R(x)               ((SUBMD_RD << ADAP_SUBMD_SHIFT) | (x))
>>>> +
>>>> +#define MIPI_ADAP_CLOCK_NUM_MAX     3
>>>> +#define MIPI_ADAP_SUBDEV_NAME       "mipi-adapter"
>>> I would add a "c3-" prefix
>>>
>> Will add "c3-" prefix
>>
>>>> +
>>>> +/* C3 MIPI adapter TOP register */
>>>> +#define MIPI_ADAPT_DE_CTRL0         ADAP_REG_T(0x40)
>>>> +#define ADAP_DE_READ_BYPASS         BIT(3)
>>>> +#define ADAP_DE_WRITE_BYPASS        BIT(7)
>>>> +
>>>> +/* C3 MIPI adapter FRONTEND register */
>>>> +#define CSI2_CLK_RESET              ADAP_REG_F(0x00)
>>>> +#define ADAP_FD_APPLY_RESET         BIT(0)
>>>> +#define ADAP_FD_ENABLE              BIT(1)
>>>> +
>>>> +#define CSI2_GEN_CTRL0              ADAP_REG_F(0x04)
>>>> +#define ADAP_FD_VIRTUAL_CHN0_EN     BIT(0)
>>>> +#define ADAP_FD_VIRTUAL_CHN1_EN     BIT(1)
>>>> +#define ADAP_FD_VIRTUAL_CHN2_EN     BIT(2)
>>>> +#define ADAP_FD_VIRTUAL_CHN3_EN     BIT(3)
>>>> +#define ADAP_FD_ENABLE_PACKETS      GENMASK(20, 16)
>>>> +#define ADAP_FD_ENABLE_RAW          BIT(16)
>>>> +
>>>> +#define CSI2_X_START_END_ISP        ADAP_REG_F(0x0c)
>>>> +#define ADAP_FD_X_END_MASK          GENMASK(31, 16)
>>>> +#define ADAP_FD_X_END_SHIFT         16
>>>> +#define ADAP_FD_X_END(x)            ((x) - 1)
>>>> +
>>>> +#define CSI2_Y_START_END_ISP        ADAP_REG_F(0x10)
>>>> +#define ADAP_FD_Y_END_MASK          GENMASK(31, 16)
>>>> +#define ADAP_FD_Y_END_SHIFT         16
>>>> +#define ADAP_FD_Y_END(x)            ((x) - 1)
>>>> +
>>>> +#define CSI2_VC_MODE                ADAP_REG_F(0x1c)
>>>> +#define ADAP_FD_VS_SEL_VC_MASK      GENMASK(19, 16)
>>>> +#define ADAP_FD_VS_DIRECT_PATH      BIT(16)
>>>> +#define ADAP_FD_HS_SEL_VC_MASK      GENMASK(23, 20)
>>>> +#define ADAP_FD_HS_DIRECT_PATH      BIT(20)
>>>> +
>>>> +/* C3 MIPI adapter READER register */
>>>> +#define MIPI_ADAPT_DDR_RD0_CNTL0    ADAP_REG_R(0x00)
>>>> +#define ADAP_RD0_MODULE_ENABLE      BIT(0)
>>>> +#define ADAP_RD0_LINE_STRIDE_MASK   GENMASK(13, 4)
>>>> +#define ADAP_RD0_LINE_STRIDE_SHIFT  4
>>>> +#define ADAP_RD0_SAMPLE_SEL_MASK    GENMASK(27, 26)
>>>> +#define ADAP_RD0_DATA_IN_VSYNC      BIT(26)
>>>> +#define ADAP_RD0_BURST_TYPE_MASK    GENMASK(29, 28)
>>>> +#define ADAP_RD0_BURST_TYPE_SHIFT   28
>>>> +#define ADAP_RD0_BURST_TYPE_INRC8   3
>>>> +#define ADAP_RD0_FRAME_RD_START     BIT(31)
>>>> +
>>>> +#define MIPI_ADAPT_DDR_RD0_CNTL1    ADAP_REG_R(0x04)
>>>> +#define ADAP_RD0_LINE_SIZE_MASK     GENMASK(9, 0)
>>>> +#define ADAP_RD0_LINE_NUM_MASK      GENMASK(28, 16)
>>>> +#define ADAP_RD0_LINE_NUM_SHIFT     16
>>>> +
>>>> +#define MIPI_ADAPT_PIXEL0_CNTL0     ADAP_REG_R(0x80)
>>>> +#define ADAP_PIXEL0_WORK_MODE_MASK  GENMASK(17, 16)
>>>> +#define ADAP_PIXEL0_DIRECT_PATH     BIT(16)
>>>> +#define ADAP_PIXEL0_DATA_TYPE_MASK  GENMASK(25, 20)
>>>> +#define ADAP_PIXEL0_DATA_TYPE_SHIFT 20
>>>> +#define ADAP_PIXEL0_DATA_TYPE_10BITS 0x2b
>>>> +#define ADAP_PIXEL0_DATA_TYPE_12BITS 0x2c
>>>> +#define ADAP_PIXEL0_START_ENABLE    BIT(31)
>>>> +
>>>> +#define MIPI_ADAPT_PIXEL0_CNTL1     ADAP_REG_R(0x84)
>>>> +#define ADAP_PIXEL0_X_END_MASK      GENMASK(15, 0)
>>>> +#define ADAP_PIXEL0_X_END(x)        ((x) - 1)
>>>> +
>>>> +#define MIPI_ADAPT_PIXEL0_CNTL2     ADAP_REG_R(0x88)
>>>> +#define ADAP_PIXEL0_FIFO_SIZE_MASK  GENMASK(9, 0)
>>>> +#define ADAP_PIXEL0_PIXEL_NUM_MASK  GENMASK(27, 15)
>>>> +#define ADAP_PIXEL0_PIXEL_NUM_SHIFT 15
>>>> +
>>>> +#define MIPI_ADAPT_ALIG_CNTL0       ADAP_REG_R(0x100)
>>>> +#define ADAP_ALIG_V_TOTAL_NUM_MASK  GENMASK(15, 0)
>>>> +/* Need to add a default blank */
>>>> +#define ADAP_ALIG_V_TOTAL_NUM(x)    ((x) + 64)
>>>> +#define ADAP_ALIG_H_TOTAL_NUM_MASK  GENMASK(31, 16)
>>>> +#define ADAP_ALIG_H_TOTAL_NUM_SHIFT 16
>>>> +/* Need to add a default blank */
>>>> +#define ADAP_ALIG_H_TOTAL_NUM(x)    ((x) + 64)
>>>> +
>>>> +#define MIPI_ADAPT_ALIG_CNTL1       ADAP_REG_R(0x104)
>>>> +#define ADAP_ALIG_HPE_NUM_MASK      GENMASK(31, 16)
>>>> +#define ADAP_ALIG_HPE_NUM_SHIFT     16
>>>> +
>>>> +#define MIPI_ADAPT_ALIG_CNTL2       ADAP_REG_R(0x108)
>>>> +#define ADAP_ALIG_VPE_NUM_MASK      GENMASK(31, 16)
>>>> +#define ADAP_ALIG_VPE_NUM_SHIFT     16
>>>> +
>>>> +#define MIPI_ADAPT_ALIG_CNTL3       ADAP_REG_R(0x10c)
>>>> +#define ADAP_ALIG_FRM_ST_PIXEL_MASK GENMASK(15, 0)
>>>> +
>>>> +#define MIPI_ADAPT_ALIG_CNTL6       ADAP_REG_R(0x118)
>>>> +#define ADAP_ALIG_LANE0_ENABLE      BIT(0)
>>>> +#define ADAP_ALIG_DATA_MODE0_MASK   BIT(4)
>>>> +#define ADAP_ALIG_DIRECT_MODE       BIT(4)
>>>> +#define ADAP_ALIG_VDATA0_ENABLE     BIT(12)
>>>> +#define ADAP_ALIG_VDATA1_ENABLE     BIT(13)
>>>> +#define ADAP_ALIG_VDATA2_ENABLE     BIT(14)
>>>> +#define ADAP_ALIG_VDATA3_ENABLE     BIT(15)
>>>> +
>>>> +#define MIPI_ADAPT_ALIG_CNTL8       ADAP_REG_R(0x120)
>>>> +#define ADAP_ALIG_FRAME_CONTINUE    BIT(5)
>>>> +#define ADAP_ALIG_EXC_MASK_DIS      BIT(12)
>>>> +#define ADAP_ALIG_START_ENABLE      BIT(31)
>>>> +
>>>> +#define MIPI_ADAP_MAX_WIDTH         2888
>>>> +#define MIPI_ADAP_MIN_WIDTH         160
>>>> +#define MIPI_ADAP_MAX_HEIGHT        2240
>>>> +#define MIPI_ADAP_MIN_HEIGHT        120
>>>> +#define MIPI_ADAP_DEFAULT_WIDTH     1920
>>>> +#define MIPI_ADAP_DEFAULT_HEIGHT    1080
>>>> +#define MIPI_ADAP_DEFAULT_FMT       MEDIA_BUS_FMT_SRGGB10_1X10
>>>> +
>>>> +/* C3 MIPI adapter pad list */
>>>> +enum {
>>>> +     MIPI_ADAP_PAD_SINK,
>>>> +     MIPI_ADAP_PAD_SRC,
>>>> +     MIPI_ADAP_PAD_MAX
>>>> +};
>>>> +
>>>> +/*
>>>> + * struct adap_info - mipi adapter information
>>>> + *
>>>> + * @clocks: array of mipi adapter clock names
>>>> + * @clock_rates: array of mipi adapter clock rate
>>>> + * @clock_num: actual clock number
>>>> + */
>>>> +struct adap_info {
>>>> +     char *clocks[MIPI_ADAP_CLOCK_NUM_MAX];
>>>> +     u32 clock_rates[MIPI_ADAP_CLOCK_NUM_MAX];
>>>> +     u32 clock_num;
>>>> +};
>>>> +
>>>> +/**
>>> Here as well you can drop one * from /**
>>
>> Will drop one  * from  /**
>>
>>>> + * struct adap_device - mipi adapter platform device
>>>> + *
>>>> + * @dev: pointer to the struct device
>>>> + * @top: mipi adapter top register address
>>>> + * @fd: mipi adapter frontend register address
>>>> + * @rd: mipi adapter reader register address
>>>> + * @clks: array of MIPI adapter clocks
>>>> + * @sd: mipi adapter sub-device
>>>> + * @pads: mipi adapter sub-device pads
>>>> + * @notifier: notifier to register on the v4l2-async API
>>>> + * @format: save sub-device format
>>>> + * @src_sd: source sub-device
>>>> + * @src_sd_pad: source sub-device pad
>>>> + * @lock: protect mipi adapter device
>>>> + * @info: version-specific MIPI adapter information
>>>> + */
>>>> +struct adap_device {
>>>> +     struct device *dev;
>>>> +     void __iomem *top;
>>>> +     void __iomem *fd;
>>>> +     void __iomem *rd;
>>>> +     struct clk_bulk_data clks[MIPI_ADAP_CLOCK_NUM_MAX];
>>>> +
>>>> +     struct v4l2_subdev sd;
>>>> +     struct media_pad pads[MIPI_ADAP_PAD_MAX];
>>>> +     struct v4l2_async_notifier notifier;
>>>> +     struct v4l2_subdev_format format;
>>>> +     struct v4l2_subdev *src_sd;
>>>> +
>>>> +     u16 src_sd_pad;
>>>> +     struct mutex lock; /* Protect adapter device */
>>> Most comments on the csi2-rx patch apply to this one as well.
>>> In this case, you don't need locking for functions that receive a
>>> subdev_state
>>>
>> Will remove this lock.
>>>> +     const struct adap_info *info;
>>>> +};
>>>> +
>>>> +/* Format helpers */
>>>> +
>>>> +struct adap_pix_format {
>>>> +     u32 code;
>>>> +     u8 width;
>>> I would call it bpp
>> Will replace width with bpp
>>>> +};
>>>> +
>>>> +static const struct adap_pix_format c3_mipi_adap_formats[] = {
>>>> +     { MEDIA_BUS_FMT_SBGGR10_1X10, 10 },
>>>> +     { MEDIA_BUS_FMT_SGBRG10_1X10, 10 },
>>>> +     { MEDIA_BUS_FMT_SGRBG10_1X10, 10 },
>>>> +     { MEDIA_BUS_FMT_SRGGB10_1X10, 10 },
>>>> +     { MEDIA_BUS_FMT_SBGGR12_1X12, 12 },
>>>> +     { MEDIA_BUS_FMT_SGBRG12_1X12, 12 },
>>>> +     { MEDIA_BUS_FMT_SGRBG12_1X12, 12 },
>>>> +     { MEDIA_BUS_FMT_SRGGB12_1X12, 12 },
>>>> +};
>>>> +
>>>> +static const struct adap_pix_format
>>>> +*c3_mipi_adap_find_format(u32 code)
>>>> +{
>>>> +     unsigned int i;
>>>> +
>>>> +     for (i = 0; i < ARRAY_SIZE(c3_mipi_adap_formats); i++)
>>>> +             if (code == c3_mipi_adap_formats[i].code)
>>>> +                     return &c3_mipi_adap_formats[i];
>>>> +
>>>> +     return NULL;
>>>> +}
>>>> +
>>>> +/* Hardware configuration */
>>>> +
>>>> +static void c3_mipi_adap_update_bits(struct adap_device *adap, u32 reg,
>>>> +                                  u32 mask, u32 val)
>>>> +{
>>>> +     void __iomem *addr;
>>>> +     u32 orig, tmp;
>>>> +
>>>> +     switch (ADAP_SUBMD(reg)) {
>>>> +     case SUBMD_TOP:
>>>> +             addr = adap->top + ADAP_REG_ADDR(reg);
>>>> +             break;
>>>> +     case SUBMD_FD:
>>>> +             addr = adap->fd + ADAP_REG_ADDR(reg);
>>>> +             break;
>>>> +     case SUBMD_RD:
>>>> +             addr = adap->rd + ADAP_REG_ADDR(reg);
>>>> +             break;
>>>> +     default:
>>>> +             dev_err(adap->dev, "Invalid sub-module: %lu\n", ADAP_SUBMD(reg));
>>>> +             return;
>>>> +     }
>>>> +
>>>> +     orig = readl(addr);
>>>> +     tmp = orig & ~mask;
>>>> +     tmp |= val & mask;
>>>> +
>>>> +     if (tmp != orig)
>>>> +             writel(tmp, addr);
>>>> +}
>>>> +
>>>> +/* Configure adapter top sub module */
>>>> +static void c3_mipi_adap_cfg_top(struct adap_device *adap)
>>>> +{
>>>> +     /* Bypass decompress */
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0,
>>>> +                              ADAP_DE_READ_BYPASS, ADAP_DE_READ_BYPASS);
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0,
>>>> +                              ADAP_DE_WRITE_BYPASS, ADAP_DE_WRITE_BYPASS);
>>>> +}
>>>> +
>>>> +/* Configure adapter frontend sub module */
>>>> +static void c3_mipi_adap_cfg_frontend(struct adap_device *adap,
>>>> +                                   struct v4l2_mbus_framefmt *fmt)
>>>> +{
>>>> +     /* The default value of BIT_0 is 1, so need release reset firstly. */
>>>> +     c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET, ADAP_FD_APPLY_RESET, 0);
>>>> +
>>>> +     c3_mipi_adap_update_bits(adap, CSI2_X_START_END_ISP, ADAP_FD_X_END_MASK,
>>>> +                              ADAP_FD_X_END(fmt->width) << ADAP_FD_X_END_SHIFT);
>>>> +     c3_mipi_adap_update_bits(adap, CSI2_Y_START_END_ISP, ADAP_FD_Y_END_MASK,
>>>> +                              ADAP_FD_Y_END(fmt->height) << ADAP_FD_Y_END_SHIFT);
>>>> +
>>>> +     /* Select VS and HS signal to direct path */
>>>> +     c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, ADAP_FD_VS_SEL_VC_MASK,
>>>> +                              ADAP_FD_VS_DIRECT_PATH);
>>>> +     c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, ADAP_FD_HS_SEL_VC_MASK,
>>>> +                              ADAP_FD_HS_DIRECT_PATH);
>>>> +
>>>> +     /* Enable to receive RAW image */
>>>> +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_ENABLE_PACKETS,
>>>> +                              ADAP_FD_ENABLE_RAW);
>>>> +
>>>> +     /* Enable virtual channel 0~3 */
>>>> +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN0_EN,
>>>> +                              ADAP_FD_VIRTUAL_CHN0_EN);
>>>> +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN1_EN,
>>>> +                              ADAP_FD_VIRTUAL_CHN1_EN);
>>>> +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN2_EN,
>>>> +                              ADAP_FD_VIRTUAL_CHN2_EN);
>>>> +     c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN3_EN,
>>>> +                              ADAP_FD_VIRTUAL_CHN3_EN);
>>>> +}
>>>> +
>>>> +/* Configure adapter reader sub module */
>>>> +static void c3_mipi_adap_cfg_reader(struct adap_device *adap,
>>>> +                                 struct v4l2_mbus_framefmt *fmt)
>>>> +{
>>>> +     const struct adap_pix_format *pix_format;
>>>> +     u32 line_size;
>>>> +     u8 data_type;
>>>> +
>>>> +     /* Data size for a line, unit: 128 bits */
>>>> +     pix_format = c3_mipi_adap_find_format(fmt->code);
>>>> +     line_size = fmt->width * pix_format->width;
>>>> +     line_size = DIV_ROUND_UP(line_size, 128);
>>>> +
>>>> +     if (pix_format->width == 10) {
>>>> +             data_type = ADAP_PIXEL0_DATA_TYPE_10BITS;
>>>> +     } else if (pix_format->width == 12) {
>>>> +             data_type = ADAP_PIXEL0_DATA_TYPE_12BITS;
>>> You should use MIPI_CSI2_DT_RAW10 and MIPI_CSI2_DT_RAW12 from
>>> "include/media/mipi-csi2.h"
>>>
>> OK,  Will use MIPI_CSI2_DT_RAW10 and MIPI_CSI2_DT_RAW12.
>>>> +     } else {
>>>> +             dev_err(adap->dev, "Invalid raw format width: %u\n", pix_format->width);
>>>> +             return;
>>>> +     }
>>>> +
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL1,
>>>> +                              ADAP_RD0_LINE_SIZE_MASK, line_size);
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL1, ADAP_RD0_LINE_NUM_MASK,
>>>> +                              fmt->height << ADAP_RD0_LINE_NUM_SHIFT);
>>>> +
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_MODULE_ENABLE,
>>>> +                              ADAP_RD0_MODULE_ENABLE);
>>> I'm not sure I get this. I read this bit as "Enable reading from DDR"
>>> while I thought the adapter receives data directly from the CSI-2
>>> receiver
>> This place is a bit confusing.
>> I will check this issue  and add explanation.
>>
>>
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_LINE_STRIDE_MASK,
>>>> +                              line_size << ADAP_RD0_LINE_STRIDE_SHIFT);
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_SAMPLE_SEL_MASK,
>>>> +                              ADAP_RD0_DATA_IN_VSYNC);
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_BURST_TYPE_MASK,
>>>> +                              ADAP_RD0_BURST_TYPE_INRC8 << ADAP_RD0_BURST_TYPE_SHIFT);
>>>> +
>>>> +     /* Set data type and work mode */
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0,
>>>> +                              ADAP_PIXEL0_WORK_MODE_MASK, ADAP_PIXEL0_DIRECT_PATH);
>>> Ah maybe this setting "RAW from direct path" enables direct path from
>>> CSI-2 receiver
>>>
>> PIXEL0 is a sub module in adapter and should be configured.
>>
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0, ADAP_PIXEL0_DATA_TYPE_MASK,
>>>> +                              data_type << ADAP_PIXEL0_DATA_TYPE_SHIFT);
>>>> +
>>>> +     /* Set pixel end number */
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL1, ADAP_PIXEL0_X_END_MASK,
>>>> +                              ADAP_PIXEL0_X_END(fmt->width));
>>>> +
>>>> +     /* Set line pixel number and reader fifo size */
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL2,
>>>> +                              ADAP_PIXEL0_FIFO_SIZE_MASK, line_size);
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL2, ADAP_PIXEL0_PIXEL_NUM_MASK,
>>>> +                              fmt->width << ADAP_PIXEL0_PIXEL_NUM_SHIFT);
>>>> +
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, ADAP_ALIG_V_TOTAL_NUM_MASK,
>>>> +                              ADAP_ALIG_V_TOTAL_NUM(fmt->width));
>>> What are the ISP alignment requirements ?
>>>
>>> #define ADAP_ALIG_V_TOTAL_NUM(x)    ((x) + 64)
>>>
>>> Are you aligning or just adding 64 here ?
>>
>> ISP hardware requires at least 40 v-blanks,
> Ok, now I see the requirements: 40 v-blanks, 64 h-blanks.
>
>> so here add 64 to ensure ISP can function properly.
>>
> However I read the register ALIGN_CNTL0 to be described as v_total_num
> and h_total_num which makes me think the actual image data are
> expanded to the desired values ?  Or maybe it's just
> to add enough processing margins to the ISP ?


Yes, the function of ALIGN is to align frame to meet

the requirement of ISP.

>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, ADAP_ALIG_H_TOTAL_NUM_MASK,
>>>> +                              ADAP_ALIG_H_TOTAL_NUM(fmt->height)
>>>> +                              << ADAP_ALIG_H_TOTAL_NUM_SHIFT);
>>>> +
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL1, ADAP_ALIG_HPE_NUM_MASK,
>>>> +                              fmt->width << ADAP_ALIG_HPE_NUM_SHIFT);
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL2, ADAP_ALIG_VPE_NUM_MASK,
>>>> +                              fmt->height << ADAP_ALIG_VPE_NUM_SHIFT);
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL3,
>>>> +                              ADAP_ALIG_FRM_ST_PIXEL_MASK, fmt->width);
> This is also something I'm not sure I get. The registers seems to be
> there to skip reading a number of lines and pixels at the beginning of
> the frame. It however only take effect if frame_sync_en =1 something
> the driver doesn't set ?
>

I have checked ALIG_CNTL3 register.

There is no need to configure ALIG_CNTL3 when frame_sync_en = 0.

The default value of frame_sync_en is 0.

So will remove this configuration.

>>>> +
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
>>>> +                              ADAP_ALIG_LANE0_ENABLE, ADAP_ALIG_LANE0_ENABLE);
>>>> +
>>>> +     /* Select direct mode */
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
>>>> +                              ADAP_ALIG_DATA_MODE0_MASK, ADAP_ALIG_DIRECT_MODE);
>>>> +
>>>> +     /* Enable vdata 0~3 */
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
>>>> +                              ADAP_ALIG_VDATA0_ENABLE, ADAP_ALIG_VDATA0_ENABLE);
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
>>>> +                              ADAP_ALIG_VDATA1_ENABLE, ADAP_ALIG_VDATA1_ENABLE);
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
>>>> +                              ADAP_ALIG_VDATA2_ENABLE, ADAP_ALIG_VDATA2_ENABLE);
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6,
>>>> +                              ADAP_ALIG_VDATA3_ENABLE, ADAP_ALIG_VDATA2_ENABLE);
>>> For my education could you tell what these bit do ?
>> Will add explanation.
> Thanks, I presume it's about controlling the internl bus between the
> adapter and the ISP. A comment on how it works would be apreciated.
>
> Thanks
>    j
>

OK.

>>>> +
>>>> +     /* continue mode and disable hold counter */
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
>>>> +                              ADAP_ALIG_FRAME_CONTINUE, ADAP_ALIG_FRAME_CONTINUE);
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
>>>> +                              ADAP_ALIG_EXC_MASK_DIS, ADAP_ALIG_EXC_MASK_DIS);
>>>> +}
>>>> +
>>>> +static void c3_mipi_adap_start_stream(struct adap_device *adap)
>>>> +{
>>>> +     /* Enable to start and will auto clear to 0 */
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8,
>>>> +                              ADAP_ALIG_START_ENABLE, ADAP_ALIG_START_ENABLE);
>>>> +
>>>> +     /* Enable to start and will auto clear to 0 */
>>>> +     c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0,
>>>> +                              ADAP_PIXEL0_START_ENABLE, ADAP_PIXEL0_START_ENABLE);
>>>> +
>>>> +     /* Enable frontend */
>>>> +     c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET,
>>>> +                              ADAP_FD_ENABLE, ADAP_FD_ENABLE);
>>>> +}
>>>> +
>>>> +static void c3_mipi_adap_cfg_format(struct adap_device *adap)
>>>> +{
>>>> +     struct v4l2_subdev_format *format = &adap->format;
>>>> +
>>>> +     c3_mipi_adap_cfg_top(adap);
>>>> +     c3_mipi_adap_cfg_frontend(adap, &format->format);
>>>> +     c3_mipi_adap_cfg_reader(adap, &format->format);
>>>> +}
>>>> +
>>>> +/* V4L2 subdev operations */
>>>> +
>>>> +static int c3_mipi_adap_enable_streams(struct v4l2_subdev *sd,
>>>> +                                    struct v4l2_subdev_state *state,
>>>> +                                    u32 pad, u64 streams_mask)
>>>> +{
>>>> +     struct adap_device *adap = v4l2_get_subdevdata(sd);
>>>> +     u64 sink_streams;
>>>> +     int ret;
>>>> +
>>>> +     guard(mutex)(&adap->lock);
>>>> +
>>>> +     pm_runtime_resume_and_get(adap->dev);
>>>> +
>>>> +     c3_mipi_adap_cfg_format(adap);
>>>> +     c3_mipi_adap_start_stream(adap);
>>>> +
>>>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>>> +                                                    MIPI_ADAP_PAD_SINK,
>>>> +                                                    &streams_mask);
>>>> +     ret = v4l2_subdev_enable_streams(adap->src_sd,
>>>> +                                      adap->src_sd_pad,
>>>> +                                      sink_streams);
>>>> +     if (ret) {
>>>> +             pm_runtime_put(adap->dev);
>>>> +             return ret;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_mipi_adap_disable_streams(struct v4l2_subdev *sd,
>>>> +                                     struct v4l2_subdev_state *state,
>>>> +                                     u32 pad, u64 streams_mask)
>>>> +{
>>>> +     struct adap_device *adap = v4l2_get_subdevdata(sd);
>>>> +     u64 sink_streams;
>>>> +     int ret;
>>>> +
>>>> +     guard(mutex)(&adap->lock);
>>>> +
>>>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>>> +                                                    MIPI_ADAP_PAD_SINK,
>>>> +                                                    &streams_mask);
>>>> +     ret = v4l2_subdev_disable_streams(adap->src_sd,
>>>> +                                       adap->src_sd_pad,
>>>> +                                       sink_streams);
>>>> +     if (ret)
>>>> +             dev_err(adap->dev, "Failed to disable %s\n", adap->src_sd->name);
>>>> +
>>>> +     pm_runtime_put(adap->dev);
>>>> +
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static int c3_mipi_adap_cfg_routing(struct v4l2_subdev *sd,
>>>> +                                 struct v4l2_subdev_state *state,
>>>> +                                 struct v4l2_subdev_krouting *routing)
>>>> +{
>>>> +     static const struct v4l2_mbus_framefmt format = {
>>>> +             .width = MIPI_ADAP_DEFAULT_WIDTH,
>>>> +             .height = MIPI_ADAP_DEFAULT_HEIGHT,
>>>> +             .code = MIPI_ADAP_DEFAULT_FMT,
>>>> +             .field = V4L2_FIELD_NONE,
>>>> +             .colorspace = V4L2_COLORSPACE_RAW,
>>>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>>>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>>>> +             .xfer_func = V4L2_XFER_FUNC_NONE,
>>>> +     };
>>>> +     int ret;
>>>> +
>>>> +     ret = v4l2_subdev_routing_validate(sd, routing,
>>>> +                                        V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_mipi_adap_init_routing(struct v4l2_subdev *sd,
>>>> +                                  struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_subdev_route routes;
>>>> +     struct v4l2_subdev_krouting routing;
>>>> +
>>>> +     routes.sink_pad = MIPI_ADAP_PAD_SINK;
>>>> +     routes.sink_stream = 0;
>>>> +     routes.source_pad = MIPI_ADAP_PAD_SRC;
>>>> +     routes.source_stream = 0;
>>>> +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>>>> +
>>>> +     routing.num_routes = 1;
>>>> +     routing.routes = &routes;
>>>> +
>>>> +     return c3_mipi_adap_cfg_routing(sd, state, &routing);
>>>> +}
>>> Same comments as per the CSI-2 Rx, do you need to allow set routing
>>> when a single route in enabled and valid ?
>>>
>> Will drop the set_routing() interface.
>>
>>>> +
>>>> +static int c3_mipi_adap_set_routing(struct v4l2_subdev *sd,
>>>> +                                 struct v4l2_subdev_state *state,
>>>> +                                 enum v4l2_subdev_format_whence which,
>>>> +                                 struct v4l2_subdev_krouting *routing)
>>>> +{
>>>> +     bool is_streaming = v4l2_subdev_is_streaming(sd);
>>>> +
>>>> +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>>>> +             return -EBUSY;
>>>> +
>>>> +     return c3_mipi_adap_cfg_routing(sd, state, routing);
>>>> +}
>>>> +
>>>> +static int c3_mipi_adap_enum_mbus_code(struct v4l2_subdev *sd,
>>>> +                                    struct v4l2_subdev_state *state,
>>>> +                                    struct v4l2_subdev_mbus_code_enum *code)
>>>> +{
>>>> +     switch (code->pad) {
>>>> +     case MIPI_ADAP_PAD_SINK:
>>>> +             if (code->index >= ARRAY_SIZE(c3_mipi_adap_formats))
>>>> +                     return -EINVAL;
>>>> +
>>>> +             code->code = c3_mipi_adap_formats[code->index].code;
>>>> +             break;
>>>> +     case MIPI_ADAP_PAD_SRC:
>>>> +             struct v4l2_mbus_framefmt *fmt;
>>>> +
>>>> +             if (code->index > 0)
>>>> +                     return -EINVAL;
>>>> +
>>>> +             fmt = v4l2_subdev_state_get_format(state, code->pad);
>>>> +             code->code = fmt->code;
>>>> +             break;
>>>> +     default:
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_mipi_adap_set_fmt(struct v4l2_subdev *sd,
>>>> +                             struct v4l2_subdev_state *state,
>>>> +                             struct v4l2_subdev_format *format)
>>>> +{
>>>> +     struct adap_device *adap = v4l2_get_subdevdata(sd);
>>>> +     struct v4l2_mbus_framefmt *fmt;
>>>> +     const struct adap_pix_format *pix_format;
>>>> +
>>>> +     if (format->pad != MIPI_ADAP_PAD_SINK)
>>>> +             return v4l2_subdev_get_fmt(sd, state, format);
>>>> +
>>>> +     pix_format = c3_mipi_adap_find_format(format->format.code);
>>>> +     if (!pix_format)
>>>> +             pix_format = &c3_mipi_adap_formats[0];
>>>> +
>>>> +     fmt = v4l2_subdev_state_get_format(state, format->pad);
>>>> +     fmt->code = pix_format->code;
>>>> +     fmt->width = clamp_t(u32, format->format.width,
>>>> +                          MIPI_ADAP_MIN_WIDTH, MIPI_ADAP_MAX_WIDTH);
>>>> +     fmt->height = clamp_t(u32, format->format.height,
>>>> +                           MIPI_ADAP_MIN_HEIGHT, MIPI_ADAP_MAX_HEIGHT);
>>>> +
>>>> +     format->format = *fmt;
>>>> +
>>>> +     /* Synchronize the format to source pad */
>>>> +     fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SRC);
>>>> +     *fmt = format->format;
>>>> +
>>>> +     adap->format = *format;
>>> There is not need to store the format in the driver-specific
>>> structure. You use it c3_mipi_adap_cfg_format() called by
>>> c3_mipi_adap_enable_streams() which receives a subdev_state where the format
>>> is stored already.
>>>
>>> Thanks
>>>     j
>>
>> Will remove the adap->format
>>
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_mipi_adap_init_state(struct v4l2_subdev *sd,
>>>> +                                struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *sink_fmt;
>>>> +     struct v4l2_mbus_framefmt *src_fmt;
>>>> +
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SINK);
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SRC);
>>>> +
>>>> +     sink_fmt->width = MIPI_ADAP_DEFAULT_WIDTH;
>>>> +     sink_fmt->height = MIPI_ADAP_DEFAULT_HEIGHT;
>>>> +     sink_fmt->field = V4L2_FIELD_NONE;
>>>> +     sink_fmt->code = MIPI_ADAP_DEFAULT_FMT;
>>>> +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>>>> +     sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
>>>> +     sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
>>>> +     sink_fmt->quantization =
>>>> +             V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
>>>> +                                           sink_fmt->ycbcr_enc);
>>>> +     *src_fmt = *sink_fmt;
>>>> +
>>>> +     return c3_mipi_adap_init_routing(sd, state);
>>>> +}
>>>> +
>>>> +static const struct v4l2_subdev_pad_ops c3_mipi_adap_pad_ops = {
>>>> +     .enum_mbus_code = c3_mipi_adap_enum_mbus_code,
>>>> +     .get_fmt = v4l2_subdev_get_fmt,
>>>> +     .set_fmt = c3_mipi_adap_set_fmt,
>>>> +     .set_routing = c3_mipi_adap_set_routing,
>>>> +     .enable_streams = c3_mipi_adap_enable_streams,
>>>> +     .disable_streams = c3_mipi_adap_disable_streams,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_ops c3_mipi_adap_subdev_ops = {
>>>> +     .pad = &c3_mipi_adap_pad_ops,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_internal_ops c3_mipi_adap_internal_ops = {
>>>> +     .init_state = c3_mipi_adap_init_state,
>>>> +};
>>>> +
>>>> +/* Media entity operations */
>>>> +static const struct media_entity_operations c3_mipi_adap_entity_ops = {
>>>> +     .link_validate = v4l2_subdev_link_validate,
>>>> +};
>>>> +
>>>> +/* PM runtime */
>>>> +
>>>> +static int __maybe_unused c3_mipi_adap_runtime_suspend(struct device *dev)
>>>> +{
>>>> +     struct adap_device *adap = dev_get_drvdata(dev);
>>>> +
>>>> +     clk_bulk_disable_unprepare(adap->info->clock_num, adap->clks);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int __maybe_unused c3_mipi_adap_runtime_resume(struct device *dev)
>>>> +{
>>>> +     struct adap_device *adap = dev_get_drvdata(dev);
>>>> +
>>>> +     return clk_bulk_prepare_enable(adap->info->clock_num, adap->clks);
>>>> +}
>>>> +
>>>> +static const struct dev_pm_ops c3_mipi_adap_pm_ops = {
>>>> +     SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>>>> +                             pm_runtime_force_resume)
>>>> +     SET_RUNTIME_PM_OPS(c3_mipi_adap_runtime_suspend,
>>>> +                        c3_mipi_adap_runtime_resume, NULL)
>>>> +};
>>>> +
>>>> +/* Probe/remove & platform driver */
>>>> +
>>>> +static int c3_mipi_adap_subdev_init(struct adap_device *adap)
>>>> +{
>>>> +     struct v4l2_subdev *sd = &adap->sd;
>>>> +     int ret;
>>>> +
>>>> +     v4l2_subdev_init(sd, &c3_mipi_adap_subdev_ops);
>>>> +     sd->owner = THIS_MODULE;
>>>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>>> +     sd->internal_ops = &c3_mipi_adap_internal_ops;
>>>> +     snprintf(sd->name, sizeof(sd->name), "%s", MIPI_ADAP_SUBDEV_NAME);
>>>> +
>>>> +     sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>>>> +     sd->entity.ops = &c3_mipi_adap_entity_ops;
>>>> +
>>>> +     sd->dev = adap->dev;
>>>> +     v4l2_set_subdevdata(sd, adap);
>>>> +
>>>> +     adap->pads[MIPI_ADAP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>>>> +     adap->pads[MIPI_ADAP_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
>>>> +     ret = media_entity_pads_init(&sd->entity, MIPI_ADAP_PAD_MAX, adap->pads);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = v4l2_subdev_init_finalize(sd);
>>>> +     if (ret) {
>>>> +             media_entity_cleanup(&sd->entity);
>>>> +             return ret;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static void c3_mipi_adap_subdev_deinit(struct adap_device *adap)
>>>> +{
>>>> +     v4l2_subdev_cleanup(&adap->sd);
>>>> +     media_entity_cleanup(&adap->sd.entity);
>>>> +}
>>>> +
>>>> +/* Subdev notifier register */
>>>> +static int c3_mipi_adap_notify_bound(struct v4l2_async_notifier *notifier,
>>>> +                                  struct v4l2_subdev *sd,
>>>> +                                  struct v4l2_async_connection *asc)
>>>> +{
>>>> +     struct adap_device *adap = v4l2_get_subdevdata(notifier->sd);
>>>> +     struct media_pad *sink = &adap->sd.entity.pads[MIPI_ADAP_PAD_SINK];
>>>> +     int ret;
>>>> +
>>>> +     ret = media_entity_get_fwnode_pad(&sd->entity,
>>>> +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
>>>> +     if (ret < 0) {
>>>> +             dev_err(adap->dev, "Failed to find pad for %s\n", sd->name);
>>>> +             return ret;
>>>> +     }
>>>> +
>>>> +     adap->src_sd = sd;
>>>> +     adap->src_sd_pad = ret;
>>>> +
>>>> +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
>>>> +                                            MEDIA_LNK_FL_IMMUTABLE);
>>>> +}
>>>> +
>>>> +static const struct v4l2_async_notifier_operations c3_mipi_adap_notify_ops = {
>>>> +     .bound = c3_mipi_adap_notify_bound,
>>>> +};
>>>> +
>>>> +static int c3_mipi_adap_async_register(struct adap_device *adap)
>>>> +{
>>>> +     struct v4l2_async_connection *asc;
>>>> +     struct fwnode_handle *ep;
>>>> +     int ret;
>>>> +
>>>> +     v4l2_async_subdev_nf_init(&adap->notifier, &adap->sd);
>>>> +
>>>> +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(adap->dev), 0, 0,
>>>> +                                          FWNODE_GRAPH_ENDPOINT_NEXT);
>>>> +     if (!ep)
>>>> +             return -ENOTCONN;
>>>> +
>>>> +     asc = v4l2_async_nf_add_fwnode_remote(&adap->notifier, ep,
>>>> +                                           struct v4l2_async_connection);
>>>> +     if (IS_ERR(asc)) {
>>>> +             ret = PTR_ERR(asc);
>>>> +             goto err_put_handle;
>>>> +     }
>>>> +
>>>> +     adap->notifier.ops = &c3_mipi_adap_notify_ops;
>>>> +     ret = v4l2_async_nf_register(&adap->notifier);
>>>> +     if (ret)
>>>> +             goto err_cleanup_nf;
>>>> +
>>>> +     ret = v4l2_async_register_subdev(&adap->sd);
>>>> +     if (ret)
>>>> +             goto err_unregister_nf;
>>>> +
>>>> +     fwnode_handle_put(ep);
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_unregister_nf:
>>>> +     v4l2_async_nf_unregister(&adap->notifier);
>>>> +err_cleanup_nf:
>>>> +     v4l2_async_nf_cleanup(&adap->notifier);
>>>> +err_put_handle:
>>>> +     fwnode_handle_put(ep);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_mipi_adap_async_unregister(struct adap_device *adap)
>>>> +{
>>>> +     v4l2_async_unregister_subdev(&adap->sd);
>>>> +     v4l2_async_nf_unregister(&adap->notifier);
>>>> +     v4l2_async_nf_cleanup(&adap->notifier);
>>>> +}
>>>> +
>>>> +static int c3_mipi_adap_ioremap_resource(struct adap_device *adap)
>>>> +{
>>>> +     struct device *dev = adap->dev;
>>>> +     struct platform_device *pdev = to_platform_device(dev);
>>>> +
>>>> +     adap->top = devm_platform_ioremap_resource_byname(pdev, "top");
>>>> +     if (IS_ERR(adap->top))
>>>> +             return PTR_ERR(adap->top);
>>>> +
>>>> +     adap->fd = devm_platform_ioremap_resource_byname(pdev, "fd");
>>>> +     if (IS_ERR(adap->fd))
>>>> +             return PTR_ERR(adap->fd);
>>>> +
>>>> +     adap->rd = devm_platform_ioremap_resource_byname(pdev, "rd");
>>>> +     if (IS_ERR(adap->rd))
>>>> +             return PTR_ERR(adap->rd);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_mipi_adap_configure_clocks(struct adap_device *adap)
>>>> +{
>>>> +     const struct adap_info *info = adap->info;
>>>> +     int ret;
>>>> +     u32 i;
>>>> +
>>>> +     for (i = 0; i < info->clock_num; i++)
>>>> +             adap->clks[i].id = info->clocks[i];
>>>> +
>>>> +     ret = devm_clk_bulk_get(adap->dev, info->clock_num, adap->clks);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     for (i = 0; i < info->clock_num; i++) {
>>>> +             if (!info->clock_rates[i])
>>>> +                     continue;
>>>> +             ret = clk_set_rate(adap->clks[i].clk, info->clock_rates[i]);
>>>> +             if (ret) {
>>>> +                     dev_err(adap->dev, "Failed to set %s rate %u\n", info->clocks[i],
>>>> +                             info->clock_rates[i]);
>>>> +                     return ret;
>>>> +             }
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_mipi_adap_probe(struct platform_device *pdev)
>>>> +{
>>>> +     struct device *dev = &pdev->dev;
>>>> +     struct adap_device *adap;
>>>> +     int ret;
>>>> +
>>>> +     adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL);
>>>> +     if (!adap)
>>>> +             return -ENOMEM;
>>>> +
>>>> +     adap->info = of_device_get_match_data(dev);
>>>> +     adap->dev = dev;
>>>> +
>>>> +     ret = c3_mipi_adap_ioremap_resource(adap);
>>>> +     if (ret)
>>>> +             return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
>>>> +
>>>> +     ret = c3_mipi_adap_configure_clocks(adap);
>>>> +     if (ret)
>>>> +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
>>>> +
>>>> +     platform_set_drvdata(pdev, adap);
>>>> +
>>>> +     mutex_init(&adap->lock);
>>>> +     pm_runtime_enable(dev);
>>>> +
>>>> +     ret = c3_mipi_adap_subdev_init(adap);
>>>> +     if (ret < 0)
>>>> +             goto err_disable_runtime_pm;
>>>> +
>>>> +     ret = c3_mipi_adap_async_register(adap);
>>>> +     if (ret < 0)
>>>> +             goto err_deinit_subdev;
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_deinit_subdev:
>>>> +     c3_mipi_adap_subdev_deinit(adap);
>>>> +err_disable_runtime_pm:
>>>> +     pm_runtime_disable(dev);
>>>> +     mutex_destroy(&adap->lock);
>>>> +     return ret;
>>>> +};
>>>> +
>>>> +static void c3_mipi_adap_remove(struct platform_device *pdev)
>>>> +{
>>>> +     struct adap_device *adap = platform_get_drvdata(pdev);
>>>> +
>>>> +     c3_mipi_adap_async_unregister(adap);
>>>> +     c3_mipi_adap_subdev_deinit(adap);
>>>> +
>>>> +     pm_runtime_disable(&pdev->dev);
>>>> +     mutex_destroy(&adap->lock);
>>>> +};
>>>> +
>>>> +static const struct adap_info c3_mipi_adap_info = {
>>>> +     .clocks = {"vapb", "isp0"},
>>>> +     .clock_rates = {0, 400000000},
>>>> +     .clock_num = 2
>>>> +};
>>>> +
>>>> +static const struct of_device_id c3_mipi_adap_of_match[] = {
>>>> +     { .compatible = "amlogic,c3-mipi-adapter",
>>>> +       .data = &c3_mipi_adap_info },
>>>> +     { },
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, c3_mipi_adap_of_match);
>>>> +
>>>> +static struct platform_driver c3_mipi_adap_driver = {
>>>> +     .probe = c3_mipi_adap_probe,
>>>> +     .remove = c3_mipi_adap_remove,
>>>> +     .driver = {
>>>> +             .name = "c3-mipi-adapter",
>>>> +             .of_match_table = c3_mipi_adap_of_match,
>>>> +             .pm = &c3_mipi_adap_pm_ops,
>>>> +     },
>>>> +};
>>>> +
>>>> +module_platform_driver(c3_mipi_adap_driver);
>>>> +
>>>> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
>>>> +MODULE_DESCRIPTION("Amlogic C3 MIPI adapter");
>>>> +MODULE_LICENSE("GPL");
>>>>
>>>> --
>>>> 2.46.1
>>>>
>>>>
>>>>

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

* Re: [PATCH v3 7/9] media: platform: Add c3 ISP driver
  2024-09-18  6:07 ` [PATCH v3 7/9] media: platform: Add c3 ISP driver Keke Li via B4 Relay
  2024-11-07 16:03   ` Jacopo Mondi
@ 2024-11-08 10:47   ` Dan Scally
  2024-11-11  9:05     ` Keke Li
  1 sibling, 1 reply; 37+ messages in thread
From: Dan Scally @ 2024-11-08 10:47 UTC (permalink / raw)
  To: keke.li, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-media, devicetree, linux-kernel, kieran.bingham,
	laurent.pinchart

Hi Keke - sorry for the delay in review

On 18/09/2024 07:07, Keke Li via B4 Relay wrote:
> From: Keke Li <keke.li@amlogic.com>
>
> The C3 ISP supports multi-camera and muti-exposure
> high dynamic range (HDR). It brings together some
> advanced imaging technologies to provide good image quality.
> This driver mainly responsible for driving ISP pipeline
> to process raw image.
>
> Signed-off-by: Keke Li <keke.li@amlogic.com>
> ---
>   drivers/media/platform/amlogic/Kconfig             |   1 +
>   drivers/media/platform/amlogic/Makefile            |   1 +
>   drivers/media/platform/amlogic/c3-isp/Kconfig      |  17 +
>   drivers/media/platform/amlogic/c3-isp/Makefile     |  10 +
>   .../media/platform/amlogic/c3-isp/c3-isp-capture.c | 759 ++++++++++++++++++
>   .../media/platform/amlogic/c3-isp/c3-isp-common.h  | 327 ++++++++
>   .../media/platform/amlogic/c3-isp/c3-isp-core.c    | 675 ++++++++++++++++
>   drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c | 486 ++++++++++++
>   .../media/platform/amlogic/c3-isp/c3-isp-params.c  | 857 +++++++++++++++++++++
>   .../media/platform/amlogic/c3-isp/c3-isp-regs.h    | 683 ++++++++++++++++
>   .../media/platform/amlogic/c3-isp/c3-isp-resizer.c | 768 ++++++++++++++++++
>   .../media/platform/amlogic/c3-isp/c3-isp-stats.c   | 488 ++++++++++++
>   .../amlogic/c3-isp/include/uapi/c3-isp-config.h    | 537 +++++++++++++
>   13 files changed, 5609 insertions(+)
>
> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
> index df09717b28d0..ebda6b7edc2d 100644
> --- a/drivers/media/platform/amlogic/Kconfig
> +++ b/drivers/media/platform/amlogic/Kconfig
> @@ -2,6 +2,7 @@
>   
>   comment "Amlogic media platform drivers"
>   
> +source "drivers/media/platform/amlogic/c3-isp/Kconfig"
>   source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
>   source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>   source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
> index b370154b090c..d0d9363d4d8d 100644
> --- a/drivers/media/platform/amlogic/Makefile
> +++ b/drivers/media/platform/amlogic/Makefile
> @@ -1,5 +1,6 @@
>   # SPDX-License-Identifier: GPL-2.0-only
>   
> +obj-y += c3-isp/
>   obj-y += c3-mipi-adapter/
>   obj-y += c3-mipi-csi2/
>   obj-y += meson-ge2d/
> diff --git a/drivers/media/platform/amlogic/c3-isp/Kconfig b/drivers/media/platform/amlogic/c3-isp/Kconfig
> new file mode 100644
> index 000000000000..e317c1e81750
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/Kconfig
> @@ -0,0 +1,17 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config VIDEO_C3_ISP
> +	tristate "Amlogic C3 Image Signal Processor (ISP) driver"
> +	depends on ARCH_MESON || COMPILE_TEST
> +	depends on VIDEO_DEV
> +	depends on OF
> +	select MEDIA_CONTROLLER
> +	select V4L2_FWNODE
> +	select VIDEO_V4L2_SUBDEV_API
> +	select VIDEOBUF2_DMA_CONTIG
> +	help
> +	  Video4Linux2 driver for Amlogic C3 ISP pipeline.
> +	  C3 ISP pipeline mainly for processing raw image
> +	  and output result to memory.
> +
> +	  To compile this driver as a module choose m here.
> diff --git a/drivers/media/platform/amlogic/c3-isp/Makefile b/drivers/media/platform/amlogic/c3-isp/Makefile
> new file mode 100644
> index 000000000000..b1b064170b57
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/Makefile
> @@ -0,0 +1,10 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +c3-isp-objs := c3-isp-dev.o \
> +	       c3-isp-params.o \
> +	       c3-isp-stats.o \
> +	       c3-isp-capture.o \
> +	       c3-isp-core.o \
> +	       c3-isp-resizer.o
> +
> +obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
> new file mode 100644
> index 000000000000..ee9a7a17a203
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
> @@ -0,0 +1,759 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +
> +static const struct c3_isp_capture_format cap_formats[] = {
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_Y8_1X8,
> +		.fourcc = V4L2_PIX_FMT_GREY,
> +		.depth = 8,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
> +		.fourcc = V4L2_PIX_FMT_NV12,
> +		.depth = 12,
> +	}, {
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
> +		.fourcc = V4L2_PIX_FMT_NV21,
> +		.depth = 12,
> +	}, {
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> +		.fourcc = V4L2_PIX_FMT_NV16,
> +		.depth = 16,
> +	}, {
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> +		.fourcc = V4L2_PIX_FMT_NV61,
> +		.depth = 16,
> +	},
> +};
> +
> +/* Hardware configuration */
> +
> +/* Set the address of wrmifx3(write memory interface) */
> +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
> +{
> +	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> +	struct c3_isp_vb2_buffer *buff = cap->buff;
> +	u32 offset;
> +
> +	c3_isp_write(cap->isp,
> +		     C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
> +		     WRMIFX3_CH0_BADDR(buff->paddr));
> +
> +	if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
> +	    pix->pixelformat == V4L2_PIX_FMT_NV21 ||
> +	    pix->pixelformat == V4L2_PIX_FMT_NV16 ||
> +	    pix->pixelformat == V4L2_PIX_FMT_NV61) {
> +		offset = pix->width * pix->height;
> +		c3_isp_write(cap->isp,
> +			     C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
> +			     WRMIFX3_CH1_BADDR(buff->paddr + offset));
> +	}
> +}
> +
> +static void c3_isp_cap_output_size(struct c3_isp_capture *cap)
> +{
> +	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> +
> +	c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
> +			   DISP_OUT_VSIZE_MASK, pix->height);
> +	c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
> +			   DISP_OUT_HSIZE_MASK, pix->width << DISP_OUT_HSIZE_SHIFT);
> +}
> +
> +static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
> +{
> +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> +	u32 stride;
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
> +
> +	/* Grey has 1 plane*/
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_PLANE_MASK,
> +			   WRMIFX3_FMT_MTX_PLANE_X1 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
> +
> +	/* Set Y only as output format */
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MODE_OUT_MASK,
> +			   WRMIFX3_FMT_MODE_OUT_Y_ONLY << WRMIFX3_FMT_MODE_OUT_SHIFT);
> +
> +	/* The unit of stride is 128 bits */
> +	stride = DIV_ROUND_UP(fmt->width * 8, 128);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> +			   WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> +			   WRMIFX3_CH0_PIX_BITS_MODE_MASK,
> +			   WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
> +}
> +
> +static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, u32 swap_uv)
> +{
> +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> +	u32 stride;
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_UV_SWAP_MASK,
> +			   swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
> +
> +	/* NV12 or NV21 has 2 planes*/
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_PLANE_MASK,
> +			   WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
> +
> +	/* Set YUV420 as output format */
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MODE_OUT_MASK,
> +			   WRMIFX3_FMT_MODE_OUT_YUV420 << WRMIFX3_FMT_MODE_OUT_SHIFT);
> +
> +	/* The unit of stride is 128 bits */
> +	stride = DIV_ROUND_UP(fmt->width * 8, 128);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> +			   WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
> +			   WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> +			   WRMIFX3_CH0_PIX_BITS_MODE_MASK,
> +			   WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
> +			   WRMIFX3_CH1_PIX_BITS_MODE_MASK,
> +			   WRMIFX3_CH1_PIX_BITS_16BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
> +}
> +
> +static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, u32 swap_uv)
> +{
> +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> +	u32 stride;
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_16BIT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_UV_SWAP_MASK,
> +			   swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
> +
> +	/* NV16 or NV61 has 2 planes*/
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MTX_PLANE_MASK,
> +			   WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
> +
> +	/* Set YUV422 as output format */
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> +			   WRMIFX3_FMT_MODE_OUT_MASK,
> +			   WRMIFX3_FMT_MODE_OUT_YUV422 << WRMIFX3_FMT_MODE_OUT_SHIFT);
> +
> +	/* The unit of stride is 128 bits */
> +	stride = DIV_ROUND_UP(fmt->width * 16, 128);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> +			   WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
> +			   WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> +			   WRMIFX3_CH0_PIX_BITS_MODE_MASK,
> +			   WRMIFX3_CH0_PIX_BITS_16BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
> +			   WRMIFX3_CH1_PIX_BITS_MODE_MASK,
> +			   WRMIFX3_CH1_PIX_BITS_32BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
> +}
> +
> +static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
> +{
> +	struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> +
> +	if (fmt->pixelformat == V4L2_PIX_FMT_GREY) {
> +		c3_isp_cap_wrmifx3_grey(cap);
> +	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV12) {
> +		c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
> +	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV21) {
> +		c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
> +	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV16) {
> +		c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
> +	} else if (fmt->pixelformat == V4L2_PIX_FMT_NV61) {
> +		c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
> +	} else {
> +		dev_err(cap->isp->dev, "Invalid pixel format: 0x%x\n", fmt->pixelformat);
> +		return;
Shouldn't this return an error code and prevent the video device from being started? It's probably 
not relevant right now, but I think it'd be easier to catch mistakes if the driver is extended in 
the future.
> +	}
> +
> +	c3_isp_write(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id),
> +		     WRMIFX3_FMT_HSIZE(fmt->width) | WRMIFX3_FMT_VSIZE(fmt->height));
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
> +			   WRMIFX3_WIN_LUMA_HEND_MASK,
> +			   WRMIFX3_WIN_LUMA_HEND(fmt->width) << WRMIFX3_WIN_LUMA_HEND_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
> +			   WRMIFX3_WIN_LUMA_VEND_MASK,
> +			   WRMIFX3_WIN_LUMA_VEND(fmt->height) << WRMIFX3_WIN_LUMA_VEND_SHIFT);
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_HSIZE, cap->id),
> +			   WRMIFX3_CROP_HEND_MASK,
> +			   WRMIFX3_CROP_HEND(fmt->width) << WRMIFX3_CROP_HEND_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_VSIZE, cap->id),
> +			   WRMIFX3_CROP_VEND_MASK,
> +			   WRMIFX3_CROP_VEND(fmt->height) << WRMIFX3_CROP_VEND_SHIFT);
> +
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
> +			   WRMIFX3_WIN_CHROM_HEND_MASK,
> +			   WRMIFX3_WIN_CHROM_HEND(fmt->width) << WRMIFX3_WIN_CHROM_HEND_SHIFT);
> +	c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
> +			   WRMIFX3_WIN_CHROM_VEND_MASK,
> +			   WRMIFX3_WIN_CHROM_VEND(fmt->height) << WRMIFX3_WIN_CHROM_VEND_SHIFT);
Maybe these shift macros can just be part of the value macro, might make things a bit tidier here. 
Not a big deal though; up to you.
> +}
> +
> +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)
> +{
> +	cap->buff = list_first_entry_or_null(&cap->pending,
> +					     struct c3_isp_vb2_buffer, list);
> +	if (cap->buff) {
> +		c3_isp_cap_wrmifx3_buff(cap);
> +		list_del(&cap->buff->list);
> +	}
> +}


Either this needs to hold cap->buff_lock or the call site in c3_isp_cap_start() does.

> +
> +static void c3_isp_cap_start(struct c3_isp_capture *cap)
> +{
> +	c3_isp_cap_cfg_buff(cap);
> +
> +	c3_isp_cap_output_size(cap);
> +	c3_isp_cap_wrmifx3_size(cap);
> +
> +	c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN,
> +			   TOP_WRMIF_EN(cap->id), TOP_WRMIF_EN(cap->id));
> +
> +	cap->is_streaming = true;
> +}


I would just drop the variable here and rely on c3_isp_cap_return_buffers() having nulled 
cap->buffer and emptied the queues to replace its functionality.

> +
> +static void c3_isp_cap_stop(struct c3_isp_capture *cap)
> +{
> +	cap->is_streaming = false;
> +
> +	c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, TOP_WRMIF_EN(cap->id), 0);
> +}
> +
> +static int c3_isp_cap_done(struct c3_isp_capture *cap)
> +{
> +	struct c3_isp_vb2_buffer *buff = cap->buff;
> +	unsigned long flags;
> +
> +	if (!cap->is_streaming)
> +		return -EINVAL;


Return value is unchecked - it can be a void function.

> +
> +	spin_lock_irqsave(&cap->buff_lock, flags);
scoped_guard() - similar to guard() but for a more limited scope. These cleanup functions are really 
handy
> +
> +	if (buff) {
> +		buff->vb.sequence = cap->isp->frm_sequence;
> +		buff->vb.vb2_buf.timestamp = ktime_get();
> +		buff->vb.field = V4L2_FIELD_NONE;
> +		vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +	}
> +
> +	c3_isp_cap_cfg_buff(cap);
> +
> +	spin_unlock_irqrestore(&cap->buff_lock, flags);
> +
> +	return 0;
> +}
> +
> +/* V4L2 video operations */
> +
> +static const struct c3_isp_capture_format
> +*c3_cap_find_fmt(u32 fourcc)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
> +		if (cap_formats[i].fourcc == fourcc)
> +			return &cap_formats[i];
> +	}
> +
> +	return NULL;
> +}
> +
> +static void c3_cap_try_fmt(struct c3_isp_capture *cap,
> +			   struct v4l2_pix_format *pix)
> +{
> +	const struct c3_isp_capture_format *fmt;
> +
> +	fmt = c3_cap_find_fmt(pix->pixelformat);
> +	if (!fmt)
> +		fmt = &cap_formats[0];
> +
> +	pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> +	pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> +	pix->pixelformat = fmt->fourcc;
> +	pix->field = V4L2_FIELD_NONE;
> +	pix->colorspace = V4L2_COLORSPACE_SRGB;
> +	pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
> +	pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> +
> +	/* ISP hardware requires 16 bytes alignment */
> +	pix->bytesperline = ALIGN(pix->width, 16);
> +	pix->sizeimage = pix->bytesperline * pix->height * fmt->depth / 8;
> +}


v4l2_fill_pixfmt() might do some of this for you, though I'm not sure if it'll handle the alignment.

> +
> +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
> +				      enum vb2_buffer_state state)
> +{
> +	unsigned long flags;
> +	struct c3_isp_vb2_buffer *buff;
> +
> +	spin_lock_irqsave(&cap->buff_lock, flags);
guard(spinlock_irqsave)(&cap->buff_lock);
> +
> +	if (cap->buff) {
> +		vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
> +		cap->buff = NULL;
> +	}
> +
> +	while (!list_empty(&cap->pending)) {
> +		buff = list_first_entry(&cap->pending,
> +					struct c3_isp_vb2_buffer, list);
> +		list_del(&buff->list);
> +		vb2_buffer_done(&buff->vb.vb2_buf, state);
> +	}
> +
> +	spin_unlock_irqrestore(&cap->buff_lock, flags);
> +}
> +
> +static int c3_isp_cap_querycap(struct file *file, void *fh,
> +			       struct v4l2_capability *cap)
> +{
> +	strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
> +	strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
> +
> +	return 0;
> +}
> +
> +static int c3_isp_cap_enum_fmt(struct file *file, void *fh,
> +			       struct v4l2_fmtdesc *f)
> +{
> +	const struct c3_isp_capture_format *fmt;
> +	unsigned int index = 0;
> +	unsigned int i;
> +
> +	if (!f->mbus_code) {
> +		if (f->index >= ARRAY_SIZE(cap_formats))
> +			return -EINVAL;
> +
> +		fmt = &cap_formats[f->index];
> +		f->pixelformat = fmt->fourcc;
> +		return 0;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
> +		fmt = &cap_formats[i];
> +		if (f->mbus_code != fmt->mbus_code)
> +			continue;
> +
> +		if (index++ == f->index) {
> +			f->pixelformat = cap_formats[i].fourcc;
> +			return 0;
> +		}
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int c3_isp_cap_g_fmt(struct file *file, void *fh,
> +			    struct v4l2_format *f)
> +{
> +	struct c3_isp_capture *cap = video_drvdata(file);
> +
> +	f->fmt.pix = cap->vfmt.fmt.pix;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_cap_s_fmt(struct file *file, void *fh,
> +			    struct v4l2_format *f)
> +{
> +	struct c3_isp_capture *cap = video_drvdata(file);
> +
> +	c3_cap_try_fmt(cap, &f->fmt.pix);
> +	cap->vfmt = *f;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_cap_try_fmt(struct file *file, void *fh,
> +			      struct v4l2_format *f)
> +{
> +	struct c3_isp_capture *cap = video_drvdata(file);
> +
> +	c3_cap_try_fmt(cap, &f->fmt.pix);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
> +				   struct v4l2_frmsizeenum *fsize)
> +{
> +	const struct c3_isp_capture_format *fmt;
> +
> +	if (fsize->index)
> +		return -EINVAL;
> +
> +	fmt = c3_cap_find_fmt(fsize->pixel_format);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> +	fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
> +	fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
> +	fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
> +	fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
> +	fsize->stepwise.step_width = 2;
> +	fsize->stepwise.step_height = 2;
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
> +	.vidioc_querycap		= c3_isp_cap_querycap,
> +	.vidioc_enum_fmt_vid_cap	= c3_isp_cap_enum_fmt,
> +	.vidioc_g_fmt_vid_cap		= c3_isp_cap_g_fmt,
> +	.vidioc_s_fmt_vid_cap		= c3_isp_cap_s_fmt,
> +	.vidioc_try_fmt_vid_cap		= c3_isp_cap_try_fmt,
> +	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
> +	.vidioc_querybuf		= vb2_ioctl_querybuf,
> +	.vidioc_qbuf			= vb2_ioctl_qbuf,
> +	.vidioc_expbuf			= vb2_ioctl_expbuf,
> +	.vidioc_dqbuf			= vb2_ioctl_dqbuf,
> +	.vidioc_prepare_buf		= vb2_ioctl_prepare_buf,
> +	.vidioc_create_bufs		= vb2_ioctl_create_bufs,
> +	.vidioc_streamon		= vb2_ioctl_streamon,
> +	.vidioc_streamoff		= vb2_ioctl_streamoff,
> +	.vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
> +	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
> +	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
> +};
> +
> +static const struct v4l2_file_operations isp_cap_v4l2_fops = {
> +	.open = v4l2_fh_open,
> +	.release = vb2_fop_release,
> +	.poll = vb2_fop_poll,
> +	.unlocked_ioctl = video_ioctl2,
> +	.mmap = vb2_fop_mmap,
> +};
> +
> +static int c3_isp_cap_link_validate(struct media_link *link)
> +{
> +	struct video_device *vdev =
> +		media_entity_to_video_device(link->sink->entity);
> +	struct v4l2_subdev *sd =
> +		media_entity_to_v4l2_subdev(link->source->entity);
> +	struct c3_isp_capture *cap = video_get_drvdata(vdev);
> +	struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
> +	struct v4l2_subdev_format src_fmt = {
> +		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
> +		.pad = link->source->index,
> +	};
> +	const struct c3_isp_capture_format *cap_fmt =
> +				c3_cap_find_fmt(pix_fmt->pixelformat);
> +	int ret;
> +
> +	ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
> +	if (ret)
> +		return ret;
> +
> +	if (src_fmt.format.width != pix_fmt->width ||
> +	    src_fmt.format.height != pix_fmt->height ||
> +	    src_fmt.format.code != cap_fmt->mbus_code) {
> +		dev_err(cap->isp->dev,
> +			"link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n",
> +			link->source->entity->name, link->source->index,
> +			link->sink->entity->name, link->sink->index,
> +			src_fmt.format.code, src_fmt.format.width,
> +			src_fmt.format.height, cap_fmt->mbus_code,
> +			pix_fmt->width, pix_fmt->height);
> +
> +		return -EPIPE;
> +	};
> +
> +	return 0;
> +}
> +
> +static const struct media_entity_operations isp_cap_entity_ops = {
> +	.link_validate = c3_isp_cap_link_validate,
> +};
> +
> +static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
> +				  unsigned int *num_buffers,
> +				  unsigned int *num_planes,
> +				  unsigned int sizes[],
> +				  struct device *alloc_devs[])
> +{
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> +	struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> +
> +	if (*num_planes) {
> +		if (*num_planes != 1)
> +			return -EINVAL;
> +
> +		if (sizes[0] < pix->sizeimage)
> +			return -EINVAL;
> +	} else {
> +		*num_planes = 1;
> +		sizes[0] = pix->sizeimage;
> +	}
> +
> +	return 0;
> +}
> +
> +static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_vb2_buffer *buf =
> +			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&cap->buff_lock, flags);
guard(spinlock_irqsave)(&cap->buff_lock)
> +
> +	list_add_tail(&buf->list, &cap->pending);
> +
> +	spin_unlock_irqrestore(&cap->buff_lock, flags);
> +}
> +
> +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
> +{
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned int size = cap->vfmt.fmt.pix.sizeimage;
> +
> +	if (vb2_plane_size(vb, 0) < size) {
> +		dev_err(cap->isp->dev,
> +			"User buffer too small (%ld < %u)\n",
> +			vb2_plane_size(vb, 0), size);
> +		return -EINVAL;
> +	}
> +
> +	vb2_set_plane_payload(vb, 0, size);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_vb2_buffer *buf =
> +		container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> +
> +	buf->vaddr = vb2_plane_vaddr(vb, 0);
> +	buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
> +
> +	memset(buf->vaddr, 0, cap->vfmt.fmt.pix.sizeimage);
Hm, why the memset()? The ISP is going to overwrite the whole buffer anyway surely? Not sure that 
you need the vaddr in this video device at all actually.
> +
> +	return 0;
> +}
> +
> +static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
> +				      unsigned int count)
> +{
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> +	int ret;
> +
> +	guard(mutex)(&cap->isp->lock);
> +
> +	ret = pm_runtime_resume_and_get(cap->isp->dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
> +	if (ret) {
> +		dev_err(cap->isp->dev,
> +			"Failed to start cap%u pipeline: %d\n", cap->id, ret);
> +		goto err_pm_put;
> +	}
> +
> +	if (c3_isp_pipeline_ready(cap->isp)) {
> +		ret = v4l2_subdev_enable_streams(&cap->isp->core.sd,
> +						 C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +						 BIT(0));
> +		if (ret)
> +			goto err_pipeline_stop;
> +	}
> +
> +	c3_isp_rsz_start(cap->rsz);
Bit surprised there's no .enable_streams() call for a resizer subdevice...
> +	c3_isp_cap_start(cap);
> +
> +	return 0;
> +
> +err_pipeline_stop:
> +	video_device_pipeline_stop(&cap->vdev);
> +err_pm_put:
> +	pm_runtime_put(cap->isp->dev);
> +	c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
> +	return ret;
> +}
> +
> +static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
> +{
> +	struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> +
> +	guard(mutex)(&cap->isp->lock);
> +
> +	c3_isp_cap_stop(cap);
> +	c3_isp_rsz_stop(cap->rsz);
> +	c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
> +
> +	if (cap->isp->pipe.start_count == 1)
if (v4l2_subdev_is_streaming(&cap->isp->core.sd))
> +		v4l2_subdev_disable_streams(&cap->isp->core.sd,
> +					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +					    BIT(0));
> +
> +	video_device_pipeline_stop(&cap->vdev);
> +	pm_runtime_put(cap->isp->dev);
> +}
> +
> +static const struct vb2_ops isp_video_vb2_ops = {
> +	.queue_setup = c3_isp_vb2_queue_setup,
> +	.buf_queue = c3_isp_vb2_buf_queue,
> +	.buf_prepare = c3_isp_vb2_buf_prepare,
> +	.buf_init = c3_isp_vb2_buf_init,
> +	.wait_prepare = vb2_ops_wait_prepare,
> +	.wait_finish = vb2_ops_wait_finish,
> +	.start_streaming = c3_isp_vb2_start_streaming,
> +	.stop_streaming = c3_isp_vb2_stop_streaming,
> +};
> +
> +static int c3_isp_register_capture(struct c3_isp_capture *cap)
> +{
> +	struct video_device *vdev = &cap->vdev;
> +	struct vb2_queue *vb2_q = &cap->vb2_q;
> +	int ret;
> +
> +	snprintf(vdev->name, sizeof(vdev->name), "isp-cap%u", cap->id);
> +	vdev->fops = &isp_cap_v4l2_fops;
> +	vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
> +	vdev->v4l2_dev = &cap->isp->v4l2_dev;
> +	vdev->entity.ops = &isp_cap_entity_ops;
> +	vdev->lock = &cap->lock;
> +	vdev->minor = -1;
> +	vdev->queue = vb2_q;
> +	vdev->release = video_device_release_empty;
> +	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> +	vdev->vfl_dir = VFL_DIR_RX;
> +	video_set_drvdata(vdev, cap);
> +
> +	vb2_q->drv_priv = cap;
> +	vb2_q->mem_ops = &vb2_dma_contig_memops;
> +	vb2_q->ops = &isp_video_vb2_ops;
> +	vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +	vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
> +	vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
> +	vb2_q->dev = cap->isp->dev;
> +	vb2_q->lock = &cap->lock;
> +	vb2_q->min_queued_buffers = 2;
> +
> +	ret = vb2_queue_init(vb2_q);
> +	if (ret < 0)
> +		goto err_destroy;
> +
> +	cap->pad.flags = MEDIA_PAD_FL_SINK;
> +	ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
> +	if (ret < 0)
> +		goto err_queue_release;
> +
> +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +	if (ret < 0) {
> +		dev_err(cap->isp->dev,
> +			"Failed to register %s: %d\n", vdev->name, ret);
> +		goto err_entity_cleanup;
> +	}
> +
> +	return 0;
> +
> +err_entity_cleanup:
> +	media_entity_cleanup(&vdev->entity);
> +err_queue_release:
> +	vb2_queue_release(vb2_q);
> +err_destroy:
> +	mutex_destroy(&cap->lock);
> +	return ret;
> +}
> +
> +int c3_isp_captures_register(struct c3_isp_device *isp)
> +{
> +	int ret;
> +	unsigned int i;
> +	struct c3_isp_capture *cap;
> +
> +	for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
> +		cap = &isp->caps[i];
> +		memset(cap, 0, sizeof(*cap));
> +
> +		cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
> +		cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
> +		cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
> +		cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
> +		cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
> +
> +		c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
> +
> +		cap->id = i;
> +		if (cap->id == C3_ISP_CAP_DEV_0)
> +			cap->rsz = &isp->resizers[C3_ISP_RSZ_0];
> +		else if (cap->id == C3_ISP_CAP_DEV_1)
> +			cap->rsz = &isp->resizers[C3_ISP_RSZ_1];
> +		else
> +			cap->rsz = &isp->resizers[C3_ISP_RSZ_2];
I think cap->rsz = &isp->resizers[i]; is fine personally, but it's up to you
> +
> +		cap->isp = isp;
> +		INIT_LIST_HEAD(&cap->pending);
> +		spin_lock_init(&cap->buff_lock);
> +		mutex_init(&cap->lock);
> +
> +		ret = c3_isp_register_capture(cap);
> +		if (ret) {
> +			cap->isp = NULL;
> +			mutex_destroy(&cap->lock);
> +			c3_isp_captures_unregister(isp);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +void c3_isp_captures_unregister(struct c3_isp_device *isp)
> +{
> +	unsigned int i;
> +	struct c3_isp_capture *cap;
> +
> +	for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
> +		cap = &isp->caps[i];
> +
> +		if (!cap->isp)
> +			continue;
> +		vb2_queue_release(&cap->vb2_q);
> +		media_entity_cleanup(&cap->vdev.entity);
> +		video_unregister_device(&cap->vdev);
> +		mutex_destroy(&cap->lock);
> +	}
> +}
> +
> +void c3_isp_captures_done(struct c3_isp_device *isp)
> +{
> +	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
> +	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
> +	c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
> +}
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
> new file mode 100644
> index 000000000000..19f2a3bc29c9
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
> @@ -0,0 +1,327 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#ifndef __C3_ISP_COMMON_H__
> +#define __C3_ISP_COMMON_H__
> +
> +#include <linux/clk.h>
> +
> +#include <media/media-device.h>
> +#include <media/videobuf2-core.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#define C3_ISP_DRIVER_NAME            "c3-isp"
> +#define C3_ISP_CLOCK_NUM_MAX          3
> +
> +#define C3_ISP_CORE_DEF_SINK_PAD_FMT  MEDIA_BUS_FMT_SRGGB10_1X10
> +#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUYV8_1_5X8
> +#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUYV8_1_5X8
> +#define C3_ISP_DEFAULT_WIDTH          1920
> +#define C3_ISP_DEFAULT_HEIGHT         1080
> +#define C3_ISP_MAX_WIDTH              2888
> +#define C3_ISP_MAX_HEIGHT             2240
> +#define C3_ISP_MIN_WIDTH              160
> +#define C3_ISP_MIN_HEIGHT             120
> +
> +#define C3_DISP_INTER                 0x400
> +#define C3_DISP_REG(base, id)         ((base) + (id) * C3_DISP_INTER)
> +#define C3_WRMIFX3_INTER              0x100
> +#define C3_WRMIFX3_REG(base, id)      ((base) + (id) * C3_WRMIFX3_INTER)
> +#define C3_PPS_TAP4_S11_H_NUM         33
> +#define C3_PPS_LUT_CTYPE_0            0
> +#define C3_PPS_LUT_CTYPE_2            2
> +#define C3_SCALE_EN                   1
> +#define C3_SCALE_DIS                  0
> +
> +#define C3_ISP_PHASE_OFFSET_0         0
> +#define C3_ISP_PHASE_OFFSET_1         1
> +#define C3_ISP_PHASE_OFFSET_NONE      0xff
> +
> +enum c3_isp_core_pads {
> +	C3_ISP_CORE_PAD_SINK_VIDEO,
> +	C3_ISP_CORE_PAD_SINK_PARAMS,
> +	C3_ISP_CORE_PAD_SOURCE_STATS,
> +	C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +	C3_ISP_CORE_PAD_MAX
> +};
> +
> +enum c3_isp_resizer_ids {
> +	C3_ISP_RSZ_0,
> +	C3_ISP_RSZ_1,
> +	C3_ISP_RSZ_2,
> +	C3_ISP_NUM_RSZ
> +};
> +
> +enum c3_isp_resizer_pads {
> +	C3_ISP_RESIZER_PAD_SINK,
> +	C3_ISP_RESIZER_PAD_SOURCE,
> +	C3_ISP_RESIZER_PAD_MAX
> +};
> +
> +enum c3_isp_cap_devs {
> +	C3_ISP_CAP_DEV_0,
> +	C3_ISP_CAP_DEV_1,
> +	C3_ISP_CAP_DEV_2,
> +	C3_ISP_NUM_CAP_DEVS
> +};
> +
> +/**
> + * struct c3_isp_pps_io_size - isp scaler input and output size
> + *
> + * @thsize: input horizontal size of after preprocessing
> + * @tvsize: input vertical size of after preprocessing
> + * @ohsize: output horizontal size
> + * @ovsize: output vertical size
> + * @ihsize: input horizontal size
> + * @max_hsize: maximum horizontal size
> + */
> +struct c3_isp_pps_io_size {
> +	u32 thsize;
> +	u32 tvsize;
> +	u32 ohsize;
> +	u32 ovsize;
> +	u32 ihsize;
> +	u32 max_hsize;
> +};
> +
> +/**
> + * @mbus_code: the mbus code
> + * @pads: save the pad flag of this mbus_code
"bitmask detailing valid pads for this mbus_code" perhaps?
> + * @xofst: horizontal phase offset of hardware
> + * @yofst: vertical phase offset of hardware
> + */
> +struct c3_isp_mbus_format_info {
> +	u32 mbus_code;
> +	u32 pads;
> +	u8 xofst;
> +	u8 yofst;
> +};
> +
> +/**
> + * @mbus_code: the mbus code
> + * @fourcc: pixel format
> + * @depth: pixel width
> + */
> +struct c3_isp_capture_format {
> +	u32 mbus_code;
> +	u32 fourcc;
> +	u8 depth;
> +};
> +
> +/**
> + * struct c3_isp_vb2_buffer - A container of vb2 buffer
> + *
> + * @vb: vb2 buffer
> + * @vaddr: buffer virtual address
> + * @paddr: buffer physical address
> + * @list: entry of the buffer in the queue
> + */
> +struct c3_isp_vb2_buffer {
> +	struct vb2_v4l2_buffer vb;
> +	void *vaddr;
> +	dma_addr_t paddr;
> +	struct list_head list;
> +};
> +
> +/**
> + * struct c3_isp_core - ISP core subdev
> + *
> + * @sd: ISP sub-device
> + * @pads: ISP sub-device pads
> + * @src_sd: source sub-device
> + * @isp: pointer to c3_isp_device
> + * @src_sd_pad: source sub-device pad
> + */
> +struct c3_isp_core {
> +	struct v4l2_subdev sd;
> +	struct media_pad pads[C3_ISP_CORE_PAD_MAX];
> +	struct v4l2_subdev *src_sd;
> +	u16 src_sd_pad;
> +	struct c3_isp_device *isp;
> +};
> +
> +/**
> + * struct c3_isp_resizer - ISP resizer subdev
> + *
> + * @id: resizer id
> + * @sd: resizer sub-device
> + * @pads: resizer sub-device pads
> + * @isp: pointer to c3_isp_device
> + * @cap: pointer to c3_isp_capture
> + */
> +struct c3_isp_resizer {
> +	enum c3_isp_resizer_ids id;
> +	struct v4l2_subdev sd;
> +	struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
> +	struct c3_isp_device *isp;
> +	struct c3_isp_capture *cap;
> +};
> +
> +/**
> + * struct c3_isp_stats - ISP statistics device
> + *
> + * @vb2_q: vb2 buffer queue
> + * @vdev: video node
> + * @vfmt: v4l2_format of the metadata format
> + * @pad: media pad
> + * @lock: protects vb2_q, vdev
> + * @is_streaming: stats status
> + * @isp: pointer to c3_isp_device
> + * @buff: in use buffer
> + * @buff_lock: protects stats buffer
> + * @pending: stats buffer list head
> + */
> +struct c3_isp_stats {
> +	struct vb2_queue vb2_q;
> +	struct video_device vdev;
> +	struct v4l2_format vfmt;
> +	struct media_pad pad;
> +
> +	struct mutex lock; /* Protects vb2_q, vdev */
> +	bool is_streaming;
> +	struct c3_isp_device *isp;
> +
> +	struct c3_isp_vb2_buffer *buff;
> +	spinlock_t buff_lock; /* Protects stream buffer */
> +	struct list_head pending;
> +};
> +
> +/**
> + * struct c3_isp_params - ISP parameters device
> + *
> + * @vb2_q: vb2 buffer queue
> + * @vdev: video node
> + * @vfmt: v4l2_format of the metadata format
> + * @pad: media pad
> + * @lock: protects vb2_q, vdev
> + * @isp: pointer to c3_isp_device
> + * @buff: in use buffer
> + * @buff_lock: protects stats buffer
> + * @pending: stats buffer list head
> + */
> +struct c3_isp_params {
> +	struct vb2_queue vb2_q;
> +	struct video_device vdev;
> +	struct v4l2_format vfmt;
> +	struct media_pad pad;
> +
> +	struct mutex lock; /* Protects vb2_q, vdev */
> +	struct c3_isp_device *isp;
> +
> +	struct c3_isp_vb2_buffer *buff;
> +	spinlock_t buff_lock; /* Protects stream buffer */
> +	struct list_head pending;
> +};
> +
> +/**
> + * struct c3_isp_capture - ISP capture device
> + *
> + * @id: capture device ID
> + * @vb2_q: vb2 buffer queue
> + * @vdev: video node
> + * @vfmt: v4l2_format of the capture format
> + * @pad: media pad
> + * @lock: protects vb2_q, vdev
> + * @is_streaming: capture device status
> + * @isp: pointer to c3_isp_device
> + * @rsz: pointer to c3_isp_resizer
> + * @buff: in use buffer
> + * @buff_lock: protects capture buffer
> + * @pending: capture buffer list head
> + */
> +struct c3_isp_capture {
> +	enum c3_isp_cap_devs id;
> +	struct vb2_queue vb2_q;
> +	struct video_device vdev;
> +	struct v4l2_format vfmt;
> +	struct media_pad pad;
> +
> +	struct mutex lock; /* Protects vb2_q, vdev */
> +	bool is_streaming;
> +	struct c3_isp_device *isp;
> +	struct c3_isp_resizer *rsz;
> +
> +	struct c3_isp_vb2_buffer *buff;
> +	spinlock_t buff_lock; /* Protects stream buffer */
> +	struct list_head pending;
> +};
> +
> +/**
> + * struct c3_isp_info - ISP information
> + *
> + * @clocks: array of ISP clock names
> + * @clock_rates: array of ISP clock rate
> + * @clock_num: actual clock number
> + */
> +struct c3_isp_info {
> +	char *clocks[C3_ISP_CLOCK_NUM_MAX];
> +	u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
> +	u32 clock_num;
> +};
> +
> +/**
> + * struct c3_isp_device - ISP platform device
> + *
> + * @dev: pointer to the struct device
> + * @base: base register address
> + * @clks: array of clocks
> + * @notifier: notifier to register on the v4l2-async API
> + * @v4l2_dev: v4l2_device variable
> + * @media_dev: media device variable
> + * @pipe: media pipeline
> + * @core: ISP core subdev
> + * @resizer: ISP resizer subdev
> + * @stats: ISP stats device
> + * @params: ISP params device
> + * @caps: array of ISP capture device
> + * @frm_sequence: used to record frame id
> + * @lock: protect ISP device
> + * @info: version-specific ISP information
> + */
> +struct c3_isp_device {
> +	struct device *dev;
> +	void __iomem *base;
> +	struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
> +
> +	struct v4l2_async_notifier notifier;
> +	struct v4l2_device v4l2_dev;
> +	struct media_device media_dev;
> +	struct media_pipeline pipe;
> +
> +	struct c3_isp_core core;
> +	struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
> +	struct c3_isp_stats stats;
> +	struct c3_isp_params params;
> +	struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
> +
> +	u32 frm_sequence;
> +	struct mutex lock; /* Protect ISP device */
> +	const struct c3_isp_info *info;
> +};
> +
> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp);
> +
> +int c3_isp_core_register(struct c3_isp_device *isp);
> +void c3_isp_core_unregister(struct c3_isp_device *isp);
> +int c3_isp_resizers_register(struct c3_isp_device *isp);
> +void c3_isp_resizers_unregister(struct c3_isp_device *isp);
> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz);
> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz);
> +int c3_isp_captures_register(struct c3_isp_device *isp);
> +void c3_isp_captures_unregister(struct c3_isp_device *isp);
> +void c3_isp_captures_done(struct c3_isp_device *isp);
> +int c3_isp_stats_register(struct c3_isp_device *isp);
> +void c3_isp_stats_unregister(struct c3_isp_device *isp);
> +int c3_isp_stats_done(struct c3_isp_device *isp);
> +int c3_isp_params_register(struct c3_isp_device *isp);
> +void c3_isp_params_unregister(struct c3_isp_device *isp);
> +int c3_isp_params_done(struct c3_isp_device *isp);
> +
> +#endif
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
> new file mode 100644
> index 000000000000..d3672aff9fd2
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
> @@ -0,0 +1,675 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/pm_runtime.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +#include "include/uapi/c3-isp-config.h"
> +
> +#define C3_ISP_CORE_SUBDEV_NAME        "isp-core"
> +
> +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
> +	/* RAW formats */
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SGBRG10_1X10,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SBGGR12_1X12,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SGBRG12_1X12,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> +		.yofst		= C3_ISP_PHASE_OFFSET_1,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SGRBG12_1X12,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_0,
> +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_SRGGB12_1X12,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_1,
> +		.yofst		= C3_ISP_PHASE_OFFSET_0,
> +	},
> +	/* YUV formats */
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_Y8_1X8,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_YUYV8_1_5X8,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_YUYV8_2X8,
> +		.pads		= BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> +	},
> +};
> +
> +static const struct c3_isp_mbus_format_info
> +*core_find_format_by_code(u32 code, u32 pad)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
> +		const struct c3_isp_mbus_format_info *info =
> +			&c3_isp_core_mbus_formats[i];
> +
> +		if (info->mbus_code == code && info->pads & BIT(pad))
> +			return info;
> +	}
> +
> +	return NULL;
> +}
> +
> +static const struct c3_isp_mbus_format_info
> +*core_find_format_by_index(u32 index, u32 pad)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
> +		const struct c3_isp_mbus_format_info *info =
> +			&c3_isp_core_mbus_formats[i];
> +
> +		if (!(info->pads & BIT(pad)))
> +			continue;
> +
> +		if (!index)
> +			return info;
> +
> +		index--;
> +	}
> +
> +	return NULL;
> +}
> +
> +static void c3_isp_core_enable(struct c3_isp_device *isp)
> +{
> +	/* Select the line sync signal */
> +	c3_isp_update_bits(isp, ISP_TOP_IRQ_LINE_THRD,
> +			   TOP_IRQ_DIN_HSYNC, TOP_IRQ_DIN_HSYNC);
> +
> +	/* Enable frame done and stats error irq */
> +	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
> +			   TOP_IRQ_FRAME_DONE, TOP_IRQ_FRAME_DONE);
> +	c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
> +			   TOP_IRQ_STATS_ERR, TOP_IRQ_STATS_ERR);
> +
> +	/* Enable image data to ISP core */
> +	c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
> +			   TOP_DATA_PATH_MASK, TOP_PATH_MIPI_TO_CORE);
> +}
> +
> +static void c3_isp_core_disable(struct c3_isp_device *isp)
> +{
> +	/* Disable image data to ISP core */
> +	c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
> +			   TOP_DATA_PATH_MASK, 0x0);
> +
> +	/* Disable all irq */
> +	c3_isp_write(isp, ISP_TOP_IRQ_EN, 0x0);
> +}
> +
> +/* Set the phase offset of blc, wb and lns */
> +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
> +				  u8 xofst, u8 yofst)
> +{
> +	c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
> +			   LSWB_BLC_XPHS_OFST_MASK,
> +			   xofst << LSWB_BLC_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
> +			   LSWB_BLC_YPHS_OFST_MASK, yofst);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
> +			   LSWB_WB_XPHS_OFST_MASK,
> +			   xofst << LSWB_WB_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
> +			   LSWB_WB_YPHS_OFST_MASK, yofst);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
> +			   LSWB_LNS_XPHS_OFST_MASK,
> +			   xofst << LSWB_LNS_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
> +			   LSWB_LNS_YPHS_OFST_MASK, yofst);
> +}
> +
> +/* Set the phase offset of af, ae and awb */
> +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
> +				u8 xofst, u8 yofst)
> +{
> +	c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_XPHS_OFST_MASK,
> +			   xofst << AF_CTRL_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_YPHS_OFST_MASK,
> +			   yofst << AF_CTRL_YPHS_OFST_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_XPHS_OFST_MASK,
> +			   xofst << AE_CTRL_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_YPHS_OFST_MASK,
> +			   yofst << AE_CTRL_YPHS_OFST_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_XPHS_OFST_MASK,
> +			   xofst << AWB_CTRL_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_YPHS_OFST_MASK, yofst);
> +}
> +
> +/* Set the phase offset of demosaic */
> +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
> +				 u8 xofst, u8 yofst)
> +{
> +	c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_XPHS_OFST_MASK,
> +			   xofst << DMS_COMMON_XPHS_OFST_SHIFT);
> +	c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_YPHS_OFST_MASK, yofst);
> +}
> +
> +static void c3_isp_core_cfg_ofst(struct c3_isp_device *isp,
> +				 struct v4l2_mbus_framefmt *fmt)
> +{
> +	const struct c3_isp_mbus_format_info *isp_fmt =
> +			core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
> +
> +	c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> +	c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> +	c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> +}
> +
> +/* Set format of the hardware control module */
> +static void c3_isp_core_top_fmt(struct c3_isp_device *isp,
> +				struct v4l2_mbus_framefmt *fmt)
> +{
> +	c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
> +		     TOP_INPUT_HSIZE(fmt->width) | TOP_INPUT_VSIZE(fmt->height));
> +
> +	c3_isp_write(isp, ISP_TOP_FRM_SIZE,
> +		     TOP_FRM_CORE_IHSIZE(fmt->width) | TOP_FRM_CORE_IVSIZE(fmt->height));
> +
> +	c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE, TOP_HOLD_HSIZE_MASK,
> +			   fmt->width << TOP_HOLD_HSIZE_SHIFT);
> +}
> +
> +static void c3_isp_core_af_fmt(struct c3_isp_device *isp,
> +			       struct v4l2_mbus_framefmt *fmt)
> +{
> +	u32 hidx;
> +	u32 vidx;
> +	int i;
> +
> +	c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, AF_HV_STAT_HBLK_NUM_MASK,
> +			   AF_STAT_BLKH_NUM << AF_HV_STAT_HBLK_NUM_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM,
> +			   AF_HV_STAT_VBLK_NUM_MASK, AF_STAT_BLKV_NUM);
> +
> +	c3_isp_write(isp, ISP_AF_HV_SIZE,
> +		     AF_HV_STAT_HSIZE(fmt->width) | AF_HV_STAT_VSIZE(fmt->height));
> +
> +	/* Set the index address to 0 position */
> +	c3_isp_write(isp, ISP_AF_IDX_ADDR, 0);
blank line please
> +	/*
> +	 * Calculate and set the coordinates of points in the grid.
> +	 * hidx and vidx need to be aligned with 2.
> +	 */
> +	for (i = 0; i <= AF_STAT_BLKH_NUM; i++) {
> +		hidx = i * fmt->width / AF_STAT_BLKH_NUM;
> +		hidx = ALIGN_DOWN(hidx, 2);
> +
> +		vidx = i * fmt->height / AF_STAT_BLKV_NUM;
> +		vidx = min(vidx, fmt->height);
> +		vidx = ALIGN_DOWN(vidx, 2);
> +		c3_isp_write(isp, ISP_AF_IDX_DATA,
> +			     AF_IDX_HIDX_DATA(hidx) | AF_IDX_VIDX_DATA(vidx));
> +	}
> +}
> +
> +static void c3_isp_core_ae_fmt(struct c3_isp_device *isp,
> +			       struct v4l2_mbus_framefmt *fmt)
> +{
> +	u32 hidx;
> +	u32 vidx;
> +	int i;
> +
> +	c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, AE_HV_STAT_HBLK_NUM_MASK,
> +			   AE_STAT_BLKH_NUM << AE_HV_STAT_HBLK_NUM_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM,
> +			   AE_HV_STAT_VBLK_NUM_MASK, AE_STAT_BLKV_NUM);
> +
> +	c3_isp_write(isp, ISP_AE_HV_SIZE,
> +		     AE_HV_STAT_HSIZE(fmt->width) | AE_HV_STAT_VSIZE(fmt->height));
> +
> +	/* Set the index address to 0 position */
> +	c3_isp_write(isp, ISP_AE_IDX_ADDR, 0);
> +	/*
> +	 * Calculate and set the coordinates of points in the grid.
> +	 * hidx and vidx need to be aligned with 2.
> +	 */
> +	for (i = 0; i <= AE_STAT_BLKH_NUM; i++) {
> +		hidx = i * fmt->width / AE_STAT_BLKH_NUM;
> +		hidx = ALIGN_DOWN(hidx, 2);
> +
> +		vidx = i * fmt->height / AE_STAT_BLKV_NUM;
> +		vidx = min(vidx, fmt->height);
> +		vidx = ALIGN_DOWN(vidx, 2);
> +
> +		c3_isp_write(isp, ISP_AE_IDX_DATA,
> +			     AE_IDX_HIDX_DATA(hidx) | AE_IDX_VIDX_DATA(vidx));
> +	}
> +}
> +
> +static void c3_isp_core_awb_fmt(struct c3_isp_device *isp,
> +				struct v4l2_mbus_framefmt *fmt)
> +{
> +	u32 hidx;
> +	u32 vidx;
> +	int i;
> +
> +	c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, AWB_HV_STAT_HBLK_NUM_MASK,
> +			   AWB_STAT_BLKH_NUM << AWB_HV_STAT_HBLK_NUM_SHIFT);
> +	c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM,
> +			   AWB_HV_STAT_VBLK_NUM_MASK, AWB_STAT_BLKV_NUM);
> +
> +	c3_isp_write(isp, ISP_AWB_HV_SIZE,
> +		     AWB_HV_STAT_HSIZE(fmt->width) | AWB_HV_STAT_VSIZE(fmt->height));
> +
> +	/* Set the index address to 0 position */
> +	c3_isp_write(isp, ISP_AWB_IDX_ADDR, 0);
> +	/*
> +	 * Calculate and set the coordinates of points in the grid.
> +	 * hidx and vidx need to be aligned with 2.
> +	 */
> +	for (i = 0; i <= AWB_STAT_BLKH_NUM; i++) {
> +		hidx = i * fmt->width / AWB_STAT_BLKH_NUM;
> +		hidx = ALIGN_DOWN(hidx, 2);
> +
> +		vidx = i * fmt->height / AWB_STAT_BLKV_NUM;
> +		vidx = min(vidx, fmt->height);
> +		vidx = ALIGN_DOWN(vidx, 2);
> +
> +		c3_isp_write(isp, ISP_AWB_IDX_DATA,
> +			     AWB_IDX_HIDX_DATA(hidx) | AWB_IDX_VIDX_DATA(vidx));
> +	}
> +}
> +
> +static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
> +				   struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> +
> +	c3_isp_core_cfg_ofst(isp, fmt);
> +	c3_isp_core_top_fmt(isp, fmt);
> +	c3_isp_core_af_fmt(isp, fmt);
> +	c3_isp_core_ae_fmt(isp, fmt);
> +	c3_isp_core_awb_fmt(isp, fmt);
> +}
> +
> +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
> +				      struct v4l2_subdev_state *state,
> +				      u32 pad, u64 streams_mask)
> +{
> +	struct c3_isp_core *core = v4l2_get_subdevdata(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	core->isp->frm_sequence = 0;
> +	c3_isp_core_cfg_format(core->isp, state);
> +	c3_isp_core_enable(core->isp);
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       C3_ISP_CORE_PAD_SINK_VIDEO,
> +						       &streams_mask);
> +	ret = v4l2_subdev_enable_streams(core->src_sd,
> +					 core->src_sd_pad, sink_streams);
> +	if (ret) {
> +		c3_isp_core_disable(core->isp);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
> +				       struct v4l2_subdev_state *state,
> +				       u32 pad, u64 streams_mask)
> +{
> +	struct c3_isp_core *core = v4l2_get_subdevdata(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       C3_ISP_CORE_PAD_SINK_VIDEO,
> +						       &streams_mask);
> +	ret = v4l2_subdev_disable_streams(core->src_sd,
> +					  core->src_sd_pad, sink_streams);
> +	if (ret)
> +		return ret;
> +
> +	c3_isp_core_disable(core->isp);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   struct v4l2_subdev_krouting *routing)
> +{
> +	static const struct v4l2_mbus_framefmt format = {
> +		.width = C3_ISP_DEFAULT_WIDTH,
> +		.height = C3_ISP_DEFAULT_HEIGHT,
> +		.code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
> +		.field = V4L2_FIELD_NONE,
> +		.colorspace = V4L2_COLORSPACE_SRGB,
> +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
> +		.xfer_func = V4L2_XFER_FUNC_SRGB,
> +	};
> +	int ret;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_NO_SOURCE_STREAM_MIX);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes[2];
> +	struct v4l2_subdev_krouting routing;
> +
> +	routes[0].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
> +	routes[0].sink_stream = 0;
> +	routes[0].source_pad = C3_ISP_CORE_PAD_SOURCE_STATS;
> +	routes[0].source_stream = 0;
> +	routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
This doesn't look right - I wouldn't expect a route between the video input and the sink. I wouldn't 
really expect routes at all for this subdevice to be honest
> +
> +	routes[1].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
> +	routes[1].sink_stream = 0;
> +	routes[1].source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
> +	routes[1].source_stream = 0;
> +	routes[1].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> +
> +	routing.num_routes = ARRAY_SIZE(routes);
> +	routing.routes = routes;
> +
> +	return c3_isp_core_cfg_routing(sd, state, &routing);
> +}
> +
> +static int c3_isp_core_set_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   enum v4l2_subdev_format_whence which,
> +				   struct v4l2_subdev_krouting *routing)
> +{
> +	bool is_streaming = v4l2_subdev_is_streaming(sd);
> +
> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> +		return -EBUSY;
> +
> +	return c3_isp_core_cfg_routing(sd, state, routing);
> +}
> +
> +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
> +				      struct v4l2_subdev_state *state,
> +				      struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	const struct c3_isp_mbus_format_info *info;
> +	int ret = 0;
> +
> +	switch (code->pad) {
> +	case C3_ISP_CORE_PAD_SINK_VIDEO:
> +	case C3_ISP_CORE_PAD_SOURCE_VIDEO:
> +		info = core_find_format_by_index(code->index, code->pad);
> +		if (!info)
> +			ret = -EINVAL;
> +		else
> +			code->code = info->mbus_code;
> +
> +		break;
> +	case C3_ISP_CORE_PAD_SINK_PARAMS:
> +	case C3_ISP_CORE_PAD_SOURCE_STATS:
> +		if (code->index)
> +			ret = -EINVAL;
> +		else
> +			code->code = MEDIA_BUS_FMT_METADATA_FIXED;
> +
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static void c3_isp_core_set_sink_fmt(struct v4l2_subdev_state *state,
> +				     struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	const struct c3_isp_mbus_format_info *isp_fmt;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
> +
> +	isp_fmt = core_find_format_by_code(format->format.code, format->pad);
> +	if (!isp_fmt)
> +		sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
> +	else
> +		sink_fmt->code = format->format.code;
> +
> +	sink_fmt->width = clamp_t(u32, format->format.width,
> +				  C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> +	sink_fmt->height = clamp_t(u32, format->format.height,
> +				   C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> +
> +	format->format = *sink_fmt;
> +}
> +
> +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
> +				       struct v4l2_subdev_format *format)
> +{
> +	const struct c3_isp_mbus_format_info *isp_fmt;
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> +	src_fmt = v4l2_subdev_state_get_format(state, format->pad);
> +
> +	isp_fmt = core_find_format_by_code(format->format.code, format->pad);
> +	if (!isp_fmt)
> +		src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
> +	else
> +		src_fmt->code = format->format.code;
> +
> +	/* The source size must be same with the sink size. */
> +	src_fmt->width  = sink_fmt->width;
> +	src_fmt->height = sink_fmt->height;
The source size should be propagated when the sink format is set then rather than picking it up on a 
subsequent .set_fmt() call for the source pad
> +
> +	format->format = *src_fmt;
> +}
> +
> +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
> +			       struct v4l2_subdev_state *state,
> +			       struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	switch (format->pad) {
> +	case C3_ISP_CORE_PAD_SINK_VIDEO:
> +		c3_isp_core_set_sink_fmt(state, format);
> +		break;
> +	case C3_ISP_CORE_PAD_SINK_PARAMS:
> +	case C3_ISP_CORE_PAD_SOURCE_STATS:
> +		fmt = v4l2_subdev_state_get_format(state, format->pad);
> +		format->format = *fmt;
> +		break;
> +	case C3_ISP_CORE_PAD_SOURCE_VIDEO:
> +		c3_isp_core_set_source_fmt(state, format);
> +		break;
> +	default:
> +		dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
> +		return -ENOTTY;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_core_init_state(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;
> +
> +	/* Video sink pad */
> +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> +	sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
> +	sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> +	sink_fmt->field = V4L2_FIELD_NONE;
> +	sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
> +	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> +	sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
> +	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> +	sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
> +
> +	/* Video source pad */
> +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
> +	src_fmt->width = C3_ISP_DEFAULT_WIDTH;
> +	src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> +	src_fmt->field = V4L2_FIELD_NONE;
> +	src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
> +	src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> +	src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
> +	src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> +	src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> +
> +	/* Parameters pad */
> +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
> +	sink_fmt->width = 0;
> +	sink_fmt->height = 0;
> +	sink_fmt->field = V4L2_FIELD_NONE;
> +	sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
> +
> +	/* Statistics pad */
> +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
> +	src_fmt->width = 0;
> +	src_fmt->height = 0;
> +	src_fmt->field = V4L2_FIELD_NONE;
> +	src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
> +
> +	return c3_isp_core_init_routing(sd, state);
> +}
> +
> +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
> +	.enum_mbus_code = c3_isp_core_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = c3_isp_core_set_fmt,
> +	.enable_streams = c3_isp_core_enable_streams,
> +	.disable_streams = c3_isp_core_disable_streams,
> +	.set_routing = c3_isp_core_set_routing,
> +};
> +
> +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
> +	.pad = &c3_isp_core_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
> +	.init_state = c3_isp_core_init_state,
> +};
> +
> +static int c3_isp_core_link_validate(struct media_link *link)
> +{
> +	if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
> +		return 0;
> +
> +	return v4l2_subdev_link_validate(link);
> +}
> +
> +/* Media entity operations */
> +static const struct media_entity_operations c3_isp_core_entity_ops = {
> +	.link_validate = c3_isp_core_link_validate,
> +};
> +
> +int c3_isp_core_register(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_core *core = &isp->core;
> +	struct v4l2_subdev *sd = &core->sd;
> +	int ret;
> +
> +	v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
> +	sd->owner = THIS_MODULE;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	sd->internal_ops = &c3_isp_core_internal_ops;
> +	snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
> +
> +	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
> +	sd->entity.ops = &c3_isp_core_entity_ops;
> +
> +	core->isp = isp;
> +	sd->dev = isp->dev;
> +	v4l2_set_subdevdata(sd, core);
> +
> +	core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
> +	core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
> +	core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
> +	core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret)
> +		goto err_entity_cleanup;
> +
> +	ret = v4l2_device_register_subdev(&isp->v4l2_dev, sd);
> +	if (ret)
> +		goto err_subdev_cleanup;
> +
> +	return 0;
> +
> +err_subdev_cleanup:
> +	v4l2_subdev_cleanup(sd);
> +err_entity_cleanup:
> +	media_entity_cleanup(&sd->entity);
> +	return ret;
> +}
> +
> +void c3_isp_core_unregister(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_core *core = &isp->core;
> +	struct v4l2_subdev *sd = &core->sd;
> +
> +	v4l2_device_unregister_subdev(sd);
> +	v4l2_subdev_cleanup(sd);
> +	media_entity_cleanup(&sd->entity);
> +}
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
> new file mode 100644
> index 000000000000..a57b9f8dbc3c
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
> @@ -0,0 +1,486 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +
> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
> +{
> +	return readl(isp->base + reg);
> +}
> +
> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
> +{
> +	writel(val, isp->base + reg);
> +}
> +
> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
> +{
> +	u32 orig, tmp;
> +
> +	orig = c3_isp_read(isp, reg);
> +
> +	tmp = orig & ~mask;
> +	tmp |= val & mask;
> +
> +	if (tmp != orig)
> +		c3_isp_write(isp, reg, tmp);
> +}
> +
> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp)
> +{
> +	struct media_pipeline_entity_iter iter;
> +	unsigned int n_video_devices = 0;
> +	struct media_entity *entity;
> +	int ret;
> +
> +	ret = media_pipeline_entity_iter_init(&isp->pipe, &iter);
> +	if (ret)
> +		return ret;
> +
> +	media_pipeline_for_each_entity(&isp->pipe, &iter, entity) {
> +		if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
> +			n_video_devices++;
> +	}
> +
> +	media_pipeline_entity_iter_cleanup(&iter);
> +
> +	return n_video_devices == isp->pipe.start_count;
> +}
> +
> +/* PM runtime suspend */
> +static int __maybe_unused c3_isp_runtime_suspend(struct device *dev)
> +{
> +	struct c3_isp_device *isp = dev_get_drvdata(dev);
> +
> +	clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
> +
> +	return 0;
> +}
> +
> +/* PM runtime resume */
> +static int __maybe_unused c3_isp_runtime_resume(struct device *dev)
> +{
> +	struct c3_isp_device *isp = dev_get_drvdata(dev);
> +
> +	return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
> +}
> +
> +static const struct dev_pm_ops c3_isp_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> +				pm_runtime_force_resume)
> +	SET_RUNTIME_PM_OPS(c3_isp_runtime_suspend,
> +			   c3_isp_runtime_resume, NULL)
> +};
> +
> +/* IRQ handling */
> +static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
> +{
> +	struct c3_isp_device *isp = dev;
> +	u32 status;
> +
> +	/* Get irq status and clear irq status */
> +	status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
> +	c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
> +
> +	if (status & TOP_IRQ_FRAME_DONE) {
> +		c3_isp_stats_done(isp);
> +		c3_isp_params_done(isp);
> +		c3_isp_captures_done(isp);
> +		isp->frm_sequence++;
> +	}
> +
> +	if (status & TOP_IRQ_STATS_ERR)
> +		dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
> +
> +	return IRQ_HANDLED;
> +}
> +
> +/* Subdev notifier register */
> +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
> +			       struct v4l2_subdev *sd,
> +			       struct v4l2_async_connection *asc)
> +{
> +	struct c3_isp_device *isp =
> +		container_of(notifier, struct c3_isp_device, notifier);
> +	struct c3_isp_core *core = &isp->core;
> +	struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
> +	int ret;
> +
> +	ret = media_entity_get_fwnode_pad(&sd->entity,
> +					  sd->fwnode, MEDIA_PAD_FL_SOURCE);
> +	if (ret < 0) {
> +		dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
> +		return ret;
> +	}
> +
> +	core->src_sd = sd;
> +	core->src_sd_pad = ret;
> +
> +	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
> +					       MEDIA_LNK_FL_IMMUTABLE);
> +}
> +
> +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
> +{
> +	struct c3_isp_device *isp =
> +		container_of(notifier, struct c3_isp_device, notifier);
> +	int ret;
> +
> +	ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
> +	if (ret < 0) {
> +		dev_err(isp->dev,
> +			"Failed to register subdev nodes: %d\n", ret);
> +		return ret;
> +	}
> +
> +	dev_info(isp->dev, "notify complete\n");
> +
> +	return media_device_register(&isp->media_dev);
> +}
> +
> +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
> +{
> +	struct c3_isp_device *isp =
> +		container_of(asc->notifier, struct c3_isp_device, notifier);
> +
> +	media_device_unregister(&isp->media_dev);
> +}
> +
> +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
> +	.bound = c3_isp_notify_bound,
> +	.complete = c3_isp_notify_complete,
> +	.destroy = c3_isp_notify_destroy,
> +};
> +
> +static int c3_isp_async_nf_register(struct c3_isp_device *isp)
> +{
> +	struct v4l2_async_connection *asc;
> +	struct fwnode_handle *ep;
> +	int ret;
> +
> +	v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
> +
> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
> +					     FWNODE_GRAPH_ENDPOINT_NEXT);
> +	if (!ep)
> +		return -ENOTCONN;
> +
> +	asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
> +					      struct v4l2_async_connection);
> +	if (IS_ERR(asc)) {
> +		fwnode_handle_put(ep);
> +		return PTR_ERR(asc);
> +	}
> +
> +	fwnode_handle_put(ep);


Move this above if (IS_ERR(asc)) and you don't need to call it in two places

> +
> +	isp->notifier.ops = &c3_isp_notify_ops;
> +	ret = v4l2_async_nf_register(&isp->notifier);
> +	if (ret)
> +		v4l2_async_nf_cleanup(&isp->notifier);
> +
> +	return ret;
> +}
> +
> +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
> +{
> +	v4l2_async_nf_unregister(&isp->notifier);
> +	v4l2_async_nf_cleanup(&isp->notifier);
> +}
> +
> +static int c3_isp_v4l2_register(struct c3_isp_device *isp)
> +{
> +	struct media_device *media_dev = &isp->media_dev;
> +	struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
> +	int ret;
> +
> +	/* Initialize media device */
> +	strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
> +		sizeof(media_dev->model));
> +	media_dev->dev = isp->dev;
> +
> +	media_device_init(media_dev);
> +
> +	/* Initialize v4l2 device */
> +	v4l2_dev->mdev = media_dev;
> +	strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
> +		sizeof(v4l2_dev->name));
> +
> +	ret = v4l2_device_register(isp->dev, v4l2_dev);
> +	if (ret) {
> +		media_device_cleanup(media_dev);
> +		dev_err(isp->dev,
> +			"Failed to register V4L2 device: %d\n", ret);
> +	}
> +
> +	return ret;
> +}
> +
> +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
> +{
> +	v4l2_device_unregister(&isp->v4l2_dev);
> +	media_device_cleanup(&isp->media_dev);
> +}
> +
> +static void c3_isp_remove_links(struct c3_isp_device *isp)
> +{
> +	unsigned int i;
> +
> +	media_entity_remove_links(&isp->core.sd.entity);
> +
> +	for (i = 0; i < C3_ISP_NUM_RSZ; i++)
> +		media_entity_remove_links(&isp->resizers[i].sd.entity);
> +
> +	for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
> +		media_entity_remove_links(&isp->caps[i].vdev.entity);
> +}
> +
> +static int c3_isp_create_links(struct c3_isp_device *isp)
> +{
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
> +		ret = media_create_pad_link(&isp->resizers[i].sd.entity,
> +					    C3_ISP_RESIZER_PAD_SOURCE,
> +					    &isp->resizers[i].cap->vdev.entity,
> +					    0, MEDIA_LNK_FL_ENABLED);
> +		if (ret) {
> +			dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
> +			goto err_remove_links;
> +		}
> +
> +		ret = media_create_pad_link(&isp->core.sd.entity,
> +					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +					    &isp->resizers[i].sd.entity,
> +					    C3_ISP_RESIZER_PAD_SINK,
> +					    MEDIA_LNK_FL_ENABLED);
> +		if (ret) {
> +			dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
> +			goto err_remove_links;
> +		}
> +	}
> +
> +	ret = media_create_pad_link(&isp->core.sd.entity,
> +				    C3_ISP_CORE_PAD_SOURCE_STATS,
> +				    &isp->stats.vdev.entity,
> +				    0, MEDIA_LNK_FL_ENABLED);
> +	if (ret) {
> +		dev_err(isp->dev, "Failed to link core and stats\n");
> +		goto err_remove_links;
> +	}
> +
> +	ret = media_create_pad_link(&isp->params.vdev.entity, 0,
> +				    &isp->core.sd.entity,
> +				    C3_ISP_CORE_PAD_SINK_PARAMS,
> +				    MEDIA_LNK_FL_ENABLED);
> +	if (ret) {
> +		dev_err(isp->dev, "Failed to link params and core\n");
> +		goto err_remove_links;
> +	}
> +
> +	return 0;
> +
> +err_remove_links:
> +	c3_isp_remove_links(isp);
> +	return ret;
> +}
> +
> +static int c3_isp_videos_register(struct c3_isp_device *isp)
> +{
> +	int ret;
> +
> +	ret = c3_isp_captures_register(isp);
> +	if (ret)
> +		return ret;
> +
> +	ret = c3_isp_stats_register(isp);
> +	if (ret)
> +		goto err_captures_unregister;
> +
> +	ret = c3_isp_params_register(isp);
> +	if (ret)
> +		goto err_stats_unregister;
> +
> +	ret = c3_isp_create_links(isp);
> +	if (ret)
> +		goto err_params_unregister;
> +
> +	return 0;
> +
> +err_params_unregister:
> +	c3_isp_params_unregister(isp);
> +err_stats_unregister:
> +	c3_isp_stats_unregister(isp);
> +err_captures_unregister:
> +	c3_isp_captures_unregister(isp);
> +	return ret;
> +}
> +
> +static void c3_isp_videos_unregister(struct c3_isp_device *isp)
> +{
> +	c3_isp_remove_links(isp);
> +	c3_isp_params_unregister(isp);
> +	c3_isp_stats_unregister(isp);
> +	c3_isp_captures_unregister(isp);
> +}
> +
> +static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
> +{
> +	const struct c3_isp_info *info = isp->info;
> +	int ret;
> +	u32 i;
> +
> +	for (i = 0; i < info->clock_num; i++)
> +		isp->clks[i].id = info->clocks[i];
> +
> +	ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < info->clock_num; i++) {
> +		if (!info->clock_rates[i])
> +			continue;
> +		ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);
> +		if (ret) {
> +			dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
> +				info->clock_rates[i]);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct c3_isp_device *isp;
> +	int irq;
> +	int ret;
> +
> +	isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
> +	if (!isp)
> +		return -ENOMEM;
> +
> +	isp->info = of_device_get_match_data(dev);
> +	isp->dev = dev;
> +
> +	isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
> +	if (IS_ERR(isp->base))
> +		return dev_err_probe(dev, PTR_ERR(isp->base),
> +				     "Failed to ioremap resource\n");
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return irq;
> +
> +	ret = c3_isp_cfg_clocks(isp);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to configure clocks\n");
> +
> +	platform_set_drvdata(pdev, isp);
> +
> +	pm_runtime_enable(dev);
> +
> +	ret = c3_isp_v4l2_register(isp);
> +	if (ret)
> +		goto err_runtime_disable;
> +
> +	ret = c3_isp_core_register(isp);
> +	if (ret)
> +		goto err_v4l2_unregister;
> +
> +	ret = c3_isp_resizers_register(isp);
> +	if (ret)
> +		goto err_core_unregister;
> +
> +	ret = c3_isp_async_nf_register(isp);
> +	if (ret)
> +		goto err_resizers_unregister;
> +
> +	ret = c3_isp_videos_register(isp);
> +	if (ret)
> +		goto err_nf_unregister;
> +
> +	ret = devm_request_irq(dev, irq,
> +			       c3_isp_irq_handler, IRQF_SHARED,
> +			       dev_driver_string(dev), isp);
> +	if (ret)
> +		goto err_streams_unregister;
> +
> +	mutex_init(&isp->lock);
> +
> +	return 0;
> +
> +err_streams_unregister:
> +	c3_isp_videos_unregister(isp);
> +err_nf_unregister:
> +	c3_isp_async_nf_unregister(isp);
> +err_resizers_unregister:
> +	c3_isp_resizers_unregister(isp);
> +err_core_unregister:
> +	c3_isp_core_unregister(isp);
> +err_v4l2_unregister:
> +	c3_isp_v4l2_unregister(isp);
> +err_runtime_disable:
> +	pm_runtime_disable(dev);
> +	return ret;
> +};
> +
> +static void c3_isp_remove(struct platform_device *pdev)
> +{
> +	struct c3_isp_device *isp = platform_get_drvdata(pdev);
> +
> +	mutex_destroy(&isp->lock);
> +	c3_isp_videos_unregister(isp);
> +	c3_isp_async_nf_unregister(isp);
> +	c3_isp_core_unregister(isp);
> +	c3_isp_resizers_unregister(isp);
> +	c3_isp_v4l2_unregister(isp);
> +	pm_runtime_disable(isp->dev);
> +};
> +
> +static const struct c3_isp_info isp_info = {
> +	.clocks = {"vapb", "isp0"},
> +	.clock_rates = {0, 400000000},
> +	.clock_num = 2
> +};
> +
> +static const struct of_device_id c3_isp_of_match[] = {
> +	{ .compatible = "amlogic,c3-isp",
> +	  .data = &isp_info },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, c3_isp_of_match);
> +
> +static struct platform_driver c3_isp_driver = {
> +	.probe = c3_isp_probe,
> +	.remove = c3_isp_remove,
> +	.driver = {
> +		.name = "c3-isp",
> +		.of_match_table = c3_isp_of_match,
> +		.pm = &c3_isp_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(c3_isp_driver);
> +
> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
> +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
> new file mode 100644
> index 000000000000..8a6b7ce86eaf
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
> @@ -0,0 +1,857 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +#include "include/uapi/c3-isp-config.h"
> +
> +typedef void (*block_handler)(struct c3_isp_device *isp,
> +			      struct c3_isp_param_block_header *block);
> +
> +struct c3_isp_block_handler {
> +	size_t size;
> +	block_handler handler;
> +};
> +
> +/* Hardware configuration */
> +
> +static void c3_isp_params_cfg_wb_change(struct c3_isp_device *isp,
> +					struct c3_isp_param_block_header *block)
> +{
> +	struct wb_change_cfg *wb = (struct wb_change_cfg *)block;

Sakari had a cool suggestion that abstracted the parameter blocks better using a union without the 
need to cast everywhere. See the use of union mali_c55_params_block in 
https://lore.kernel.org/linux-media/20241106100534.768400-17-dan.scally@ideasonboard.com/T/#u



> +
> +	if (!block->enabled) {
> +		c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
> +				   TOP_BEO_CTRL_WB_EN, false);
> +		return;
> +	}
> +
> +	c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
> +			   TOP_BEO_CTRL_WB_EN, TOP_BEO_CTRL_WB_EN);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, LSWB_WB_GAIN0_MASK,
> +			   wb->wb_gain[0] << LSWB_WB_GAIN0_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0,
> +			   LSWB_WB_GAIN1_MASK, wb->wb_gain[1]);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, LSWB_WB_GAIN2_MASK,
> +			   wb->wb_gain[2] << LSWB_WB_GAIN2_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1,
> +			   LSWB_WB_GAIN3_MASK, wb->wb_gain[3]);
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN2,
> +			   LSWB_WB_GAIN4_MASK, wb->wb_gain[4]);
> +
> +	c3_isp_write(isp, ISP_LSWB_WB_LIMIT0,
> +		     LSWB_WB_LIMIT0(wb->wb_limit[0]) | LSWB_WB_LIMIT1(wb->wb_limit[1]));
> +
> +	c3_isp_write(isp, ISP_LSWB_WB_LIMIT1,
> +		     LSWB_WB_LIMIT2(wb->wb_limit[2]) | LSWB_WB_LIMIT3(wb->wb_limit[3]));
> +
> +	c3_isp_update_bits(isp, ISP_LSWB_WB_LIMIT2, LSWB_WB_LIMIT4_MASK,
> +			   wb->wb_limit[4]);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_BL12_GRBGI0_MASK,
> +			   wb->ae_bl12_grbgi[0]);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_GAIN_GRBGI0_MASK,
> +			   wb->ae_gain_grbgi[0] << AE_CRTL2_GAIN_GRBGI0_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_BL12_GRBGI1_MASK,
> +			   wb->ae_bl12_grbgi[1]);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_GAIN_GRBGI1_MASK,
> +			   wb->ae_gain_grbgi[1] << AE_CRTL2_GAIN_GRBGI1_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_BL12_GRBGI2_MASK,
> +			   wb->ae_bl12_grbgi[2]);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_GAIN_GRBGI2_MASK,
> +			   wb->ae_gain_grbgi[2] << AE_CRTL2_GAIN_GRBGI2_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_BL12_GRBGI3_MASK,
> +			   wb->ae_bl12_grbgi[3]);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_GAIN_GRBGI3_MASK,
> +			   wb->ae_gain_grbgi[3] << AE_CRTL2_GAIN_GRBGI3_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_BL12_GRBGI4_MASK,
> +			   wb->ae_bl12_grbgi[4]);
> +
> +	c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_GAIN_GRBGI4_MASK,
> +			   wb->ae_gain_grbgi[4] << AE_CRTL2_GAIN_GRBGI4_SHIFT);
> +}
> +
> +static void c3_isp_params_cfg_wb_luma(struct c3_isp_device *isp,
> +				      struct c3_isp_param_block_header *block)
> +{
> +	struct wb_luma_cfg *wb_luma = (struct wb_luma_cfg *)block;
> +
> +	if (!block->enabled)
> +		return;
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_0,
> +			   AWB_STAT_BLC20_GR_MASK, wb_luma->awb_stat_blc20[0]);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_1,
> +			   AWB_STAT_BLC20_R_MASK, wb_luma->awb_stat_blc20[1]);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_2,
> +			   AWB_STAT_BLC20_B_MASK, wb_luma->awb_stat_blc20[2]);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_3,
> +			   AWB_STAT_BLC20_GB_MASK, wb_luma->awb_stat_blc20[3]);
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_0,
> +			   AWB_STAT_GAIN10_GR_MASK, wb_luma->awb_stat_gain10[0]);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_1,
> +			   AWB_STAT_GAIN10_R_MASK, wb_luma->awb_stat_gain10[1]);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_2,
> +			   AWB_STAT_GAIN10_B_MASK, wb_luma->awb_stat_gain10[2]);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_3,
> +			   AWB_STAT_GAIN10_GB_MASK, wb_luma->awb_stat_gain10[3]);
> +
> +	c3_isp_write(isp, ISP_AWB_STAT_SATUR_CTRL,
> +		     AWB_STAT_SATUR_LOW(wb_luma->awb_stat_satur_low) |
> +		     AWB_STAT_SATUR_HIGH(wb_luma->awb_stat_satur_high));
> +}
> +
> +static void c3_isp_params_cfg_wb_triangle(struct c3_isp_device *isp,
> +					  struct c3_isp_param_block_header *block)
> +{
> +	struct wb_triangle_cfg *wb = (struct wb_triangle_cfg *)block;
> +
> +	if (!block->enabled)
> +		return;
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_CTRL2, AWB_STAT_SATUR_VALID_MASK,
> +			   wb->awb_stat_satur_vald);
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MIN_MASK,
> +			   wb->awb_stat_rg_min);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MAX_MASK,
> +			   wb->awb_stat_rg_max << AWB_STAT_RG_MAX_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MIN_MASK,
> +			   wb->awb_stat_bg_min);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MAX_MASK,
> +			   wb->awb_stat_bg_max << AWB_STAT_BG_MAX_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_LOW_MASK,
> +			   wb->awb_stat_rg_low);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_HIGH_MASK,
> +			   wb->awb_stat_rg_high << AWB_STAT_RG_HIGH_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_LOW_MASK,
> +			   wb->awb_stat_bg_low);
> +	c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_HIGH_MASK,
> +			   wb->awb_stat_bg_high << AWB_STAT_BG_HIGH_SHIFT);
> +}
> +
> +static void c3_isp_params_cfg_awb_stats(struct c3_isp_device *isp,
> +					struct c3_isp_param_block_header *block)
> +{
> +	struct awb_stats_cfg *awb_stats = (struct awb_stats_cfg *)block;
> +	u32 *weight = awb_stats->awb_stat_blk_weight;
> +	int idx_base;
> +	int group;
> +	int i;
> +
> +	if (!block->enabled)
> +		return;
> +
> +	c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AWB_STAT_SWITCH_MASK,
> +			   awb_stats->awb_stat_switch << TOP_3A_AWB_STAT_SWITCH_SHIFT);
> +
> +	/* Calculate the group number */
> +	group = AWB_BLOCK_WT_NUM / AWB_BLK_WT_DATA_NUM_OF_GROUP;
> +
> +	/* Set the weight address to 0 position */
> +	c3_isp_write(isp, ISP_AWB_BLK_WT_ADDR, 0);
> +	for (i = 0; i < group; i++) {
> +		idx_base = i * AWB_BLK_WT_DATA_NUM_OF_GROUP;
> +		c3_isp_write(isp, ISP_AWB_BLK_WT_DATA,
> +			     AWB_BLK_WT_DATA0(weight[idx_base + 0]) |
> +			     AWB_BLK_WT_DATA1(weight[idx_base + 1]) |
> +			     AWB_BLK_WT_DATA2(weight[idx_base + 2]) |
> +			     AWB_BLK_WT_DATA3(weight[idx_base + 3]) |
> +			     AWB_BLK_WT_DATA4(weight[idx_base + 4]) |
> +			     AWB_BLK_WT_DATA5(weight[idx_base + 5]) |
> +			     AWB_BLK_WT_DATA6(weight[idx_base + 6]) |
> +			     AWB_BLK_WT_DATA7(weight[idx_base + 7]));
> +	}
> +}
> +
> +static void c3_isp_params_cfg_ae_stats(struct c3_isp_device *isp,
> +				       struct c3_isp_param_block_header *block)
> +{
> +	struct ae_stats_cfg *ae_stats = (struct ae_stats_cfg *)block;
> +	u32 *weight = ae_stats->ae_stat_blk_weight;
> +	int idx_base;
> +	int group;
> +	int i;
> +
> +	if (!block->enabled)
> +		return;
> +
> +	c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AE_STAT_SWITCH_MASK,
> +			   ae_stats->ae_stat_switch << TOP_3A_AE_STAT_SWITCH_SHIFT);
> +
> +	/* Calculate the group number */
> +	group = AE_BLOCK_WT_NUM / AE_BLK_WT_DATA_NUM_OF_GROUP;
> +
> +	/* Set the weight address to 0 position */
> +	c3_isp_write(isp, ISP_AE_BLK_WT_ADDR, 0);
> +	for (i = 0; i < group; i++) {
> +		idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
> +		c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
> +			     AE_BLK_WT_DATA0(weight[idx_base + 0]) |
> +			     AE_BLK_WT_DATA1(weight[idx_base + 1]) |
> +			     AE_BLK_WT_DATA2(weight[idx_base + 2]) |
> +			     AE_BLK_WT_DATA3(weight[idx_base + 3]) |
> +			     AE_BLK_WT_DATA4(weight[idx_base + 4]) |
> +			     AE_BLK_WT_DATA5(weight[idx_base + 5]) |
> +			     AE_BLK_WT_DATA6(weight[idx_base + 6]) |
> +			     AE_BLK_WT_DATA7(weight[idx_base + 7]));
> +	}
> +
> +	/* Write the last weight data */
> +	idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
> +	c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
> +		     AE_BLK_WT_DATA0(weight[idx_base + 0]) |
> +		     AE_BLK_WT_DATA1(weight[idx_base + 1]) |
> +		     AE_BLK_WT_DATA2(weight[idx_base + 2]) |
> +		     AE_BLK_WT_DATA3(weight[idx_base + 3]) |
> +		     AE_BLK_WT_DATA4(weight[idx_base + 4]) |
> +		     AE_BLK_WT_DATA5(weight[idx_base + 5]) |
> +		     AE_BLK_WT_DATA6(weight[idx_base + 6]));
> +}
> +
> +static void c3_isp_params_cfg_af_stats(struct c3_isp_device *isp,
> +				       struct c3_isp_param_block_header *block)
> +{
> +	struct af_stats_cfg *af_stats = (struct af_stats_cfg *)block;
> +
> +	if (!block->enabled)
> +		return;
> +
> +	c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AF_STAT_SWITCH_MASK,
> +			   af_stats->af_stat_switch << TOP_3A_AF_STAT_SWITCH_SHIFT);
> +}
> +
> +static void c3_isp_params_cfg_pst_gamma(struct c3_isp_device *isp,
> +					struct c3_isp_param_block_header *block)
> +{
> +	struct pst_gamma_cfg *gamma = (struct pst_gamma_cfg *)block;
> +	int idx_base;
> +	int i, j;
> +
> +	if (!block->enabled) {
> +		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, false);
> +		return;
> +	}
> +
> +	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, TOP_BED_GAMMA_EN);
> +
> +	for (j = 0; j < GAMMA_LUT_GROUP_NUM; j++) {
> +		c3_isp_write(isp, ISP_PST_GAMMA_LUT_ADDR, PST_GAMMA_LUT_ADDR(j));
> +
> +		/* Calculate the block number */
> +		for (i = 0; i < GAMMA_LUT_POINT_NUM / GAMMA_LUT_DATA_NUM_OF_GROUP; i++) {
> +			idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
> +			c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
> +				     PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]) |
> +				     PST_GAMMA_LUT_DATA1(gamma->pst_gamma_lut[j][idx_base + 1]));
> +		}
> +
> +		/* Write the last one lut data of group j */
> +		idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
> +		c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
> +			     PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]));
> +	}
> +}
> +
> +static void c3_isp_params_cfg_dmsc(struct c3_isp_device *isp,
> +				   struct c3_isp_param_block_header *block)
> +{
> +	if (!block->enabled) {
> +		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, false);
> +		return;
> +	}
> +
> +	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, TOP_BED_DMSC_EN);
> +}
> +
> +/* Configure 4 x 3 ccm matrix */
> +static void c3_isp_params_cfg_ccm(struct c3_isp_device *isp,
> +				  struct c3_isp_param_block_header *block)
> +{
> +	struct ccm_cfg *ccm = (struct ccm_cfg *)block;
> +
> +	if (!block->enabled) {
> +		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, false);
> +		return;
> +	}
> +
> +	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, TOP_BED_CCM_EN);
> +
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_00_MASK,
> +			   ccm->ccm_4x3matrix[0][0]);
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_01_MASK,
> +			   ccm->ccm_4x3matrix[0][1] << CCM_MTX_01_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_02_MASK,
> +			   ccm->ccm_4x3matrix[0][2]);
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_03_MASK,
> +			   ccm->ccm_4x3matrix[0][3] << CCM_MTX_03_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_10_MASK,
> +			   ccm->ccm_4x3matrix[1][0]);
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_11_MASK,
> +			   ccm->ccm_4x3matrix[1][1] << CCM_MTX_11_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_12_MASK,
> +			   ccm->ccm_4x3matrix[1][2]);
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_13_MASK,
> +			   ccm->ccm_4x3matrix[1][3] << CCM_MTX_13_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_20_MASK,
> +			   ccm->ccm_4x3matrix[2][0]);
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_21_MASK,
> +			   ccm->ccm_4x3matrix[2][1] << CCM_MTX_21_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_22_MASK,
> +			   ccm->ccm_4x3matrix[2][2]);
> +	c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_23_MASK,
> +			   ccm->ccm_4x3matrix[2][3] << CCM_MTX_23_SHIFT);
> +}
> +
> +/* Configure color space conversion matrix parameters */
> +static void c3_isp_params_cfg_csc(struct c3_isp_device *isp,
> +				  struct c3_isp_param_block_header *block)
> +{
> +	struct csc_cfg *csc = (struct csc_cfg *)block;
> +
> +	if (!block->enabled) {
> +		c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, false);
> +		return;
> +	}
> +
> +	c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, TOP_BED_CM0_EN);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST0_MASK,
> +			   csc->cm0_offset_inp[0]);
> +	c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST1_MASK,
> +			   csc->cm0_offset_inp[1] << CM0_INP_OFST1_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_INP_OFST2, CM0_INP_OFST2_MASK,
> +			   csc->cm0_offset_inp[2]);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_00_MASK,
> +			   csc->cm0_3x3matrix[0][0]);
> +	c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_01_MASK,
> +			   csc->cm0_3x3matrix[0][1] << CM0_MTX_01_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_02_MASK,
> +			   csc->cm0_3x3matrix[0][2]);
> +	c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_10_MASK,
> +			   csc->cm0_3x3matrix[1][0] << CM0_MTX_10_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_11_MASK,
> +			   csc->cm0_3x3matrix[1][1]);
> +	c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_12_MASK,
> +			   csc->cm0_3x3matrix[1][2] << CM0_MTX_12_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_20_MASK,
> +			   csc->cm0_3x3matrix[2][0]);
> +	c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_21_MASK,
> +			   csc->cm0_3x3matrix[2][1] << CM0_MTX_21_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_MTX_22_MASK,
> +			   csc->cm0_3x3matrix[2][2]);
> +	c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_OFST_OUP0_MASK,
> +			   csc->cm0_offset_oup[0] << CM0_OFST_OUP0_SHIFT);
> +
> +	c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP1_MASK,
> +			   csc->cm0_offset_oup[1]);
> +	c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP2_MASK,
> +			   csc->cm0_offset_oup[2] << CM0_OFST_OUP2_SHIFT);
> +	c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_MTX_RS_MASK,
> +			   csc->cm0_3x3mtrx_rs << CM0_MTX_RS_SHIFT);
> +}
> +
> +/* Set blc offset of each color channel */
> +static void c3_isp_params_cfg_blc(struct c3_isp_device *isp,
> +				  struct c3_isp_param_block_header *block)
> +{
> +	struct blc_cfg *blc = (struct blc_cfg *)block;
> +
> +	if (!block->enabled) {
> +		c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN, false);
> +		return;
> +	}
> +
> +	c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN,
> +			   TOP_BEO_CTRL_BLC_EN);
> +
> +	c3_isp_write(isp, ISP_FED_BL_OFST_GR, FED_BL_GR_OFST(blc->fe_bl_ofst[0]));
> +	c3_isp_write(isp, ISP_FED_BL_OFST_R, FED_BL_R_OFST(blc->fe_bl_ofst[1]));
> +	c3_isp_write(isp, ISP_FED_BL_OFST_B, FED_BL_B_OFST(blc->fe_bl_ofst[2]));
> +	c3_isp_write(isp, ISP_FED_BL_OFST_GB, FED_BL_GB_OFST(blc->fe_bl_ofst[3]));
> +	c3_isp_write(isp, ISP_FED_BL_OFST_IR, FED_BL_IR_OFST(blc->fe_bl_ofst[4]));
> +
> +	c3_isp_write(isp, ISP_LSWB_BLC_OFST0,
> +		     LSWB_BLC_OFST0(blc->blc_ofst[0]) | LSWB_BLC_OFST1(blc->blc_ofst[1]));
> +	c3_isp_write(isp, ISP_LSWB_BLC_OFST1,
> +		     LSWB_BLC_OFST2(blc->blc_ofst[2]) | LSWB_BLC_OFST3(blc->blc_ofst[3]));
> +	c3_isp_write(isp, ISP_LSWB_BLC_OFST2, LSWB_BLC_OFST4(blc->blc_ofst[4]));
> +}
> +
> +static const struct c3_isp_block_handler c3_isp_block_handlers[] = {
> +	[C3_ISP_PARAM_BLOCK_WB_CHANGE] = {
> +		.size = sizeof(struct wb_change_cfg),
> +		.handler = c3_isp_params_cfg_wb_change,
> +	},
> +	[C3_ISP_PARAM_BLOCK_WB_LUMA] = {
> +		.size = sizeof(struct wb_luma_cfg),
> +		.handler = c3_isp_params_cfg_wb_luma,
> +	},
> +	[C3_ISP_PARAM_BLOCK_WB_TRIANGLE] = {
> +		.size = sizeof(struct wb_triangle_cfg),
> +		.handler = c3_isp_params_cfg_wb_triangle,
> +	},
> +	[C3_ISP_PARAM_BLOCK_AWB_STATS] = {
> +		.size = sizeof(struct awb_stats_cfg),
> +		.handler = c3_isp_params_cfg_awb_stats,
> +	},
> +	[C3_ISP_PARAM_BLOCK_AE_STATS] = {
> +		.size = sizeof(struct ae_stats_cfg),
> +		.handler = c3_isp_params_cfg_ae_stats,
> +	},
> +	[C3_ISP_PARAM_BLOCK_AF_STATS] = {
> +		.size = sizeof(struct af_stats_cfg),
> +		.handler = c3_isp_params_cfg_af_stats,
> +	},
> +	[C3_ISP_PARAM_BLOCK_PST_GAMMA] = {
> +		.size = sizeof(struct pst_gamma_cfg),
> +		.handler = c3_isp_params_cfg_pst_gamma,
> +	},
> +	[C3_ISP_PARAM_BLOCK_DMSC] = {
> +		.size = sizeof(struct dmsc_cfg),
> +		.handler = c3_isp_params_cfg_dmsc,
> +	},
> +	[C3_ISP_PARAM_BLOCK_CCM] = {
> +		.size = sizeof(struct ccm_cfg),
> +		.handler = c3_isp_params_cfg_ccm,
> +	},
> +	[C3_ISP_PARAM_BLOCK_CSC] = {
> +		.size = sizeof(struct csc_cfg),
> +		.handler = c3_isp_params_cfg_csc,
> +	},
> +	[C3_ISP_PARAM_BLOCK_BLC] = {
> +		.size = sizeof(struct blc_cfg),
> +		.handler = c3_isp_params_cfg_blc,
> +	},
> +};
> +
> +static enum vb2_buffer_state
> +c3_isp_params_cfg_blocks(struct c3_isp_params *params)
> +{
> +	struct c3_isp_params_buffer *config = params->buff->vaddr;
> +	enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
> +	size_t block_offset = 0;
> +	size_t max_offset = 0;
> +
> +	if (config->total_size > C3_ISP_PARAMS_MAX_SIZE) {
> +		dev_dbg(params->isp->dev, "Invalid parameters buffer size %lu\n",
> +			config->total_size);
> +		state = VB2_BUF_STATE_ERROR;
> +		goto err_return_state;
> +	}
> +
> +	/* Ensure config->data has a full struct c3_isp_param_block_header */
> +	max_offset = config->total_size - sizeof(struct c3_isp_param_block_header);
> +
> +	while (block_offset <= max_offset) {
> +		const struct c3_isp_block_handler *block_handler;
> +		struct c3_isp_param_block_header *block;
> +
> +		block = (struct c3_isp_param_block_header *)
> +			 &config->data[block_offset];
> +
> +		if (block->type >= C3_ISP_PARAM_BLOCK_SENTINEL) {
> +			dev_dbg(params->isp->dev, "Invalid parameters block type\n");
> +			state = VB2_BUF_STATE_ERROR;
> +			goto err_return_state;
> +		}
> +
> +		block_handler = &c3_isp_block_handlers[block->type];
> +		if (block->size != block_handler->size) {
> +			dev_dbg(params->isp->dev, "Invalid parameters block size\n");
> +			state = VB2_BUF_STATE_ERROR;
> +			goto err_return_state;
> +		}
> +
> +		block_handler->handler(params->isp, block);
> +
> +		block_offset += block->size;
> +	}
> +
> +err_return_state:
> +	return state;
> +}


The validation in this function can be moved to .buf_queue() so it's not done in interrupt context

> +
> +/* Initialize ISP pipeline */
> +static int c3_isp_params_start(struct c3_isp_params *params)
> +{
> +	enum vb2_buffer_state state;
> +	unsigned long flags;
> +
> +	/* Reset these controllers */
> +	c3_isp_write(params->isp, ISP_TOP_FEO_CTRL0, TOP_FEO_CTRL0_ALL_DIS);
> +	c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_0, TOP_FEO_CTRL1_0_ALL_DIS);
> +	c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_1, TOP_FEO_CTRL1_1_ALL_DIS);
> +	c3_isp_write(params->isp, ISP_TOP_FED_CTRL, TOP_FED_CTRL_ALL_DIS);
> +	c3_isp_write(params->isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_ALL_DIS);
> +	c3_isp_write(params->isp, ISP_TOP_BED_CTRL, TOP_BED_CTRL_ALL_DIS);
> +
> +	spin_lock_irqsave(&params->buff_lock, flags);
> +
> +	/* Only use the first buffer to initialize ISP */
> +	params->buff = list_first_entry_or_null(&params->pending,
> +						struct c3_isp_vb2_buffer, list);
> +	if (!params->buff) {
> +		spin_unlock_irqrestore(&params->buff_lock, flags);
> +		return -EINVAL;
> +	}


You have min_queued_buffers set to a non-zero figure, so this error is theoretically impossible - I 
would keep the check but add a comment that it ought not to have happened.

> +
> +	state = c3_isp_params_cfg_blocks(params);
> +
> +	spin_unlock_irqrestore(&params->buff_lock, flags);
> +
> +	return 0;
> +}
> +
> +/* V4L2 video operations */
> +
> +static void c3_isp_params_return_buffers(struct c3_isp_params *params,
> +					 enum vb2_buffer_state state)
> +{
> +	unsigned long flags;
> +	struct c3_isp_vb2_buffer *buff;
> +
> +	spin_lock_irqsave(&params->buff_lock, flags);
> +
> +	while (!list_empty(&params->pending)) {
> +		buff = list_first_entry(&params->pending,
> +					struct c3_isp_vb2_buffer, list);
> +		list_del(&buff->list);
> +		vb2_buffer_done(&buff->vb.vb2_buf, state);
> +	}
> +
> +	spin_unlock_irqrestore(&params->buff_lock, flags);
> +}
> +
> +static int c3_isp_params_querycap(struct file *file, void *fh,
> +				  struct v4l2_capability *cap)
> +{
> +	strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
> +	strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
> +
> +	return 0;
> +}
> +
> +static int c3_isp_params_enum_fmt(struct file *file, void *fh,
> +				  struct v4l2_fmtdesc *f)
> +{
> +	if (f->index)
> +		return -EINVAL;
> +
> +	f->pixelformat = V4L2_META_FMT_C3ISP_PARAMS;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_params_g_fmt(struct file *file, void *fh,
> +			       struct v4l2_format *f)
> +{
> +	struct c3_isp_params *params = video_drvdata(file);
> +
> +	f->fmt.meta = params->vfmt.fmt.meta;
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops isp_params_v4l2_ioctl_ops = {
> +	.vidioc_querycap                = c3_isp_params_querycap,
> +	.vidioc_enum_fmt_meta_out       = c3_isp_params_enum_fmt,
> +	.vidioc_g_fmt_meta_out          = c3_isp_params_g_fmt,
> +	.vidioc_s_fmt_meta_out          = c3_isp_params_g_fmt,
> +	.vidioc_try_fmt_meta_out        = c3_isp_params_g_fmt,
> +	.vidioc_reqbufs                 = vb2_ioctl_reqbufs,
> +	.vidioc_querybuf                = vb2_ioctl_querybuf,
> +	.vidioc_qbuf                    = vb2_ioctl_qbuf,
> +	.vidioc_expbuf                  = vb2_ioctl_expbuf,
> +	.vidioc_dqbuf                   = vb2_ioctl_dqbuf,
> +	.vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
> +	.vidioc_create_bufs             = vb2_ioctl_create_bufs,
> +	.vidioc_streamon                = vb2_ioctl_streamon,
> +	.vidioc_streamoff               = vb2_ioctl_streamoff,
> +	.vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
> +	.vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
> +};
> +
> +static const struct v4l2_file_operations isp_params_v4l2_fops = {
> +	.open = v4l2_fh_open,
> +	.release = vb2_fop_release,
> +	.poll = vb2_fop_poll,
> +	.unlocked_ioctl = video_ioctl2,
> +	.mmap = vb2_fop_mmap,
> +};
> +
> +static int c3_isp_params_vb2_queue_setup(struct vb2_queue *q,
> +					 unsigned int *num_buffers,
> +					 unsigned int *num_planes,
> +					 unsigned int sizes[],
> +					 struct device *alloc_devs[])
> +{
> +	if (*num_planes) {
> +		if (*num_planes != 1)
> +			return -EINVAL;
> +
> +		if (sizes[0] < sizeof(struct c3_isp_params_buffer))
> +			return -EINVAL;
> +	} else {
> +		*num_planes = 1;
> +		sizes[0] = sizeof(struct c3_isp_params_buffer);
> +	}
> +
> +	return 0;
> +}
> +
> +static void c3_isp_params_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_vb2_buffer *buf =
> +			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> +	struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&params->buff_lock, flags);
> +
> +	list_add_tail(&buf->list, &params->pending);
> +
> +	spin_unlock_irqrestore(&params->buff_lock, flags);
> +}


So as mentioned above, you can validate the contents of the buffer in this function rather than 
during the interrupt. We've also started using memcpy() here to store the buffer in a scratch buffer 
internal to the driver; this is designed to protect against userspace making changes to the buffer 
whilst it's supposed the be being processed by the driver. Check the equivalent function in the 
mali-c55 series:


https://lore.kernel.org/linux-media/20241106100534.768400-17-dan.scally@ideasonboard.com/T/#u

> +
> +static int c3_isp_params_vb2_buf_prepare(struct vb2_buffer *vb)
> +{
> +	struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned int size = params->vfmt.fmt.meta.buffersize;
> +
> +	if (vb2_plane_size(vb, 0) < size) {


isn't vfmt.fmt.meta.buffersize filled by userspace? If so I think this would mean you'd have to pass 
all of the blocks - we don't want that, the design of the extensible params is to allow only part of 
the config to be passed if that's all that's needed.

> +		dev_err(params->isp->dev,
> +			"User buffer too small (%ld < %u)\n",
> +			vb2_plane_size(vb, 0), size);
> +		return -EINVAL;
> +	}
> +
> +	vb2_set_plane_payload(vb, 0, size);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_params_vb2_buf_init(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_vb2_buffer *buf =
> +		container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> +	struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
> +
> +	buf->vaddr = vb2_plane_vaddr(vb, 0);
> +	buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
> +
> +	memset(buf->vaddr, 0, params->vfmt.fmt.meta.buffersize);
> +
I'm not sure the memset is needed - although with the extensible format not all of the buffer need 
be overwritten, everything up to buffersize would be.
> +	return 0;
> +}
> +
> +static int c3_isp_params_vb2_start_streaming(struct vb2_queue *q,
> +					     unsigned int count)
> +{
> +	struct c3_isp_params *params = vb2_get_drv_priv(q);
> +	int ret;
> +
> +	guard(mutex)(&params->isp->lock);
> +
> +	ret = pm_runtime_resume_and_get(params->isp->dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = video_device_pipeline_start(&params->vdev, &params->isp->pipe);
> +	if (ret) {
> +		dev_err(params->isp->dev,
> +			"Failed to start params pipeline: %d\n", ret);
> +		goto err_pm_put;
> +	}
> +
> +	if (c3_isp_pipeline_ready(params->isp)) {
> +		ret = v4l2_subdev_enable_streams(&params->isp->core.sd,
> +						 C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +						 BIT(0));
> +		if (ret)
> +			goto err_pipeline_stop;
> +	}
> +
> +	c3_isp_params_start(params);
> +
> +	return 0;
> +
> +err_pipeline_stop:
> +	video_device_pipeline_stop(&params->vdev);
> +err_pm_put:
> +	pm_runtime_put(params->isp->dev);
> +	c3_isp_params_return_buffers(params, VB2_BUF_STATE_QUEUED);
> +	return ret;
> +}
> +
> +static void c3_isp_params_vb2_stop_streaming(struct vb2_queue *q)
> +{
> +	struct c3_isp_params *params = vb2_get_drv_priv(q);
> +
> +	guard(mutex)(&params->isp->lock);
> +
> +	c3_isp_params_return_buffers(params, VB2_BUF_STATE_ERROR);
> +
> +	if (params->isp->pipe.start_count == 1)
> +		v4l2_subdev_disable_streams(&params->isp->core.sd,
> +					    C3_ISP_CORE_PAD_SOURCE_VIDEO,
> +					    BIT(0));
> +
> +	video_device_pipeline_stop(&params->vdev);
> +	pm_runtime_put(params->isp->dev);
> +}
> +
> +static const struct vb2_ops isp_params_vb2_ops = {
> +	.queue_setup = c3_isp_params_vb2_queue_setup,
> +	.buf_queue = c3_isp_params_vb2_buf_queue,
> +	.buf_prepare = c3_isp_params_vb2_buf_prepare,
> +	.buf_init = c3_isp_params_vb2_buf_init,
> +	.wait_prepare = vb2_ops_wait_prepare,
> +	.wait_finish = vb2_ops_wait_finish,
> +	.start_streaming = c3_isp_params_vb2_start_streaming,
> +	.stop_streaming = c3_isp_params_vb2_stop_streaming,
> +};
> +
> +int c3_isp_params_register(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_params *params = &isp->params;
> +	struct video_device *vdev = &params->vdev;
> +	struct vb2_queue *vb2_q = &params->vb2_q;
> +	int ret;
> +
> +	memset(params, 0, sizeof(*params));
> +	params->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_PARAMS;
> +	params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
> +	params->isp = isp;
> +	INIT_LIST_HEAD(&params->pending);
> +	spin_lock_init(&params->buff_lock);
> +	mutex_init(&params->lock);
> +
> +	snprintf(vdev->name, sizeof(vdev->name), "isp-params");
> +	vdev->fops = &isp_params_v4l2_fops;
> +	vdev->ioctl_ops = &isp_params_v4l2_ioctl_ops;
> +	vdev->v4l2_dev = &isp->v4l2_dev;
> +	vdev->lock = &params->lock;
> +	vdev->minor = -1;
> +	vdev->queue = vb2_q;
> +	vdev->release = video_device_release_empty;
> +	vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING;
> +	vdev->vfl_dir = VFL_DIR_TX;
> +	video_set_drvdata(vdev, params);
> +
> +	vb2_q->drv_priv = params;
> +	vb2_q->mem_ops = &vb2_dma_contig_memops;
> +	vb2_q->ops = &isp_params_vb2_ops;
> +	vb2_q->type = V4L2_BUF_TYPE_META_OUTPUT;
> +	vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
> +	vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
> +	vb2_q->dev = isp->dev;
> +	vb2_q->lock = &params->lock;
> +	vb2_q->min_queued_buffers = 1;
> +
> +	ret = vb2_queue_init(vb2_q);
> +	if (ret)
> +		goto err_detroy;
> +
> +	params->pad.flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&vdev->entity, 1, &params->pad);
> +	if (ret)
> +		goto err_queue_release;
> +
> +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +	if (ret < 0) {
> +		dev_err(isp->dev,
> +			"Failed to register %s: %d\n", vdev->name, ret);
> +		goto err_entity_cleanup;
> +	}
> +
> +	return 0;
> +
> +err_entity_cleanup:
> +	media_entity_cleanup(&vdev->entity);
> +err_queue_release:
> +	vb2_queue_release(vb2_q);
> +err_detroy:
> +	mutex_destroy(&params->lock);
> +	return ret;
> +}
> +
> +void c3_isp_params_unregister(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_params *params = &isp->params;
> +
> +	vb2_queue_release(&params->vb2_q);
> +	media_entity_cleanup(&params->vdev.entity);
> +	video_unregister_device(&params->vdev);
> +	mutex_destroy(&params->lock);
> +}
> +
> +int c3_isp_params_done(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_params *params = &isp->params;
> +	enum vb2_buffer_state state;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&params->buff_lock, flags);
Usual comment about guard()
> +
> +	params->buff = list_first_entry_or_null(&params->pending,
> +						struct c3_isp_vb2_buffer, list);
> +	if (!params->buff) {
> +		spin_unlock_irqrestore(&params->buff_lock, flags);
> +		return -EINVAL;
> +	}
> +
> +	list_del(&params->buff->list);
> +
> +	state = c3_isp_params_cfg_blocks(params);
> +
> +	params->buff->vb.sequence = params->isp->frm_sequence;
> +	params->buff->vb.vb2_buf.timestamp = ktime_get();
> +	params->buff->vb.field = V4L2_FIELD_NONE;
> +	vb2_buffer_done(&params->buff->vb.vb2_buf, state);
> +
> +	spin_unlock_irqrestore(&params->buff_lock, flags);
> +
> +	return 0;
> +}
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
> new file mode 100644
> index 000000000000..de1938f7c354
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
> @@ -0,0 +1,683 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#ifndef __C3_ISP_REGS_H__
> +#define __C3_ISP_REGS_H__
> +
> +#define ISP_TOP_INPUT_SIZE                       0x0000
> +#define TOP_INPUT_HSIZE(x)                       (((x) & 0xffff) << 16)
> +#define TOP_INPUT_VSIZE(x)                       ((x) & 0xffff)
> +
> +#define ISP_TOP_FRM_SIZE                         0x0004
> +#define TOP_FRM_CORE_IHSIZE(x)                   (((x) & 0xffff) << 16)
> +#define TOP_FRM_CORE_IVSIZE(x)                   ((x) & 0xffff)
> +
> +#define ISP_TOP_HOLD_SIZE                        0x0008
> +#define TOP_HOLD_HSIZE_MASK                      GENMASK(31, 16)
> +#define TOP_HOLD_HSIZE_SHIFT                     16
> +
> +#define ISP_TOP_PATH_EN                          0x0010
> +#define TOP_DISP_EN(x)                           BIT((x))
> +#define TOP_WRMIF_EN(x)                          BIT((x) + 8)
> +
> +#define ISP_TOP_PATH_SEL                         0x0014
> +#define TOP_DATA_PATH_MASK                       GENMASK(18, 16)
> +#define TOP_PATH_MIPI_TO_CORE                    BIT(16)
> +
> +#define ISP_TOP_IRQ_EN                           0x0080
> +#define TOP_IRQ_FRAME_DONE                       BIT(0)
> +#define TOP_IRQ_STATS_ERR                        BIT(5)
> +
> +#define ISP_TOP_IRQ_CLR                          0x0084
> +#define ISP_TOP_IRQ_LINE_THRD                    0x0088
> +#define TOP_IRQ_DIN_HSYNC                        BIT(16)
> +
> +#define ISP_TOP_RO_IRQ_STAT                      0x01c4
> +#define ISP_TOP_MODE_CTRL                        0x0400
> +#define ISP_TOP_FEO_CTRL0                        0x040c
> +#define TOP_FEO_CTRL0_ALL_DIS                    0
> +#define TOP_FEO_INPUT_FMT_EN                     BIT(8)
> +
> +#define ISP_TOP_FEO_CTRL1_0                      0x0410
> +#define TOP_FEO_CTRL1_0_ALL_DIS                  0
> +
> +#define ISP_TOP_FEO_CTRL1_1                      0x0414
> +#define TOP_FEO_CTRL1_1_ALL_DIS                  0
> +
> +#define ISP_TOP_FED_CTRL                         0x0418
> +#define TOP_FED_CTRL_ALL_DIS                     0
> +
> +#define ISP_TOP_BEO_CTRL                         0x041c
> +#define TOP_BEO_CTRL_ALL_DIS                     0
> +#define TOP_BEO_CTRL_GTM_EN                      BIT(2)
> +#define TOP_BEO_CTRL_WB_EN                       BIT(6)
> +#define TOP_BEO_CTRL_BLC_EN                      BIT(7)
> +#define TOP_BEO_CTRL_IDGAIN_EN                   BIT(8)
> +#define TOP_BEO_CTRL_EOTF_EN                     BIT(9)
> +
> +#define ISP_TOP_BED_CTRL                         0x0420
> +#define TOP_BED_CTRL_ALL_DIS                     0
> +#define TOP_BED_CM0_EN                           BIT(14)
> +#define TOP_BED_GAMMA_EN                         BIT(16)
> +#define TOP_BED_CCM_EN                           BIT(18)
> +#define TOP_BED_DMSC_EN                          BIT(19)
> +
> +#define ISP_TOP_3A_STAT_CRTL                     0x0424
> +#define TOP_3A_AE_STAT_EN                        BIT(0)
> +#define TOP_3A_AWB_STAT_EN                       BIT(1)
> +#define TOP_3A_AF_STAT_EN                        BIT(2)
> +#define TOP_3A_AWB_STAT_SWITCH_MASK              GENMASK(6, 4)
> +#define TOP_3A_AWB_STAT_SWITCH_SHIFT             4
> +#define TOP_3A_AWB_STAT_SWITCH_BEFORE_WB         2
> +
> +#define TOP_3A_AE_STAT_SWITCH_MASK               GENMASK(9, 8)
> +#define TOP_3A_AE_STAT_SWITCH_SHIFT              8
> +#define TOP_3A_AE_STAT_SWITCH_FROM_MLS           1
> +#define TOP_3A_AF_STAT_SWITCH_MASK               GENMASK(13, 12)
> +#define TOP_3A_AF_STAT_SWITCH_SHIFT              12
> +#define TOP_3A_AF_STAT_SWITCH_FROM_SNR            0
> +
> +#define ISP_FED_BL_OFST_GR                       0x2018
> +#define FED_BL_GR_OFST(x)                        ((x) & 0x1fffff)
> +
> +#define ISP_FED_BL_OFST_R                        0x201c
> +#define FED_BL_R_OFST(x)                         ((x) & 0x1fffff)
> +
> +#define ISP_FED_BL_OFST_B                        0x2020
> +#define FED_BL_B_OFST(x)                         ((x) & 0x1fffff)
> +
> +#define ISP_FED_BL_OFST_GB                       0x2024
> +#define FED_BL_GB_OFST(x)                        ((x) & 0x1fffff)
> +
> +#define ISP_FED_BL_OFST_IR                       0x2028
> +#define FED_BL_IR_OFST(x)                        ((x) & 0x1fffff)
> +
> +#define ISP_LSWB_BLC_OFST0                       0x4028
> +#define LSWB_BLC_OFST1_MASK                      GENMASK(15, 0)
> +#define LSWB_BLC_OFST1(x)                        ((x) & 0xffff)
> +#define LSWB_BLC_OFST1_EXT(x)                    ((x) & 0xffff)
> +#define LSWB_BLC_OFST0_MASK                      GENMASK(31, 16)
> +#define LSWB_BLC_OFST0(x)                        (((x) & 0xffff) << 16)
> +#define LSWB_BLC_OFST0_EXT(x)                    (((x) >> 16) & 0xffff)
> +
> +#define ISP_LSWB_BLC_OFST1                       0x402c
> +#define LSWB_BLC_OFST3_MASK                      GENMASK(15, 0)
> +#define LSWB_BLC_OFST3(x)                        ((x) & 0xffff)
> +#define LSWB_BLC_OFST3_EXT(x)                    ((x) & 0xffff)
> +#define LSWB_BLC_OFST2_MASK                      GENMASK(31, 16)
> +#define LSWB_BLC_OFST2(x)                        (((x) & 0xffff) << 16)
> +#define LSWB_BLC_OFST2_EXT(x)                    (((x) >> 16) & 0xffff)
> +
> +#define ISP_LSWB_BLC_OFST2                       0x4030
> +#define LSWB_BLC_OFST4_MASK                      GENMASK(15, 0)
> +#define LSWB_BLC_OFST4(x)                        ((x) & 0xffff)
> +#define LSWB_BLC_OFST4_EXT(x)                    ((x) & 0xffff)
> +
> +#define ISP_LSWB_BLC_PHSOFST                     0x4034
> +#define LSWB_BLC_YPHS_OFST_MASK                  GENMASK(1, 0)
> +#define LSWB_BLC_XPHS_OFST_MASK                  GENMASK(3, 2)
> +#define LSWB_BLC_XPHS_OFST_SHIFT                 2
> +
> +#define ISP_LSWB_WB_GAIN0                        0x4038
> +#define LSWB_WB_GAIN1_MASK                       GENMASK(11, 0)
> +#define LSWB_WB_GAIN0_MASK                       GENMASK(27, 16)
> +#define LSWB_WB_GAIN0_SHIFT                      16
> +
> +#define ISP_LSWB_WB_GAIN1                        0x403c
> +#define LSWB_WB_GAIN3_MASK                       GENMASK(11, 0)
> +#define LSWB_WB_GAIN2_MASK                       GENMASK(27, 16)
> +#define LSWB_WB_GAIN2_SHIFT                      16
> +
> +#define ISP_LSWB_WB_GAIN2                        0x4040
> +#define LSWB_WB_GAIN4_MASK                       GENMASK(11, 0)
> +
> +#define ISP_LSWB_WB_LIMIT0                       0x4044
> +#define LSWB_WB_LIMIT1(x)                        ((x) & 0xffff)
> +#define LSWB_WB_LIMIT0(x)                        (((x) & 0xffff) << 16)
> +
> +#define ISP_LSWB_WB_LIMIT1                       0x4048
> +#define LSWB_WB_LIMIT3(x)                        ((x) & 0xffff)
> +#define LSWB_WB_LIMIT2(x)                        (((x) & 0xffff) << 16)
> +
> +#define ISP_LSWB_WB_LIMIT2                       0x404c
> +#define LSWB_WB_LIMIT4_MASK                      GENMASK(15, 0)
> +
> +#define ISP_LSWB_WB_PHSOFST                      0x4050
> +#define LSWB_WB_YPHS_OFST_MASK                   GENMASK(1, 0)
> +#define LSWB_WB_XPHS_OFST_MASK                   GENMASK(3, 2)
> +#define LSWB_WB_XPHS_OFST_SHIFT                  2
> +
> +#define ISP_LSWB_LNS_PHSOFST                     0x4054
> +#define LSWB_LNS_YPHS_OFST_MASK                  GENMASK(1, 0)
> +#define LSWB_LNS_XPHS_OFST_MASK                  GENMASK(3, 2)
> +#define LSWB_LNS_XPHS_OFST_SHIFT                 2
> +
> +#define ISP_DMS_COMMON_PARAM0                    0x5000
> +#define DMS_COMMON_YPHS_OFST_MASK                GENMASK(1, 0)
> +#define DMS_COMMON_XPHS_OFST_MASK                GENMASK(3, 2)
> +#define DMS_COMMON_XPHS_OFST_SHIFT               2
> +
> +#define ISP_CM0_INP_OFST01                       0x6040
> +#define CM0_INP_OFST0_MASK                       GENMASK(12, 0)
> +#define CM0_INP_OFST1_MASK                       GENMASK(28, 16)
> +#define CM0_INP_OFST1_SHIFT                      16
> +
> +#define ISP_CM0_INP_OFST2                        0x6044
> +#define CM0_INP_OFST2_MASK                       GENMASK(12, 0)
> +
> +#define ISP_CM0_COEF00_01                        0x6048
> +#define CM0_MTX_00_MASK                          GENMASK(12, 0)
> +#define CM0_MTX_01_MASK                          GENMASK(28, 16)
> +#define CM0_MTX_01_SHIFT                         16
> +
> +#define ISP_CM0_COEF02_10                        0x604c
> +#define CM0_MTX_02_MASK                          GENMASK(12, 0)
> +#define CM0_MTX_10_MASK                          GENMASK(28, 16)
> +#define CM0_MTX_10_SHIFT                         16
> +
> +#define ISP_CM0_COEF11_12                        0x6050
> +#define CM0_MTX_11_MASK                          GENMASK(12, 0)
> +#define CM0_MTX_12_MASK                          GENMASK(28, 16)
> +#define CM0_MTX_12_SHIFT                         16
> +
> +#define ISP_CM0_COEF20_21                        0x6054
> +#define CM0_MTX_20_MASK                          GENMASK(12, 0)
> +#define CM0_MTX_21_MASK                          GENMASK(28, 16)
> +#define CM0_MTX_21_SHIFT                         16
> +
> +#define ISP_CM0_COEF22_OUP_OFST0                 0x6058
> +#define CM0_MTX_22_MASK                          GENMASK(12, 0)
> +#define CM0_OFST_OUP0_MASK                       GENMASK(28, 16)
> +#define CM0_OFST_OUP0_SHIFT                      16
> +
> +#define ISP_CM0_OUP_OFST12_RS                    0x605c
> +#define CM0_OFST_OUP1_MASK                       GENMASK(12, 0)
> +#define CM0_OFST_OUP2_MASK                       GENMASK(28, 16)
> +#define CM0_OFST_OUP2_SHIFT                      16
> +#define CM0_MTX_RS_MASK                          GENMASK(31, 30)
> +#define CM0_MTX_RS_SHIFT                         30
> +
> +#define ISP_CCM_MTX_00_01                        0x6098
> +#define CCM_MTX_00_MASK                          GENMASK(12, 0)
> +#define CCM_MTX_01_MASK                          GENMASK(28, 16)
> +#define CCM_MTX_01_SHIFT                         16
> +
> +#define ISP_CCM_MTX_02_03                        0x609c
> +#define CCM_MTX_02_MASK                          GENMASK(12, 0)
> +#define CCM_MTX_03_MASK                          GENMASK(28, 16)
> +#define CCM_MTX_03_SHIFT                         16
> +
> +#define ISP_CCM_MTX_10_11                        0x60A0
> +#define CCM_MTX_10_MASK                          GENMASK(12, 0)
> +#define CCM_MTX_11_MASK                          GENMASK(28, 16)
> +#define CCM_MTX_11_SHIFT                         16
> +
> +#define ISP_CCM_MTX_12_13                        0x60A4
> +#define CCM_MTX_12_MASK                          GENMASK(12, 0)
> +#define CCM_MTX_13_MASK                          GENMASK(28, 16)
> +#define CCM_MTX_13_SHIFT                         16
> +
> +#define ISP_CCM_MTX_20_21                        0x60A8
> +#define CCM_MTX_20_MASK                          GENMASK(12, 0)
> +#define CCM_MTX_21_MASK                          GENMASK(28, 16)
> +#define CCM_MTX_21_SHIFT                         16
> +
> +#define ISP_CCM_MTX_22_23_RS                     0x60Ac
> +#define CCM_MTX_22_MASK                          GENMASK(12, 0)
> +#define CCM_MTX_23_MASK                          GENMASK(28, 16)
> +#define CCM_MTX_23_SHIFT                         16
> +
> +#define ISP_PST_GAMMA_MODE                       0x60C0
> +#define PST_GAMMA_MODE                           BIT(0)
> +#define PST_GAMMA_MODE_EXT(x)                    ((x) & 0x1)
> +
> +#define ISP_PST_GAMMA_LUT_ADDR                   0x60cc
> +#define PST_GAMMA_LUT_ADDR(x)                    ((x) << 7)
> +
> +#define ISP_PST_GAMMA_LUT_DATA                   0x60d0
> +#define PST_GAMMA_LUT_DATA0(x)                   ((x) & 0xffff)
> +#define PST_GAMMA_LUT_DATA1(x)                   (((x) & 0xffff) << 16)
> +
> +#define DISP0_TOP_TOP_CTRL                       0x8000
> +#define DISP_CRP2_EN                             BIT(5)
> +
> +#define DISP0_TOP_CRP2_START                     0x8004
> +#define DISP_CRP2_VSTART_MASK                    GENMASK(15, 0)
> +#define DISP_CRP2_VSTART(x)                      ((x) & 0xffff)
> +#define DISP_CRP2_HSTART_MASK                    GENMASK(31, 16)
> +#define DISP_CRP2_HSTART(x)                      (((x) & 0xffff) << 16)
> +
> +#define DISP0_TOP_CRP2_SIZE                      0x8008
> +#define DISP_CRP2_VSIZE_MASK                     GENMASK(15, 0)
> +#define DISP_CRP2_VSIZE(x)                       ((x) & 0xffff)
> +#define DISP_CRP2_HSIZE_MASK                     GENMASK(31, 16)
> +#define DISP_CRP2_HSIZE(x)                       (((x) & 0xffff) << 16)
> +
> +#define DISP0_TOP_OUT_SIZE                       0x800c
> +#define DISP_OUT_VSIZE_MASK                      GENMASK(12, 0)
> +#define DISP_OUT_HSIZE_MASK                      GENMASK(28, 16)
> +#define DISP_OUT_HSIZE_SHIFT                     16
> +
> +#define ISP_DISP0_TOP_IN_SIZE                    0x804c
> +#define DISP_TOP_IN_VSIZE(x)                     ((x) & 0x1fff)
> +#define DISP_TOP_IN_HSIZE(x)                     (((x) & 0x1fff) << 16)
> +
> +#define DISP0_PPS_SCALE_EN                       0x8200
> +#define PPS_VSC_TAP_NUM_MASK                     GENMASK(3, 0)
> +#define PPS_HSC_TAP_NUM_MASK                     GENMASK(7, 4)
> +#define PPS_HSC_TAP_NUM_SHIFT                    4
> +#define PPS_HSC_TAP_NUM_INIT                     4
> +#define PPS_PREVSC_FLT_NUM_MASK                  GENMASK(11, 8)
> +#define PPS_PREVSC_FLT_NUM_SHIFT                 8
> +#define PPS_PREHSC_FLT_NUM_MASK                  GENMASK(15, 12)
> +#define PPS_PREHSC_FLT_NUM_SHIFT                 12
> +#define PPS_PREHSC_FLT_NUM_INIT                  8
> +#define PPS_PREVSC_RATE_MASK                     GENMASK(17, 16)
> +#define PPS_PREVSC_RATE_SHIFT                    16
> +#define PPS_PREHSC_RATE_MASK                     GENMASK(19, 18)
> +#define PPS_PREHSC_RATE_SHIFT                    18
> +#define PPS_HSC_EN_MASK                          BIT(20)
> +#define PPS_HSC_EN_SHIFT                         20
> +#define PPS_VSC_EN_MASK                          BIT(21)
> +#define PPS_VSC_EN_SHIFT                         21
> +#define PPS_PREVSC_EN_MASK                       BIT(22)
> +#define PPS_PREVSC_EN_SHIFT                      22
> +#define PPS_PREHSC_EN_MASK                       BIT(23)
> +#define PPS_PREHSC_EN_SHIFT                      23
> +#define PPS_HSC_NOR_RS_BITS_MASK                 GENMASK(27, 24)
> +#define PPS_HSC_NOR_RS_BITS_SHIFT                24
> +#define PPS_HSC_NOR_RS_BITS_INIT                 9
> +#define PPS_VSC_NOR_RS_BITS_MASK                 GENMASK(31, 28)
> +#define PPS_VSC_NOR_RS_BITS_SHIFT                28
> +#define PPS_VSC_NOR_RS_BITS_INIT                 9
> +
> +#define DISP0_PPS_PRE_HSCALE_COEF_0              0x8204
> +#define PPS_PREHSC_LUMA_COEF0_MASK               GENMASK(9, 0)
> +#define PPS_PREHSC_LUMA_COEF0_INIT               128
> +#define PPS_PREHSC_LUMA_COEF1_MASK               GENMASK(25, 16)
> +#define PPS_PREHSC_LUMA_COEF1_SHIFT              16
> +#define PPS_PREHSC_LUMA_COEF1_INIT               128
> +
> +#define DISP0_PPS_PRE_HSCALE_COEF_1              0x8208
> +#define PPS_PREHSC_LUMA_COEF2_MASK               GENMASK(9, 0)
> +#define PPS_PREHSC_LUMA_COEF2_INIT               32
> +#define PPS_PREHSC_LUMA_COEF3_MASK               GENMASK(25, 16)
> +#define PPS_PREHSC_LUMA_COEF3_SHIFT              16
> +#define PPS_PREHSC_LUMA_COEF3_INIT               32
> +
> +#define DISP0_PPS_VSC_START_PHASE_STEP           0x8224
> +#define PPS_VSC_FRACTION_PART_MASK               GENMASK(23, 0)
> +#define PPS_VSC_INTEGER_PART_MASK                GENMASK(27, 24)
> +#define PPS_VSC_INTEGER_PART_SHIFT               24
> +
> +#define DISP0_PPS_HSC_START_PHASE_STEP           0x8230
> +#define PPS_HSC_FRACTION_PART_MASK               GENMASK(23, 0)
> +#define PPS_HSC_INTEGER_PART_MASK                GENMASK(27, 24)
> +#define PPS_HSC_INTEGER_PART_SHIFT               24
> +
> +#define DISP0_PPS_444TO422                       0x823c
> +#define PPS_444TO422_EN_MASK                     BIT(0)
> +
> +#define ISP_SCALE0_COEF_IDX_LUMA                 0x8240
> +#define SCALE_LUMA_COEF_S11_MODE                 BIT(9)
> +#define SCALE_LUMA_CTYPE(x)                      (((x) & 0x7) << 10)
> +
> +#define ISP_SCALE0_COEF_LUMA                     0x8244
> +#define SCALE_COEF_LUMA_DATA1(x)                 ((x) & 0x7ff)
> +#define SCALE_COEF_LUMA_DATA0(x)                 (((x) & 0x7ff) << 16)
> +
> +#define ISP_SCALE0_COEF_IDX_CHRO                 0x8248
> +#define SCALE_CHRO_COEF_S11_MODE                 BIT(9)
> +#define SCALE_CHRO_CTYPE(x)                      (((x) & 0x7) << 10)
> +
> +#define ISP_SCALE0_COEF_CHRO                     0x824c
> +#define SCALE_COEF_CHRO_DATA1(x)                 ((x) & 0x7ff)
> +#define SCALE_COEF_CHRO_DATA0(x)                 (((x) & 0x7ff) << 16)
> +
> +#define ISP_AF_ROI0_WIN01                        0xa00c
> +#define AF_ROI_XYWIN_00_INIT                     (50 & 0xffff)
> +#define AF_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
> +
> +#define ISP_AF_ROI1_WIN01                        0xa010
> +#define AF_ROI_XYWIN_10_INIT                     (100 & 0xffff)
> +#define AF_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
> +
> +#define ISP_AF_ROI0_WIN23                        0xa014
> +#define AF_ROI_XYWIN_02_INIT                     (80 & 0xffff)
> +#define AF_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
> +
> +#define ISP_AF_ROI1_WIN23                        0xa018
> +#define AF_ROI_XYWIN_12_INIT                     (80 & 0xffff)
> +#define AF_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
> +
> +#define ISP_AF_CTRL                              0xa044
> +#define AF_CTRL_YPHS_OFST_MASK                   GENMASK(15, 14)
> +#define AF_CTRL_YPHS_OFST_SHIFT                  14
> +#define AF_CTRL_XPHS_OFST_MASK                   GENMASK(17, 16)
> +#define AF_CTRL_XPHS_OFST_SHIFT                  16
> +
> +#define ISP_AF_HV_SIZE                           0xa04c
> +#define AF_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
> +#define AF_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
> +
> +#define ISP_AF_HV_BLKNUM                         0xa050
> +#define AF_HV_STAT_VBLK_NUM_MASK                 GENMASK(5, 0)
> +#define AF_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x3f)
> +#define AF_HV_STAT_HBLK_NUM_MASK                 GENMASK(21, 16)
> +#define AF_HV_STAT_HBLK_NUM_SHIFT                16
> +
> +#define ISP_AF_EN_CTRL                           0xa054
> +#define AF_STAT_SELECT                           BIT(21)
> +#define AF_STAT_SELECT_SHIFT                     21
> +
> +#define ISP_RO_AF_GLB_STAT_PCK0                  0xa0e0
> +#define ISP_RO_AF_GLB_STAT_PCK1                  0xa0e4
> +#define ISP_RO_AF_GLB_STAT_PCK2                  0xa0e8
> +#define ISP_RO_AF_GLB_STAT_PCK3                  0xa0eC
> +#define ISP_AF_IDX_ADDR                          0xa1c0
> +#define ISP_AF_IDX_DATA                          0xa1c4
> +#define AF_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
> +#define AF_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
> +
> +#define ISP_AE_ROI0_WIN01                        0xa40c
> +#define AE_ROI_XYWIN_00_INIT                     (50 & 0xffff)
> +#define AE_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
> +
> +#define ISP_AE_ROI1_WIN01                        0xa410
> +#define AE_ROI_XYWIN_10_INIT                     (100 & 0xffff)
> +#define AE_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
> +
> +#define ISP_AE_ROI0_WIN23                        0xa414
> +#define AE_ROI_XYWIN_02_INIT                     (80 & 0xffff)
> +#define AE_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
> +
> +#define ISP_AE_ROI1_WIN23                        0xa418
> +#define AE_ROI_XYWIN_12_INIT                     (80 & 0xffff)
> +#define AE_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
> +
> +#define ISP_RO_AE_ROI_STAT_PCK0_0                0xa424
> +#define ISP_RO_AE_ROI_STAT_PCK1_0                0xa428
> +#define ISP_RO_AE_ROI_STAT_PCK0_1                0xa42c
> +#define ISP_RO_AE_ROI_STAT_PCK1_1                0xa430
> +#define ISP_AE_CTRL                              0xa448
> +#define AE_CTRL_AE_STAT_LOCAL_MODE               GENMASK(3, 2)
> +#define AE_CTRL_AE_STAT_LOCAL_MODE_EXT(x)        (((x) >> 2) & 0x3)
> +#define AE_CTRL_INPUT_2LINE_TOGETHER             BIT(7)
> +#define AE_CTRL_LUMA_MODE_MASK                   GENMASK(9, 8)
> +#define AE_CTRL_LUMA_MODE_SHIFT                  8
> +#define AE_CTRL_LUMA_MODE_FILTER                 2
> +#define AE_CTRL_YPHS_OFST_MASK                   GENMASK(25, 24)
> +#define AE_CTRL_YPHS_OFST_SHIFT                  24
> +#define AE_CTRL_XPHS_OFST_MASK                   GENMASK(27, 26)
> +#define AE_CTRL_XPHS_OFST_SHIFT                  26
> +
> +#define ISP_AE_CRTL2_0                           0xa44c
> +#define AE_CRTL2_BL12_GRBGI0_MASK                GENMASK(11, 0)
> +#define AE_CRTL2_GAIN_GRBGI0_MASK                GENMASK(23, 16)
> +#define AE_CRTL2_GAIN_GRBGI0_SHIFT               16
> +
> +#define ISP_AE_CRTL2_1                           0xa450
> +#define AE_CRTL2_BL12_GRBGI1_MASK                GENMASK(11, 0)
> +#define AE_CRTL2_GAIN_GRBGI1_MASK                GENMASK(23, 16)
> +#define AE_CRTL2_GAIN_GRBGI1_SHIFT               16
> +
> +#define ISP_AE_CRTL2_2                           0xa454
> +#define AE_CRTL2_BL12_GRBGI2_MASK                GENMASK(11, 0)
> +#define AE_CRTL2_GAIN_GRBGI2_MASK                GENMASK(23, 16)
> +#define AE_CRTL2_GAIN_GRBGI2_SHIFT               16
> +
> +#define ISP_AE_CRTL2_3                           0xa458
> +#define AE_CRTL2_BL12_GRBGI3_MASK                GENMASK(11, 0)
> +#define AE_CRTL2_GAIN_GRBGI3_MASK                GENMASK(23, 16)
> +#define AE_CRTL2_GAIN_GRBGI3_SHIFT               16
> +
> +#define ISP_AE_CRTL2_4                           0xa45C
> +#define AE_CRTL2_BL12_GRBGI4_MASK                GENMASK(11, 0)
> +#define AE_CRTL2_GAIN_GRBGI4_MASK                GENMASK(23, 16)
> +#define AE_CRTL2_GAIN_GRBGI4_SHIFT               16
> +
> +#define ISP_AE_HV_SIZE                           0xa464
> +#define AE_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
> +#define AE_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
> +
> +#define ISP_AE_HV_BLKNUM                         0xa468
> +#define AE_HV_STAT_VBLK_NUM_MASK                 GENMASK(6, 0)
> +#define AE_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x7f)
> +#define AE_HV_STAT_HBLK_NUM_MASK                 GENMASK(22, 16)
> +#define AE_HV_STAT_HBLK_NUM_SHIFT                16
> +
> +#define ISP_AE_STAT_THD01                        0xa46c
> +#define AE_STAT_THRD0_INIT                       (13107 & 0xffff)
> +#define AE_STAT_THRD1_INIT                       ((26214 & 0xffff) << 16)
> +
> +#define ISP_AE_STAT_THD23                        0xa470
> +#define AE_STAT_THRD2_INIT                       (39321 & 0xffff)
> +#define AE_STAT_THRD3_INIT                       ((52428 & 0xffff) << 16)
> +
> +#define ISP_RO_AE_STAT_GPNUM                     0xa49c
> +#define ISP_AE_IDX_ADDR                          0xa600
> +#define ISP_AE_IDX_DATA                          0xa604
> +#define AE_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
> +#define AE_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
> +
> +#define ISP_AE_BLK_WT_ADDR                       0xa608
> +#define ISP_AE_BLK_WT_DATA                       0xa60c
> +#define AE_BLK_WT_DATA0(x)                       ((x) & 0xf)
> +#define AE_BLK_WT_DATA1(x)                       (((x) & 0xf) << 4)
> +#define AE_BLK_WT_DATA2(x)                       (((x) & 0xf) << 8)
> +#define AE_BLK_WT_DATA3(x)                       (((x) & 0xf) << 12)
> +#define AE_BLK_WT_DATA4(x)                       (((x) & 0xf) << 16)
> +#define AE_BLK_WT_DATA5(x)                       (((x) & 0xf) << 20)
> +#define AE_BLK_WT_DATA6(x)                       (((x) & 0xf) << 24)
> +#define AE_BLK_WT_DATA7(x)                       (((x) & 0xf) << 28)
> +
> +#define ISP_AWB_CTRL                             0xa834
> +#define AWB_CTRL_YPHS_OFST_MASK                  GENMASK(1, 0)
> +#define AWB_CTRL_XPHS_OFST_MASK                  GENMASK(3, 2)
> +#define AWB_CTRL_XPHS_OFST_SHIFT                 2
> +#define AWB_CTRL_STAT_RATIO_MODE                 BIT(4)
> +
> +#define ISP_AWB_HV_SIZE                          0xa83c
> +#define AWB_HV_STAT_VSIZE(x)                     ((x) & 0xffff)
> +#define AWB_HV_STAT_HSIZE(x)                     (((x) & 0xffff) << 16)
> +
> +#define ISP_AWB_STAT_ROI0_WIN01                  0xa80c
> +#define AWB_ROI_XYWIN_00_INIT                    (32 & 0xffff)
> +#define AWB_ROI_XYWIN_01_INIT                    ((62 & 0xffff) << 16)
> +
> +#define ISP_AWB_STAT_ROI1_WIN01                  0xa810
> +#define AWB_ROI_XYWIN_10_INIT                    (32 & 0xffff)
> +#define AWB_ROI_XYWIN_11_INIT                    ((82 & 0xffff) << 16)
> +
> +#define ISP_AWB_STAT_ROI0_WIN23                  0xa814
> +#define AWB_ROI_XYWIN_02_INIT                    (32 & 0xffff)
> +#define AWB_ROI_XYWIN_03_INIT                    ((42 & 0xffff) << 16)
> +
> +#define ISP_AWB_STAT_ROI1_WIN23                  0xa818
> +#define AWB_ROI_XYWIN_12_INIT                    (64 & 0xffff)
> +#define AWB_ROI_XYWIN_13_INIT                    ((52 & 0xffff) << 16)
> +
> +#define ISP_AWB_HV_BLKNUM                        0xa840
> +#define AWB_HV_STAT_VBLK_NUM_MASK                GENMASK(5, 0)
> +#define AWB_HV_STAT_VBLK_NUM_EXT(x)              ((x) & 0x3f)
> +#define AWB_HV_STAT_HBLK_NUM_MASK                GENMASK(21, 16)
> +#define AWB_HV_STAT_HBLK_NUM_SHIFT               16
> +
> +#define ISP_AWB_STAT_RG                          0xa848
> +#define AWB_STAT_RG_MIN_MASK                     GENMASK(11, 0)
> +#define AWB_STAT_RG_MAX_MASK                     GENMASK(27, 16)
> +#define AWB_STAT_RG_MAX_SHIFT                    16
> +
> +#define ISP_AWB_STAT_BG                          0xa84c
> +#define AWB_STAT_BG_MIN_MASK                     GENMASK(11, 0)
> +#define AWB_STAT_BG_MAX_MASK                     GENMASK(27, 16)
> +#define AWB_STAT_BG_MAX_SHIFT                    16
> +
> +#define ISP_AWB_STAT_RG_HL                       0xa850
> +#define AWB_STAT_RG_LOW_MASK                     GENMASK(11, 0)
> +#define AWB_STAT_RG_HIGH_MASK                    GENMASK(27, 16)
> +#define AWB_STAT_RG_HIGH_SHIFT                   16
> +
> +#define ISP_AWB_STAT_BG_HL                       0xa854
> +#define AWB_STAT_BG_LOW_MASK                     GENMASK(11, 0)
> +#define AWB_STAT_BG_HIGH_MASK                    GENMASK(27, 16)
> +#define AWB_STAT_BG_HIGH_SHIFT                   16
> +
> +#define ISP_AWB_STAT_CTRL2                       0xa858
> +#define AWB_STAT_SATUR_VALID_MASK                BIT(0)
> +#define AWB_STAT_LOCAL_MODE                      BIT(2)
> +#define AWB_STAT_LOCAL_MODE_EXT(x)               (((x) >> 2) & 0x1)
> +#define AWB_STAT_LUMA_DIV_MODE                   GENMASK(4, 3)
> +#define AWB_STAT_LUMA_DIV_MODE_EXT(x)            (((x) >> 3) & 0x3)
> +
> +#define ISP_AWB_STAT_BLC20_0                     0xa85c
> +#define AWB_STAT_BLC20_GR_MASK                   GENMASK(19, 0)
> +
> +#define ISP_AWB_STAT_BLC20_1                     0xa860
> +#define AWB_STAT_BLC20_R_MASK                    GENMASK(19, 0)
> +
> +#define ISP_AWB_STAT_BLC20_2                     0xa864
> +#define AWB_STAT_BLC20_B_MASK                    GENMASK(19, 0)
> +
> +#define ISP_AWB_STAT_BLC20_3                     0xa868
> +#define AWB_STAT_BLC20_GB_MASK                   GENMASK(19, 0)
> +
> +#define ISP_AWB_STAT_GAIN10_0                    0xa86c
> +#define AWB_STAT_GAIN10_GR_MASK                  GENMASK(9, 0)
> +
> +#define ISP_AWB_STAT_GAIN10_1                    0xa870
> +#define AWB_STAT_GAIN10_R_MASK                   GENMASK(9, 0)
> +
> +#define ISP_AWB_STAT_GAIN10_2                    0xa874
> +#define AWB_STAT_GAIN10_B_MASK                   GENMASK(9, 0)
> +
> +#define ISP_AWB_STAT_GAIN10_3                    0xa878
> +#define AWB_STAT_GAIN10_GB_MASK                  GENMASK(9, 0)
> +
> +#define ISP_AWB_STAT_SATUR_CTRL                  0xa884
> +#define AWB_STAT_SATUR_LOW(x)                    ((x) & 0xffff)
> +#define AWB_STAT_SATUR_HIGH(x)                   (((x) & 0xffff) << 16)
> +
> +#define ISP_AWB_IDX_ADDR                         0xaa00
> +#define ISP_AWB_IDX_DATA                         0xaa04
> +#define AWB_IDX_VIDX_DATA(x)                     ((x) & 0xffff)
> +#define AWB_IDX_HIDX_DATA(x)                     (((x) & 0xffff) << 16)
> +
> +#define ISP_AWB_BLK_WT_ADDR                      0xaa08
> +#define ISP_AWB_BLK_WT_DATA                      0xaa0c
> +#define AWB_BLK_WT_DATA0(x)                      ((x) & 0xf)
> +#define AWB_BLK_WT_DATA1(x)                      (((x) & 0xf) << 4)
> +#define AWB_BLK_WT_DATA2(x)                      (((x) & 0xf) << 8)
> +#define AWB_BLK_WT_DATA3(x)                      (((x) & 0xf) << 12)
> +#define AWB_BLK_WT_DATA4(x)                      (((x) & 0xf) << 16)
> +#define AWB_BLK_WT_DATA5(x)                      (((x) & 0xf) << 20)
> +#define AWB_BLK_WT_DATA6(x)                      (((x) & 0xf) << 24)
> +#define AWB_BLK_WT_DATA7(x)                      (((x) & 0xf) << 28)
> +
> +#define ISP_WRMIFX3_0_CH0_CTRL0                  0xc400
> +#define WRMIFX3_CH0_STRIDE_MASK                  GENMASK(28, 16)
> +#define WRMIFX3_CH0_STRIDE_SHIFT                 16
> +
> +#define ISP_WRMIFX3_0_CH0_CTRL1                  0xc404
> +#define WRMIFX3_CH0_PIX_BITS_MODE_MASK           GENMASK(30, 27)
> +#define WRMIFX3_CH0_PIX_BITS_MODE_SHIFT          27
> +#define WRMIFX3_CH0_PIX_BITS_8BITS               1
> +#define WRMIFX3_CH0_PIX_BITS_16BITS              2
> +
> +#define ISP_WRMIFX3_0_CH1_CTRL0                  0xc408
> +#define WRMIFX3_CH1_STRIDE_MASK                  GENMASK(28, 16)
> +#define WRMIFX3_CH1_STRIDE_SHIFT                 16
> +
> +#define ISP_WRMIFX3_0_CH1_CTRL1                  0xc40c
> +#define WRMIFX3_CH1_PIX_BITS_MODE_MASK           GENMASK(30, 27)
> +#define WRMIFX3_CH1_PIX_BITS_MODE_SHIFT          27
> +#define WRMIFX3_CH1_PIX_BITS_16BITS              2
> +#define WRMIFX3_CH1_PIX_BITS_32BITS              3
> +
> +#define ISP_WRMIFX3_0_CH0_BADDR                  0xc440
> +/* WRMIF base address need 16 bits alignment */
> +#define WRMIFX3_CH0_BADDR(x)                     (((x) >> 4) & 0xffffffff)
> +
> +#define ISP_WRMIFX3_0_CH1_BADDR                  0xc444
> +#define WRMIFX3_CH1_BADDR(x)                     (((x) >> 4) & 0xffffffff)
> +
> +#define ISP_WRMIFX3_0_FMT_SIZE                   0xc464
> +#define WRMIFX3_FMT_HSIZE(x)                     ((x) & 0xffff)
> +#define WRMIFX3_FMT_VSIZE(x)                     (((x) & 0xffff) << 16)
> +
> +#define ISP_WRMIFX3_0_FMT_CTRL                   0xc468
> +#define WRMIFX3_FMT_MTX_IBITS_MASK               GENMASK(1, 0)
> +#define WRMIFX3_FMT_MTX_IBITS_8BIT               0
> +#define WRMIFX3_FMT_MTX_IBITS_10BIT              1
> +#define WRMIFX3_FMT_MTX_IBITS_12BIT              2
> +#define WRMIFX3_FMT_MTX_IBITS_16BIT              3
> +#define WRMIFX3_FMT_MTX_UV_SWAP_MASK             BIT(2)
> +#define WRMIFX3_FMT_MTX_UV_SWAP_SHIFT            2
> +#define WRMIFX3_FMT_MTX_UV_SWAP_VU               0
> +#define WRMIFX3_FMT_MTX_UV_SWAP_UV               1
> +#define WRMIFX3_FMT_MTX_PLANE_MASK               GENMASK(5, 4)
> +#define WRMIFX3_FMT_MTX_PLANE_SHIFT              4
> +#define WRMIFX3_FMT_MTX_PLANE_X1                 0
> +#define WRMIFX3_FMT_MTX_PLANE_X2                 1
> +#define WRMIFX3_FMT_MODE_OUT_MASK                GENMASK(18, 16)
> +#define WRMIFX3_FMT_MODE_OUT_SHIFT               16
> +#define WRMIFX3_FMT_MODE_OUT_YUV422              1
> +#define WRMIFX3_FMT_MODE_OUT_YUV420              2
> +#define WRMIFX3_FMT_MODE_OUT_Y_ONLY              3
> +
> +#define ISP_WRMIFX3_0_WIN_LUMA_H                 0xc420
> +#define WRMIFX3_WIN_LUMA_HEND_MASK               GENMASK(28, 16)
> +#define WRMIFX3_WIN_LUMA_HEND_SHIFT              16
> +#define WRMIFX3_WIN_LUMA_HEND(x)                 ((x) - 1)
> +
> +#define ISP_WRMIFX3_0_WIN_LUMA_V                 0xc424
> +#define WRMIFX3_WIN_LUMA_VEND_MASK               GENMASK(28, 16)
> +#define WRMIFX3_WIN_LUMA_VEND_SHIFT              16
> +#define WRMIFX3_WIN_LUMA_VEND(x)                 ((x) - 1)
> +
> +#define ISP_WRMIFX3_0_WIN_CHROM_H                0xc428
> +#define WRMIFX3_WIN_CHROM_HEND_MASK              GENMASK(28, 16)
> +#define WRMIFX3_WIN_CHROM_HEND_SHIFT             16
> +#define WRMIFX3_WIN_CHROM_HEND(x)                (((x) >> 1) - 1)
> +
> +#define ISP_WRMIFX3_0_WIN_CHROM_V                0xc42c
> +#define WRMIFX3_WIN_CHROM_VEND_MASK              GENMASK(28, 16)
> +#define WRMIFX3_WIN_CHROM_VEND_SHIFT             16
> +#define WRMIFX3_WIN_CHROM_VEND(x)                (((x) >> 1) - 1)
> +
> +#define ISP_WRMIFX3_0_CRP_HSIZE                  0xc48c
> +#define WRMIFX3_CROP_HEND_MASK                   GENMASK(31, 16)
> +#define WRMIFX3_CROP_HEND_SHIFT                  16
> +#define WRMIFX3_CROP_HEND(x)                     ((x) - 1)
> +
> +#define ISP_WRMIFX3_0_CRP_VSIZE                  0xc490
> +#define WRMIFX3_CROP_VEND_MASK                   GENMASK(31, 16)
> +#define WRMIFX3_CROP_VEND_SHIFT                  16
> +#define WRMIFX3_CROP_VEND(x)                     ((x) - 1)
> +
> +#define VIU_DMAWR_BADDR0                         0xc840
> +#define VIU_DMAWR_AF_BADDR_MASK                  GENMASK(27, 0)
> +/* AF base address need 16 bits alignment */
> +#define VIU_DMAWR_AF_BADDR(x)                    ((x) >> 4)
> +
> +#define VIU_DMAWR_BADDR1                         0xc844
> +#define VIU_DMAWR_AWB_BADDR_MASK                  GENMASK(27, 0)
> +/* AWB base address need 16 bits alignment */
> +#define VIU_DMAWR_AWB_BADDR(x)                    ((x) >> 4)
> +
> +#define VIU_DMAWR_BADDR2                         0xc848
> +#define VIU_DMAWR_AE_BADDR_MASK                  GENMASK(27, 0)
> +/* AE base address need 16 bits alignment */
> +#define VIU_DMAWR_AE_BADDR(x)                    ((x) >> 4)
> +
> +#define VIU_DMAWR_SIZE0                          0xc854
> +#define VIU_DMAWR_SIZE_AF_MASK                   GENMASK(15, 0)
> +#define VIU_DMAWR_SIZE_AWB_MASK                  GENMASK(31, 16)
> +#define VIU_DMAWR_SIZE_AWB_SHIFT                 16
> +
> +#define VIU_DMAWR_SIZE1                          0xc858
> +#define VIU_DMAWR_SIZE_AE_MASK                   GENMASK(15, 0)
> +
> +#endif
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
> new file mode 100644
> index 000000000000..01d99b66cb32
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
> @@ -0,0 +1,768 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/pm_runtime.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +
> +static const struct c3_isp_mbus_format_info c3_isp_rsz_mbus_formats[] = {
> +	/* YUV formats */
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_Y8_1X8,
> +		.pads		= BIT(C3_ISP_RESIZER_PAD_SINK)
> +				| BIT(C3_ISP_RESIZER_PAD_SOURCE),
> +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_YUYV8_1_5X8,
> +		.pads		= BIT(C3_ISP_RESIZER_PAD_SINK)
> +				| BIT(C3_ISP_RESIZER_PAD_SOURCE),
> +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> +	}, {
> +		.mbus_code	= MEDIA_BUS_FMT_YUYV8_2X8,
> +		.pads		= BIT(C3_ISP_RESIZER_PAD_SINK)
> +				| BIT(C3_ISP_RESIZER_PAD_SOURCE),
> +		.xofst		= C3_ISP_PHASE_OFFSET_NONE,
> +		.yofst		= C3_ISP_PHASE_OFFSET_NONE,
> +	},
> +};
> +
> +/* The normal parameters of pps module */
> +static const int pps_lut_tap4_s11[C3_PPS_TAP4_S11_H_NUM][4] =  {
> +	{  0, 511,   0,   0}, { -5, 511,   5,   0}, {-10, 511,  11,   0},
> +	{-14, 510,  17,  -1}, {-18, 508,  23,  -1}, {-22, 506,  29,  -1},
> +	{-25, 503,  36,  -2}, {-28, 500,  43,  -3}, {-32, 496,  51,  -3},
> +	{-34, 491,  59,  -4}, {-37, 487,  67,  -5}, {-39, 482,  75,  -6},
> +	{-41, 476,  84,  -7}, {-42, 470,  92,  -8}, {-44, 463, 102,  -9},
> +	{-45, 456, 111, -10}, {-45, 449, 120, -12}, {-47, 442, 130, -13},
> +	{-47, 434, 140, -15}, {-47, 425, 151, -17}, {-47, 416, 161, -18},
> +	{-47, 407, 172, -20}, {-47, 398, 182, -21}, {-47, 389, 193, -23},
> +	{-46, 379, 204, -25}, {-45, 369, 215, -27}, {-44, 358, 226, -28},
> +	{-43, 348, 237, -30}, {-43, 337, 249, -31}, {-41, 326, 260, -33},
> +	{-40, 316, 271, -35}, {-39, 305, 282, -36}, {-37, 293, 293, -37}
> +};
> +
> +static const struct c3_isp_mbus_format_info
> +*rsz_find_format_by_code(u32 code, u32 pad)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(c3_isp_rsz_mbus_formats); i++) {
> +		const struct c3_isp_mbus_format_info *info =
> +			&c3_isp_rsz_mbus_formats[i];
> +
> +		if (info->mbus_code == code && info->pads & BIT(pad))
> +			return info;
> +	}
> +
> +	return NULL;
> +}
> +
> +static void c3_isp_rsz_cfg_fmt(struct c3_isp_resizer *rsz,
> +			       struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> +
> +	c3_isp_write(rsz->isp, C3_DISP_REG(ISP_DISP0_TOP_IN_SIZE, rsz->id),
> +		     DISP_TOP_IN_HSIZE(fmt->width) | DISP_TOP_IN_VSIZE(fmt->height));
> +}
> +
> +static void c3_isp_rsz_crop_enable(struct c3_isp_resizer *rsz,
> +				   struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_rect *crop;
> +
> +	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> +
> +	c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_START, rsz->id),
> +		     DISP_CRP2_VSTART(crop->top) | DISP_CRP2_HSTART(crop->left));
> +	c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_SIZE, rsz->id),
> +		     DISP_CRP2_VSIZE(crop->height) | DISP_CRP2_HSIZE(crop->width));
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
> +			   DISP_CRP2_EN, DISP_CRP2_EN);
> +}
> +
> +static void c3_isp_rsz_pps_size(struct c3_isp_resizer *rsz,
> +				struct c3_isp_pps_io_size *io_size)
> +{
> +	int thsize = io_size->thsize;
> +	int tvsize = io_size->tvsize;
> +	u32 ohsize = io_size->ohsize;
> +	u32 ovsize = io_size->ovsize;
> +	u32 ihsize = io_size->ihsize;
> +	u32 max_hsize = io_size->max_hsize;
> +	int step_h_integer, step_v_integer;
> +	int step_h_fraction, step_v_fraction;
> +	int yuv444to422_en;
> +
> +	/* Calculate the integer part of horizonal scaler step */
> +	step_h_integer = thsize / ohsize;
> +
> +	/* Calculate the vertical part of horizonal scaler step */
> +	step_v_integer = tvsize / ovsize;
> +
> +	/*
> +	 * Calculate the fraction part of horizonal scaler step.
> +	 * step_h_fraction = (source / dest) * 2^24,
> +	 * so step_h_fraction = ((source << 12) / dest) << 12.
> +	 */
> +	step_h_fraction = ((thsize << 12) / ohsize) << 12;
> +
> +	/*
> +	 * Calculate the fraction part of vertical scaler step
> +	 * step_v_fraction = (source / dest) * 2^24,
> +	 * so step_v_fraction = ((source << 12) / dest) << 12.
> +	 */
> +	step_v_fraction = ((tvsize << 12) / ovsize) << 12;
> +
> +	yuv444to422_en = ihsize > (max_hsize / 2) ? 1 : 0;
> +
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_444TO422, rsz->id),
> +			   PPS_444TO422_EN_MASK, yuv444to422_en);
> +
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
> +			   PPS_VSC_FRACTION_PART_MASK, step_v_fraction);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
> +			   PPS_VSC_INTEGER_PART_MASK,
> +			   step_v_integer << PPS_VSC_INTEGER_PART_SHIFT);
> +
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
> +			   PPS_HSC_FRACTION_PART_MASK, step_h_fraction);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
> +			   PPS_HSC_INTEGER_PART_MASK,
> +			   step_h_integer << PPS_HSC_INTEGER_PART_SHIFT);
> +
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
> +			   PPS_PREHSC_LUMA_COEF0_MASK, PPS_PREHSC_LUMA_COEF0_INIT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
> +			   PPS_PREHSC_LUMA_COEF1_MASK,
> +			   PPS_PREHSC_LUMA_COEF1_INIT << PPS_PREHSC_LUMA_COEF1_SHIFT);
> +
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
> +			   PPS_PREHSC_LUMA_COEF2_MASK, PPS_PREHSC_LUMA_COEF2_INIT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
> +			   PPS_PREHSC_LUMA_COEF3_MASK,
> +			   PPS_PREHSC_LUMA_COEF3_INIT << PPS_PREHSC_LUMA_COEF3_SHIFT);
> +}
> +
> +static void c3_isp_rsz_pps_lut(struct c3_isp_resizer *rsz, u32 ctype)
> +{
> +	int i;
> +
> +	/*
> +	 * Default value of this register is 0,
> +	 * so only need to set SCALE_LUMA_COEF_S11_MODE
> +	 * and SCALE_LUMA_CTYPE.
> +	 * This register needs to be written in one time.
> +	 */
> +	c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_LUMA, rsz->id),
> +		     SCALE_LUMA_COEF_S11_MODE | SCALE_LUMA_CTYPE(ctype));
> +
> +	for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
> +		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
> +			     SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
> +			     SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
> +		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
> +			     SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
> +			     SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][3]));
> +	}
> +
> +	/*
> +	 * Default value of this register is 0,
> +	 * so only need to set SCALE_CHRO_COEF_S11_MODE
> +	 * and SCALE_CHRO_CTYPE.
> +	 * This register needs to be written in one time.
> +	 */
> +	c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_CHRO, rsz->id),
> +		     SCALE_CHRO_COEF_S11_MODE | SCALE_CHRO_CTYPE(ctype));
> +
> +	for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
> +		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
> +			     SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
> +			     SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
> +		c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
> +			     SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
> +			     SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][3]));
> +	}
> +}
> +
> +static void c3_isp_rsz_pps_disable(struct c3_isp_resizer *rsz)
> +{
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_HSC_EN_MASK, 0);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_VSC_EN_MASK, 0);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREVSC_EN_MASK, 0);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREHSC_EN_MASK, 0);
> +}
> +
> +static int c3_isp_rsz_pps_enable(struct c3_isp_resizer *rsz,
> +				 struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_rect *crop;
> +	struct v4l2_rect *cmps;
> +	int max_hsize;
> +	int hsc_en, vsc_en;
> +	int preh_en, prev_en;
> +	u32 reg_prehsc_rate;
> +	u32 reg_prevsc_flt_num;
> +	int pre_vscale_max_hsize;
> +	u32 ihsize_after_pre_hsc;
> +	u32 ihsize_after_pre_hsc_alt;
> +	u32 reg_vsc_tap_num_alt;
> +	u32 ihsize;
> +	u32 ivsize;
> +	struct c3_isp_pps_io_size io_size;
> +
> +	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> +	cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> +
> +	ihsize = crop->width;
> +	ivsize = crop->height;
> +
> +	hsc_en = (ihsize == cmps->width) ? C3_SCALE_DIS : C3_SCALE_EN;
> +	vsc_en = (ivsize == cmps->height) ? C3_SCALE_DIS : C3_SCALE_EN;
> +
> +	/* Disable pps when there no need to use pps */
> +	if (!hsc_en && !vsc_en) {
> +		c3_isp_rsz_pps_disable(rsz);
> +		return 0;
> +	}
> +
> +	/*
> +	 * Pre-scale needs to be enable
> +	 * if the down scaling factor exceeds 4.
> +	 */
> +	preh_en = (ihsize > cmps->width * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
> +	prev_en = (ivsize > cmps->height * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
> +
> +	if (rsz->id == C3_ISP_RSZ_2) {
> +		max_hsize = C3_ISP_MAX_WIDTH;
> +		/*
> +		 * Set vertical tap number and
> +		 * the max hsize of pre-vertical scale.
> +		 */
> +		reg_prevsc_flt_num = 4;
> +		pre_vscale_max_hsize = max_hsize / 2;
> +	} else {
> +		max_hsize = C3_ISP_DEFAULT_WIDTH;
> +		preh_en  = (ihsize > max_hsize) ? C3_SCALE_EN : C3_SCALE_DIS;
> +		/*
> +		 * Set vertical tap number and
> +		 * the max hsize of pre-vertical scale.
> +		 */
> +		if (ihsize > (max_hsize / 2) &&
> +		    ihsize <= max_hsize && prev_en) {
> +			reg_prevsc_flt_num = 2;
> +			pre_vscale_max_hsize = max_hsize;
> +		} else {
> +			reg_prevsc_flt_num = 4;
> +			pre_vscale_max_hsize = max_hsize / 2;
> +		}
> +	}
> +
> +	/*
> +	 * Set pre-horizonal scale rate and
> +	 * the hsize of after pre-horizonal scale.
> +	 */
> +	if (preh_en) {
> +		reg_prehsc_rate = 1;
> +		ihsize_after_pre_hsc = DIV_ROUND_UP(ihsize, 2);
> +	} else {
> +		reg_prehsc_rate = 0;
> +		ihsize_after_pre_hsc = ihsize;
> +	}
> +
> +	/* Change pre-horizonal scale rate */
> +	if (prev_en && ihsize_after_pre_hsc >= pre_vscale_max_hsize)
> +		reg_prehsc_rate += 1;
> +
> +	/* Set the actual hsize of after pre-horizonal scale */
> +	if (preh_en)
> +		ihsize_after_pre_hsc_alt =
> +			DIV_ROUND_UP(ihsize, 1 << reg_prehsc_rate);
> +	else
> +		ihsize_after_pre_hsc_alt = ihsize;
> +
> +	/* Set vertical scaler bank length */
> +	if (ihsize_after_pre_hsc_alt <= (max_hsize / 2))
> +		reg_vsc_tap_num_alt = 4;
> +	else if (ihsize_after_pre_hsc_alt <= max_hsize)
> +		reg_vsc_tap_num_alt = prev_en ? 2 : 4;
> +	else
> +		reg_vsc_tap_num_alt = prev_en ? 4 : 2;
> +
> +	io_size.thsize = ihsize_after_pre_hsc_alt;
> +	io_size.tvsize = prev_en ? DIV_ROUND_UP(ivsize, 2) : ivsize;
> +	io_size.ohsize = cmps->width;
> +	io_size.ovsize = cmps->height;
> +	io_size.ihsize = ihsize;
> +	io_size.max_hsize = max_hsize;
> +
> +	c3_isp_rsz_pps_size(rsz, &io_size);
> +	c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_0);
> +	c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_2);
> +
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_VSC_TAP_NUM_MASK, reg_vsc_tap_num_alt);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_HSC_TAP_NUM_MASK,
> +			   PPS_HSC_TAP_NUM_INIT << PPS_HSC_TAP_NUM_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREVSC_FLT_NUM_MASK,
> +			   reg_prevsc_flt_num << PPS_PREVSC_FLT_NUM_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREHSC_FLT_NUM_MASK,
> +			   PPS_PREHSC_FLT_NUM_INIT << PPS_PREHSC_FLT_NUM_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREVSC_RATE_MASK, prev_en << PPS_PREVSC_RATE_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREHSC_RATE_MASK, reg_prehsc_rate << PPS_PREHSC_RATE_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_HSC_EN_MASK, hsc_en << PPS_HSC_EN_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_VSC_EN_MASK, vsc_en << PPS_VSC_EN_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREVSC_EN_MASK, prev_en << PPS_PREVSC_EN_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_PREHSC_EN_MASK, preh_en << PPS_PREHSC_EN_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_HSC_NOR_RS_BITS_MASK,
> +			   PPS_HSC_NOR_RS_BITS_INIT << PPS_HSC_NOR_RS_BITS_SHIFT);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> +			   PPS_VSC_NOR_RS_BITS_MASK,
> +			   PPS_VSC_NOR_RS_BITS_INIT << PPS_VSC_NOR_RS_BITS_SHIFT);
> +
> +	return 0;
> +}
> +
> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz)
> +{
> +	struct v4l2_subdev_state *state;
> +
> +	state = v4l2_subdev_lock_and_get_active_state(&rsz->sd);
> +
> +	c3_isp_rsz_cfg_fmt(rsz, state);
> +	c3_isp_rsz_crop_enable(rsz, state);
> +	c3_isp_rsz_pps_enable(rsz, state);
> +
> +	c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN,
> +			   TOP_DISP_EN(rsz->id), TOP_DISP_EN(rsz->id));
> +
> +	v4l2_subdev_unlock_state(state);
> +}
> +
> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
> +{
> +	c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, TOP_DISP_EN(rsz->id), 0);
> +	c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
> +			   DISP_CRP2_EN, 0x0);
> +
> +	c3_isp_rsz_pps_disable(rsz);


I would suggest enable_streams() / disable_streams() for these subdevices too, which gives you the 
state already

> +}
> +
> +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state,
> +				  struct v4l2_subdev_krouting *routing)
> +{
> +	static const struct v4l2_mbus_framefmt format = {
> +		.width = C3_ISP_DEFAULT_WIDTH,
> +		.height = C3_ISP_DEFAULT_HEIGHT,
> +		.code = C3_ISP_RSZ_DEF_PAD_FMT,
> +		.field = V4L2_FIELD_NONE,
> +		.colorspace = V4L2_COLORSPACE_SRGB,
> +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
> +		.xfer_func = V4L2_XFER_FUNC_SRGB,
> +	};
> +	int ret;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes;
> +	struct v4l2_subdev_krouting routing;
> +
> +	routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
> +	routes.sink_stream = 0;
> +	routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
> +	routes.source_stream = 0;
> +	routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> +
> +	routing.num_routes = 1;
> +	routing.routes = &routes;
> +
> +	return c3_isp_rsz_cfg_routing(sd, state, &routing);
> +}
> +
> +static int c3_isp_rsz_set_routing(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state,
> +				  enum v4l2_subdev_format_whence which,
> +				  struct v4l2_subdev_krouting *routing)
> +{
> +	bool is_streaming = v4l2_subdev_is_streaming(sd);
> +
> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> +		return -EBUSY;
> +
> +	return c3_isp_rsz_cfg_routing(sd, state, routing);
> +}
> +
> +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
> +				     struct v4l2_subdev_state *state,
> +				     struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	if (code->index >= ARRAY_SIZE(c3_isp_rsz_mbus_formats))
> +		return -EINVAL;
> +
> +	code->code = c3_isp_rsz_mbus_formats[code->index].mbus_code;
> +
> +	return 0;
> +}
> +
If there's only a single sink and source pad I don't think the routing functions are needed
> +static void c3_isp_rsz_set_sink_fmt(struct v4l2_subdev_state *state,
> +				    struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	const struct c3_isp_mbus_format_info *isp_fmt;
> +	struct v4l2_rect *sink_crop;
> +	struct v4l2_rect *sink_cmps;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
> +	sink_crop = v4l2_subdev_state_get_crop(state, format->pad);
> +	sink_cmps = v4l2_subdev_state_get_compose(state, format->pad);
> +
> +	isp_fmt = rsz_find_format_by_code(format->format.code, format->pad);
> +	if (!isp_fmt)
> +		sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> +	else
> +		sink_fmt->code = format->format.code;
> +
> +	sink_fmt->width = clamp_t(u32, format->format.width,
> +				  C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> +	sink_fmt->height = clamp_t(u32, format->format.height,
> +				   C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> +
> +	sink_crop->width = sink_fmt->width;
> +	sink_crop->height = sink_fmt->height;
> +	sink_crop->left = 0;
> +	sink_crop->top = 0;
> +
> +	sink_cmps->width = sink_crop->width;
> +	sink_cmps->height = sink_crop->height;
> +	sink_cmps->left = 0;
> +	sink_cmps->top = 0;
> +
> +	format->format = *sink_fmt;
> +}
You should propagate to the source pad here too
> +
> +static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state,
> +				      struct v4l2_subdev_format *format)
> +{
> +	const struct c3_isp_mbus_format_info *rsz_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;
> +	struct v4l2_rect *sink_crop;
> +	struct v4l2_rect *sink_cmps;
> +
> +	src_fmt = v4l2_subdev_state_get_format(state, format->pad);
> +	sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> +	sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> +
> +	rsz_fmt = rsz_find_format_by_code(format->format.code, format->pad);
> +	if (!rsz_fmt)
> +		src_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> +	else
> +		src_fmt->code = format->format.code;
Can the ISP convert between the YUV formats? If not I'd expect it the source pad to take the same 
mbus code as the sink pad format
> +
> +	src_fmt->width = clamp_t(u32, format->format.width,
> +				 C3_ISP_MIN_WIDTH, sink_crop->width);
> +	src_fmt->height = clamp_t(u32, format->format.height,
> +				  C3_ISP_MIN_HEIGHT, sink_crop->height);
> +
> +	/* The sink compose size must be same with the source size. */
> +	sink_cmps->width = src_fmt->width;
> +	sink_cmps->height = src_fmt->height;
It should work the other way round - the .set_fmt() for the sink pad should update the source pad 
format instead
> +
> +	format->format = *src_fmt;
> +}
> +
> +static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd,
> +			      struct v4l2_subdev_state *state,
> +			      struct v4l2_subdev_format *format)
> +{
> +	if (format->pad == C3_ISP_RESIZER_PAD_SINK) {
> +		c3_isp_rsz_set_sink_fmt(state, format);
> +	} else if (format->pad == C3_ISP_RESIZER_PAD_SOURCE) {
> +		c3_isp_rsz_set_source_fmt(state, format);
> +	} else {
> +		dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
> +		return -ENOTTY;
> +	}
v4l2-core should already have checked that I believe
> +
> +	return 0;
> +}
> +
> +static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state,
> +				    struct v4l2_subdev_selection *sel)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +	struct v4l2_rect *crop;
> +	struct v4l2_rect *cmps;
> +
> +	if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
> +		return -EINVAL;
> +
> +	switch (sel->target) {
> +	case V4L2_SEL_TGT_CROP_BOUNDS:
> +		fmt = v4l2_subdev_state_get_format(state, sel->pad);
> +		sel->r.width = fmt->width;
> +		sel->r.height = fmt->height;
> +		sel->r.left = 0;
> +		sel->r.top = 0;
> +		break;
> +	case V4L2_SEL_TGT_COMPOSE_BOUNDS:
> +		crop = v4l2_subdev_state_get_crop(state, sel->pad);
> +		sel->r.width = crop->width;
> +		sel->r.height = crop->height;
> +		sel->r.left = 0;
> +		sel->r.top = 0;
> +		break;
> +	case V4L2_SEL_TGT_CROP:
> +		crop = v4l2_subdev_state_get_crop(state, sel->pad);
> +		sel->r = *crop;
> +		break;
> +	case V4L2_SEL_TGT_COMPOSE:
> +		cmps = v4l2_subdev_state_get_compose(state, sel->pad);
> +		sel->r = *cmps;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state,
> +				    struct v4l2_subdev_selection *sel)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +	struct v4l2_rect *crop;
> +	struct v4l2_rect *cmps;
> +
> +	if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
> +		return -EINVAL;
> +
> +	switch (sel->target) {
> +	case V4L2_SEL_TGT_CROP:
> +		fmt = v4l2_subdev_state_get_format(state, sel->pad);
> +		crop = v4l2_subdev_state_get_crop(state, sel->pad);
> +
> +		sel->r.left = clamp_t(s32, sel->r.left, 0, fmt->width - 1);
> +		sel->r.top = clamp_t(s32, sel->r.top, 0, fmt->height - 1);
> +		sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH,
> +				     fmt->width - sel->r.left);
> +		sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT,
> +				      fmt->height - sel->r.top);
> +
> +		crop->width = ALIGN(sel->r.width, 2);
> +		crop->height = ALIGN(sel->r.height, 2);
> +		crop->left = sel->r.left;
> +		crop->top = sel->r.top;
> +
> +		sel->r = *crop;
The size should be propagated to the compose rectangle and source format too.
> +		break;
> +	case V4L2_SEL_TGT_COMPOSE:
> +		crop = v4l2_subdev_state_get_crop(state, sel->pad);
> +		cmps = v4l2_subdev_state_get_compose(state, sel->pad);
> +
> +		sel->r.left = 0;
> +		sel->r.top = 0;
> +		sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH, crop->width);
> +		sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT, crop->height);
> +
> +		cmps->width = ALIGN(sel->r.width, 2);
> +		cmps->height = ALIGN(sel->r.height, 2);
> +		cmps->left = sel->r.left;
> +		cmps->top = sel->r.top;
> +
> +		sel->r = *cmps;
> +
> +		fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
> +		fmt->width = cmps->width;
> +		fmt->height = cmps->height;
> +		break;
This looks ok
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int c3_isp_rsz_init_state(struct v4l2_subdev *sd,
> +				 struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *sink_fmt;
> +	struct v4l2_mbus_framefmt *src_fmt;
> +	struct v4l2_rect *crop;
> +	struct v4l2_rect *cmps;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> +	sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
> +	sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> +	sink_fmt->field = V4L2_FIELD_NONE;
> +	sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> +	sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> +	sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
> +	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> +	sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> +
> +	crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> +	crop->width = C3_ISP_DEFAULT_WIDTH;
> +	crop->height = C3_ISP_DEFAULT_HEIGHT;
> +	crop->left = 0;
> +	crop->top = 0;
> +
> +	cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> +	cmps->width = C3_ISP_DEFAULT_WIDTH;
> +	cmps->height = C3_ISP_DEFAULT_HEIGHT;
> +	cmps->left = 0;
> +	cmps->top = 0;
> +
> +	src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
> +	*src_fmt = *sink_fmt;
> +
> +	return c3_isp_rsz_init_routing(sd, state);
> +}
> +
> +static const struct v4l2_subdev_pad_ops c3_isp_rsz_pad_ops = {
> +	.enum_mbus_code = c3_isp_rsz_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = c3_isp_rsz_set_fmt,
> +	.get_selection = c3_isp_rsz_get_selection,
> +	.set_selection = c3_isp_rsz_set_selection,
> +	.set_routing = c3_isp_rsz_set_routing,
> +};
> +
> +static const struct v4l2_subdev_ops c3_isp_rsz_subdev_ops = {
> +	.pad = &c3_isp_rsz_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops c3_isp_rsz_internal_ops = {
> +	.init_state = c3_isp_rsz_init_state,
> +};
> +
> +/* Media entity operations */
> +static const struct media_entity_operations c3_isp_rsz_entity_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +static int c3_isp_rsz_register(struct c3_isp_resizer *rsz)
> +{
> +	struct v4l2_subdev *sd = &rsz->sd;
> +	int ret;
> +
> +	v4l2_subdev_init(sd, &c3_isp_rsz_subdev_ops);
> +	sd->owner = THIS_MODULE;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	sd->internal_ops = &c3_isp_rsz_internal_ops;
> +	snprintf(sd->name, sizeof(sd->name), "isp-resizer%u", rsz->id);
> +
> +	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
> +	sd->entity.ops = &c3_isp_rsz_entity_ops;
> +
> +	sd->dev = rsz->isp->dev;
> +	v4l2_set_subdevdata(sd, rsz);
> +
> +	rsz->pads[C3_ISP_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +	rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_PAD_MAX, rsz->pads);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret)
> +		goto err_entity_cleanup;
> +
> +	ret = v4l2_device_register_subdev(&rsz->isp->v4l2_dev, sd);
> +	if (ret)
> +		goto err_subdev_cleanup;
> +
> +	return 0;
> +
> +err_subdev_cleanup:
> +	v4l2_subdev_cleanup(sd);
> +err_entity_cleanup:
> +	media_entity_cleanup(&sd->entity);
> +	return ret;
> +}
> +
> +static void c3_isp_rsz_unregister(struct c3_isp_resizer *rsz)
> +{
> +	struct v4l2_subdev *sd = &rsz->sd;
> +
> +	v4l2_device_unregister_subdev(sd);
> +	v4l2_subdev_cleanup(sd);
> +	media_entity_cleanup(&sd->entity);
> +}
> +
> +int c3_isp_resizers_register(struct c3_isp_device *isp)
> +{
> +	u32 i;
> +	int ret;
> +
> +	for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
> +		struct c3_isp_resizer *rsz = &isp->resizers[i];
> +
> +		rsz->id = i;
> +		rsz->isp = isp;
> +
> +		if (rsz->id == C3_ISP_RSZ_0)
> +			rsz->cap = &isp->caps[C3_ISP_CAP_DEV_0];
> +		else if (rsz->id == C3_ISP_RSZ_1)
> +			rsz->cap = &isp->caps[C3_ISP_CAP_DEV_1];
> +		else
> +			rsz->cap = &isp->caps[C3_ISP_CAP_DEV_2];
> +
> +		ret = c3_isp_rsz_register(rsz);
> +		if (ret) {
> +			rsz->isp = NULL;
> +			c3_isp_resizers_unregister(isp);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +void c3_isp_resizers_unregister(struct c3_isp_device *isp)
> +{
> +	u32 i;
> +
> +	for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
> +		struct c3_isp_resizer *rsz = &isp->resizers[i];
> +
> +		if (rsz->isp)
> +			c3_isp_rsz_unregister(rsz);
> +	};
> +}
> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
> new file mode 100644
> index 000000000000..72024442d48f
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
> @@ -0,0 +1,488 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "c3-isp-common.h"
> +#include "c3-isp-regs.h"
> +#include "include/uapi/c3-isp-config.h"
> +
> +/* Hardware configuration */
> +
> +static void c3_isp_stats_af_init(struct c3_isp_stats *stats)
> +{
> +	c3_isp_write(stats->isp, ISP_AF_ROI0_WIN01,
> +		     AF_ROI_XYWIN_00_INIT | AF_ROI_XYWIN_01_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AF_ROI0_WIN23,
> +		     AF_ROI_XYWIN_02_INIT | AF_ROI_XYWIN_03_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AF_ROI1_WIN01,
> +		     AF_ROI_XYWIN_10_INIT | AF_ROI_XYWIN_11_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AF_ROI1_WIN23,
> +		     AF_ROI_XYWIN_12_INIT | AF_ROI_XYWIN_13_INIT);
> +
> +	/* 0: old statistics output, 1: new statistics output. */
> +	c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL,
> +			   AF_STAT_SELECT, 0x1 << AF_STAT_SELECT_SHIFT);
> +}
> +
> +static void c3_isp_stats_ae_init(struct c3_isp_stats *stats)
> +{
> +	c3_isp_write(stats->isp, ISP_AE_ROI0_WIN01,
> +		     AE_ROI_XYWIN_00_INIT | AE_ROI_XYWIN_01_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AE_ROI0_WIN23,
> +		     AE_ROI_XYWIN_02_INIT | AE_ROI_XYWIN_03_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AE_ROI1_WIN01,
> +		     AE_ROI_XYWIN_10_INIT | AE_ROI_XYWIN_11_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AE_ROI1_WIN23,
> +		     AE_ROI_XYWIN_12_INIT | AE_ROI_XYWIN_13_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AE_STAT_THD01,
> +		     AE_STAT_THRD0_INIT | AE_STAT_THRD1_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AE_STAT_THD23,
> +		     AE_STAT_THRD2_INIT | AE_STAT_THRD3_INIT);
> +
> +	/* Set 0 when ae_stat_switch is not 0 */
> +	c3_isp_update_bits(stats->isp, ISP_AE_CTRL,
> +			   AE_CTRL_INPUT_2LINE_TOGETHER, 0);
> +
> +	/* Configure ae luma mode */
> +	c3_isp_update_bits(stats->isp, ISP_AE_CTRL, AE_CTRL_LUMA_MODE_MASK,
> +			   AE_CTRL_LUMA_MODE_FILTER << AE_CTRL_LUMA_MODE_SHIFT);
> +}
> +
> +static void c3_isp_stats_awb_init(struct c3_isp_stats *stats)
> +{
> +	/* Initialize the awb statistics rectangle of image */
> +	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN01,
> +		     AWB_ROI_XYWIN_00_INIT | AWB_ROI_XYWIN_01_INIT);
> +	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN23,
> +		     AWB_ROI_XYWIN_02_INIT | AWB_ROI_XYWIN_03_INIT);
> +
> +	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN01,
> +		     AWB_ROI_XYWIN_10_INIT | AWB_ROI_XYWIN_11_INIT);
> +	c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN23,
> +		     AWB_ROI_XYWIN_12_INIT | AWB_ROI_XYWIN_13_INIT);
> +}
> +
> +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
> +{
> +	struct c3_isp_device *isp = stats->isp;
> +	struct c3_isp_stats_info *stats_info = stats->buff->vaddr;
> +	u32 awb_dma_size = sizeof(stats_info->awb_stats);
> +	u32 ae_dma_size = sizeof(stats_info->ae_stats);
You could just use sizeof(struct awb_stats_info) and not bother with the vaddr at all...but then 
again perhaps doing it this way is less prone to future mistakes from expansion of struct 
c3_isp_stats_info. I vacillate a little, so I leave it up to you.
> +	u32 awb_dma_addr = stats->buff->paddr;
> +	u32 af_dma_addr;
> +	u32 ae_dma_addr;
> +
> +	ae_dma_addr = awb_dma_addr + awb_dma_size;
> +	af_dma_addr = ae_dma_addr + ae_dma_size;
> +
> +	c3_isp_update_bits(isp, VIU_DMAWR_BADDR0, VIU_DMAWR_AF_BADDR_MASK,
> +			   VIU_DMAWR_AF_BADDR(af_dma_addr));
> +	c3_isp_update_bits(isp, VIU_DMAWR_BADDR1, VIU_DMAWR_AWB_BADDR_MASK,
> +			   VIU_DMAWR_AWB_BADDR(awb_dma_addr));
> +	c3_isp_update_bits(isp, VIU_DMAWR_BADDR2, VIU_DMAWR_AE_BADDR_MASK,
> +			   VIU_DMAWR_AE_BADDR(ae_dma_addr));
> +}
> +
> +static void c3_isp_stats_enable(struct c3_isp_stats *stats)
> +{
> +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> +			   TOP_3A_AE_STAT_EN, TOP_3A_AE_STAT_EN);
> +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> +			   TOP_3A_AWB_STAT_EN, TOP_3A_AWB_STAT_EN);
> +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> +			   TOP_3A_AF_STAT_EN, TOP_3A_AF_STAT_EN);
> +}
> +
> +static void c3_isp_stats_disable(struct c3_isp_stats *stats)
> +{
> +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> +			   TOP_3A_AE_STAT_EN, 0);
> +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> +			   TOP_3A_AWB_STAT_EN, 0);
> +	c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> +			   TOP_3A_AF_STAT_EN, 0);
> +}
> +
> +/* The unit of dma_size is 16 bytes */
> +static void c3_isp_stats_cfg_dmawr_size(struct c3_isp_stats *stats)
> +{
> +	u32 dma_size;
> +
> +	dma_size = sizeof(struct af_stats_info) / 16;
> +	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0,
> +			   VIU_DMAWR_SIZE_AF_MASK, dma_size);
> +
> +	dma_size = sizeof(struct awb_stats_info) / 16;
> +	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE_AWB_MASK,
> +			   dma_size << VIU_DMAWR_SIZE_AWB_SHIFT);
> +
> +	dma_size = sizeof(struct ae_stats_info) / 16;
> +	c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1,
> +			   VIU_DMAWR_SIZE_AE_MASK, dma_size);
> +}
> +
> +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
> +{
> +	stats->buff = list_first_entry_or_null(&stats->pending,
> +					       struct c3_isp_vb2_buffer, list);
> +	if (stats->buff) {
> +		c3_isp_stats_cfg_dmawr_addr(stats);
> +		list_del(&stats->buff->list);
> +	}
> +}

Same locking comment as with the capture code.
> +
> +static void c3_isp_stats_start(struct c3_isp_stats *stats)
> +{
> +	c3_isp_stats_af_init(stats);
> +	c3_isp_stats_ae_init(stats);
> +	c3_isp_stats_awb_init(stats);
> +
> +	c3_isp_stats_cfg_dmawr_size(stats);
> +	c3_isp_stats_cfg_buff(stats);
> +	c3_isp_stats_enable(stats);
> +
> +	stats->is_streaming = true;
And similarly I'd drop this variable and rely on the state of the list and stats->buff
> +}
> +
> +static void c3_isp_stats_stop(struct c3_isp_stats *stats)
> +{
> +	stats->is_streaming = false;
> +
> +	c3_isp_stats_disable(stats);
> +}
> +
> +static void c3_isp_stats_return_buffers(struct c3_isp_stats *stats,
> +					enum vb2_buffer_state state)
> +{
> +	unsigned long flags;
> +	struct c3_isp_vb2_buffer *buff;
> +
> +	spin_lock_irqsave(&stats->buff_lock, flags);
Usual comment about the use of guard()
> +
> +	if (stats->buff) {
> +		vb2_buffer_done(&stats->buff->vb.vb2_buf, state);
> +		stats->buff = NULL;
> +	}
> +
> +	while (!list_empty(&stats->pending)) {
> +		buff = list_first_entry(&stats->pending,
> +					struct c3_isp_vb2_buffer, list);
> +		list_del(&buff->list);
> +		vb2_buffer_done(&buff->vb.vb2_buf, state);
> +	}
> +
> +	spin_unlock_irqrestore(&stats->buff_lock, flags);
> +}
> +
> +static int c3_isp_stats_querycap(struct file *file, void *fh,
> +				 struct v4l2_capability *cap)
> +{
> +	strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
> +	strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
> +
> +	return 0;
> +}
> +
> +static int c3_isp_stats_enum_fmt(struct file *file, void *fh,
> +				 struct v4l2_fmtdesc *f)
> +{
> +	struct c3_isp_stats *stats = video_drvdata(file);
> +
> +	if (f->index > 0 || f->type != stats->vb2_q.type)
> +		return -EINVAL;
> +
> +	f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
> +
> +	return 0;
> +}
> +
> +static int c3_isp_stats_g_fmt(struct file *file, void *fh,
> +			      struct v4l2_format *f)
> +{
> +	struct c3_isp_stats *stats = video_drvdata(file);
> +
> +	f->fmt.meta = stats->vfmt.fmt.meta;
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
> +	.vidioc_querycap                = c3_isp_stats_querycap,
> +	.vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
> +	.vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
> +	.vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
> +	.vidioc_try_fmt_meta_cap        = c3_isp_stats_g_fmt,
> +	.vidioc_reqbufs	                = vb2_ioctl_reqbufs,
> +	.vidioc_querybuf                = vb2_ioctl_querybuf,
> +	.vidioc_qbuf                    = vb2_ioctl_qbuf,
> +	.vidioc_expbuf                  = vb2_ioctl_expbuf,
> +	.vidioc_dqbuf                   = vb2_ioctl_dqbuf,
> +	.vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
> +	.vidioc_create_bufs             = vb2_ioctl_create_bufs,
> +	.vidioc_streamon                = vb2_ioctl_streamon,
> +	.vidioc_streamoff               = vb2_ioctl_streamoff,
> +	.vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
> +	.vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
> +};
> +
> +static const struct v4l2_file_operations isp_stats_v4l2_fops = {
> +	.open = v4l2_fh_open,
> +	.release = vb2_fop_release,
> +	.poll = vb2_fop_poll,
> +	.unlocked_ioctl = video_ioctl2,
> +	.mmap = vb2_fop_mmap,
> +};
> +
> +static int c3_isp_stats_vb2_queue_setup(struct vb2_queue *q,
> +					unsigned int *num_buffers,
> +					unsigned int *num_planes,
> +					unsigned int sizes[],
> +					struct device *alloc_devs[])
> +{
> +	if (*num_planes) {
> +		if (*num_planes != 1)
> +			return -EINVAL;
> +
> +		if (sizes[0] < sizeof(struct c3_isp_stats_info))
> +			return -EINVAL;
> +	} else {
> +		*num_planes = 1;
> +		sizes[0] = sizeof(struct c3_isp_stats_info);
> +	}
> +
> +	return 0;
> +}
> +
> +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_vb2_buffer *buf =
> +			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> +	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&stats->buff_lock, flags);
> +
> +	list_add_tail(&buf->list, &stats->pending);
> +
> +	spin_unlock_irqrestore(&stats->buff_lock, flags);
> +}
> +
> +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
> +{
> +	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> +	unsigned int size = stats->vfmt.fmt.meta.buffersize;
> +
> +	if (vb2_plane_size(vb, 0) < size) {
> +		dev_err(stats->isp->dev,
> +			"User buffer too small (%ld < %u)\n",
> +			vb2_plane_size(vb, 0), size);
> +		return -EINVAL;
> +	}
> +
> +	vb2_set_plane_payload(vb, 0, size);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> +	struct c3_isp_vb2_buffer *buf =
> +			container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> +	struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> +
> +	buf->vaddr = vb2_plane_vaddr(vb, 0);
> +	buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
> +
> +	memset(buf->vaddr, 0, stats->vfmt.fmt.meta.buffersize);
> +
> +	return 0;
> +}
> +
> +static int c3_isp_stats_vb2_start_streaming(struct vb2_queue *q,
> +					    unsigned int count)
> +{
> +	struct c3_isp_stats *stats = vb2_get_drv_priv(q);
> +	int ret;
> +
> +	guard(mutex)(&stats->isp->lock);
> +
> +	ret = pm_runtime_resume_and_get(stats->isp->dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = video_device_pipeline_start(&stats->vdev, &stats->isp->pipe);
> +	if (ret) {
> +		dev_err(stats->isp->dev,
> +			"Failed to start stats pipeline: %d\n", ret);
> +		goto err_pm_put;
> +	}
> +
> +	if (c3_isp_pipeline_ready(stats->isp)) {
> +		ret = v4l2_subdev_enable_streams(&stats->isp->core.sd,
> +						 C3_ISP_CORE_PAD_SOURCE_STATS,
> +						 BIT(0));
> +		if (ret)
> +			goto err_pipeline_stop;
> +	}
> +
> +	c3_isp_stats_start(stats);
> +
> +	return 0;
> +
> +err_pipeline_stop:
> +	video_device_pipeline_stop(&stats->vdev);
> +err_pm_put:
> +	pm_runtime_put(stats->isp->dev);
> +	c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_QUEUED);
> +	return ret;
> +}
> +
> +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
> +{
> +	struct c3_isp_stats *stats = vb2_get_drv_priv(q);
> +
> +	guard(mutex)(&stats->isp->lock);
> +
> +	c3_isp_stats_stop(stats);
> +	c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_ERROR);
> +
> +	if (stats->isp->pipe.start_count == 1)
> +		v4l2_subdev_disable_streams(&stats->isp->core.sd,
> +					    C3_ISP_CORE_PAD_SOURCE_STATS,
> +					    BIT(0));
> +
> +	video_device_pipeline_stop(&stats->vdev);
> +	pm_runtime_put(stats->isp->dev);
> +}
> +
> +static const struct vb2_ops isp_stats_vb2_ops = {
> +	.queue_setup = c3_isp_stats_vb2_queue_setup,
> +	.buf_queue = c3_isp_stats_vb2_buf_queue,
> +	.buf_prepare = c3_isp_stats_vb2_buf_prepare,
> +	.buf_init = c3_isp_stats_vb2_buf_init,
> +	.wait_prepare = vb2_ops_wait_prepare,
> +	.wait_finish = vb2_ops_wait_finish,
> +	.start_streaming = c3_isp_stats_vb2_start_streaming,
> +	.stop_streaming = c3_isp_stats_vb2_stop_streaming,
> +};
> +
> +int c3_isp_stats_register(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_stats *stats = &isp->stats;
> +	struct video_device *vdev = &stats->vdev;
> +	struct vb2_queue *vb2_q = &stats->vb2_q;
> +	int ret;
> +
> +	memset(stats, 0, sizeof(*stats));
> +	stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
> +	stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_info);
> +	stats->isp = isp;
> +	INIT_LIST_HEAD(&stats->pending);
> +	spin_lock_init(&stats->buff_lock);
> +
> +	mutex_init(&stats->lock);
> +
> +	snprintf(vdev->name, sizeof(vdev->name), "isp-stats");
> +	vdev->fops = &isp_stats_v4l2_fops;
> +	vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
> +	vdev->v4l2_dev = &isp->v4l2_dev;
> +	vdev->lock = &stats->lock;
> +	vdev->minor = -1;
> +	vdev->queue = vb2_q;
> +	vdev->release = video_device_release_empty;
> +	vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
> +	vdev->vfl_dir = VFL_DIR_RX;
> +	video_set_drvdata(vdev, stats);
> +
> +	vb2_q->drv_priv = stats;
> +	vb2_q->mem_ops = &vb2_dma_contig_memops;
> +	vb2_q->ops = &isp_stats_vb2_ops;
> +	vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
> +	vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
> +	vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
> +	vb2_q->dev = isp->dev;
> +	vb2_q->lock = &stats->lock;
> +	vb2_q->min_queued_buffers = 2;
> +
> +	ret = vb2_queue_init(vb2_q);
> +	if (ret)
> +		goto err_destroy;
> +
> +	stats->pad.flags = MEDIA_PAD_FL_SINK;
> +	ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
> +	if (ret)
> +		goto err_queue_release;
> +
> +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +	if (ret) {
> +		dev_err(isp->dev,
> +			"Failed to register %s: %d\n", vdev->name, ret);
> +		goto err_entity_cleanup;
> +	}
> +
> +	return 0;
> +
> +err_entity_cleanup:
> +	media_entity_cleanup(&vdev->entity);
> +err_queue_release:
> +	vb2_queue_release(vb2_q);
> +err_destroy:
> +	mutex_destroy(&stats->lock);
> +	return ret;
> +}
> +
> +void c3_isp_stats_unregister(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_stats *stats = &isp->stats;
> +
> +	vb2_queue_release(&stats->vb2_q);
> +	media_entity_cleanup(&stats->vdev.entity);
> +	video_unregister_device(&stats->vdev);
> +	mutex_destroy(&stats->lock);
> +}
> +
> +int c3_isp_stats_done(struct c3_isp_device *isp)
> +{
> +	struct c3_isp_stats *stats = &isp->stats;
> +	struct c3_isp_vb2_buffer *buff = stats->buff;
> +	unsigned long flags;
> +
> +	if (!stats->is_streaming)
> +		return -EINVAL;
> +
> +	spin_lock_irqsave(&stats->buff_lock, flags);
> +
> +	if (buff) {
> +		buff->vb.sequence = stats->isp->frm_sequence;
> +		buff->vb.vb2_buf.timestamp = ktime_get();
> +		buff->vb.field = V4L2_FIELD_NONE;
> +		vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +	}
> +
> +	c3_isp_stats_cfg_buff(stats);
> +
> +	spin_unlock_irqrestore(&stats->buff_lock, flags);
> +
> +	return 0;
> +}
> diff --git a/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
> new file mode 100644
> index 000000000000..84ff5741357a
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
> @@ -0,0 +1,537 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#ifndef __C3_ISP_CONFIG_H__
> +#define __C3_ISP_CONFIG_H__
> +
> +#define AF_STAT_BLKH_NUM             17
> +#define AF_STAT_BLKV_NUM             15
> +#define AF_STAT_BLK_NUM              (AF_STAT_BLKH_NUM * AF_STAT_BLKV_NUM)
> +/* AF stats block size need to be aligned with 2 */
> +#define AF_STAT_BLK_SIZE             ALIGN(AF_STAT_BLK_NUM, 2)
> +#define AE_HISTOGRAM_SIZE	     1024
> +#define AE_STAT_BLKH_NUM             17
> +#define AE_STAT_BLKV_NUM             15
> +#define AE_STAT_BLK_NUM              (AE_STAT_BLKH_NUM * AE_STAT_BLKV_NUM)
> +/* AE stats block size need to be aligned with 2 */
> +#define AE_STAT_BLK_SIZE             ALIGN(AE_STAT_BLK_NUM, 2)
> +#define AE_BLOCK_WT_NUM              255
> +#define AE_BLK_WT_DATA_NUM_OF_GROUP  8
> +#define AWB_STAT_BLKH_NUM            32
> +#define AWB_STAT_BLKV_NUM            24
> +#define AWB_STAT_BLK_NUM             (AWB_STAT_BLKH_NUM * AWB_STAT_BLKV_NUM)
> +/* AWB stats block size need to be aligned with 2 */
> +#define AWB_STAT_BLK_SIZE            ALIGN(AWB_STAT_BLK_NUM, 2)
> +#define AWB_BLOCK_WT_NUM             768
> +#define AWB_BLK_WT_DATA_NUM_OF_GROUP 8
> +#define AWB_STAT_BLC20_NUM           4
> +#define AWB_STAT_GAIN10_NUM          4
> +#define BLC_OFFSET_NUM               5
> +#define GAMMA_LUT_GROUP_NUM          4
> +#define GAMMA_LUT_POINT_NUM          129
> +#define GAMMA_LUT_DATA_NUM_OF_GROUP  2
> +
> +/**
> + * struct awb_zone_stats - AWB statistics of a block
> + *
> + * AWB zone stats is aligned with 8 bytes
> + *
> + * @rg: the ratio of R / G in a zone
> + * @bg: the ratio of B / G in a zone
> + * @pixel_sum: the total number of pixels used in a zone
> + */
The documentation throughout this file is much improved
> +struct awb_zone_stats {
> +	u16 rg;
> +	u16 bg;
> +	u32 pixel_sum;
> +};


__u8/__u16/__u32 for userspace API headers

> +
> +/**
> + * struct awb_stats_info - Auto white balance statistics information.
> + *
> + * AWB statistical information of all blocks.
> + *
> + * @awb_stats: array of auto white balance statistics
> + */
> +struct awb_stats_info {
> +	struct awb_zone_stats awb_stats[AWB_STAT_BLK_SIZE];
> +};
> +
> +/**
> + * struct ae_zone_stats - AE statistics of a block
> + *
> + * AE zone stats is aligned with 8 bytes.
> + * This is a 5-bin histogram and the total sum is
> + * normalized to 0xffff.
> + * So hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4)
> + *
> + * @hist0: the global normalized pixel count for bin 0
> + * @hist1: the global normalized pixel count for bin 1
> + * @hist3: the global normalized pixel count for bin 3
> + * @hist4: the global normalized pixel count for bin 4
> + */
> +struct ae_zone_stats {
> +	u16 hist0;
> +	u16 hist1;
> +	u16 hist3;
> +	u16 hist4;
> +};
> +
> +/**
> + * struct ae_stats_info - Exposure statistics information
> + *
> + * AE statistical information consists of
> + * all blocks information and a 1024-bin histogram.
> + *
> + * @ae_stats: array of auto exposure block statistics
> + * @hist: a 1024-bin histogram for the entire image
> + */
> +struct ae_stats_info {
> +	struct ae_zone_stats ae_stats[AE_STAT_BLK_SIZE];
> +	u32 hist[AE_HISTOGRAM_SIZE];
> +};
> +
> +/**
> + * struct af_zone_stats - AF statistics of a block
> + *
> + * AF block stats is aligned with 8 bytes.
> + * The zonal accumulated contrast metrics are stored
> + * in floating point format with 16 bits mantissa and
> + * 5 or 6 bits exponent.
> + * Apart from contrast metrics we accumulate squared image and
> + * quartic image data over the zone.
> + *
> + * @i2_mat: the mantissa of zonal squared image pixel sum
> + * @i4_mat: the mantissa of zonal quartic image pixel sum
> + * @e4_mat: the mantissa of zonal multi-directional quartic edge sum
> + * @i2_exp: the exponent of zonal squared image pixel sum
> + * @i4_exp: the exponent of zonal quartic image pixel sum
> + * @e4_exp: the exponent of zonal multi-directional quartic edge sum
> + */
> +struct af_zone_stats {
> +	u16 i2_mat;
> +	u16 i4_mat;
> +	u16 e4_mat;
> +	u16 i2_exp: 5;
> +	u16 i4_exp: 6;
> +	u16 e4_exp: 5;
> +};
> +
> +/**
> + * struct af_stats_info - Auto Focus statistics information
> + *
> + * AF statistical information of each block
> + *
> + * @af_stats: array of auto focus block statistics
> + */
> +struct af_stats_info {
> +	struct af_zone_stats af_stats[AF_STAT_BLK_SIZE];
> +};
> +
> +/**
> + * struct c3_isp_stats_info - V4L2_META_FMT_C3ISP_STATS
> + *
> + * Contains ISP statistics
> + *
> + * @awb_stats: auto white balance stats
> + * @ae_stats: auto exposure stats
> + * @af_stats: auto focus stats
> + */
> +struct c3_isp_stats_info {
> +	struct awb_stats_info awb_stats;
> +	struct ae_stats_info ae_stats;
> +	struct af_stats_info af_stats;
> +};
> +
> +/**
> + * enum c3_isp_param_buffer_version -  C3 ISP parameters block versioning
> + *
> + * @C3_ISP_PARAM_BUFFER_V0: First version of C3 ISP parameters block
> + */
> +enum c3_isp_param_buffer_version {
> +	C3_ISP_PARAM_BUFFER_V0,
> +};
> +
> +/**
> + * enum c3_isp_param_block_type - Enumeration of C3 ISP parameter blocks
> + *
> + * Each block configures a specific processing block of the C3 ISP.
> + * The block type allows the driver to correctly interpret
> + * the parameters block data.
> + *
> + * @C3_ISP_PARAM_BLOCK_WB_CHANGE: White balance change parameters
> + * @C3_ISP_PARAM_BLOCK_WB_LUMA: White balance Luma-based parameters
> + * @C3_ISP_PARAM_BLOCK_WB_TRIANGLE: White balance triangle parameters
> + * @C3_ISP_PARAM_BLOCK_AWB_STATS: AWB statistics parameters
> + * @C3_ISP_PARAM_BLOCK_AE_STATS: AE statistics parameters
> + * @C3_ISP_PARAM_BLOCK_AF_STATS: AF statistics parameters
> + * @C3_ISP_PARAM_BLOCK_PST_GAMMA: post gamma parameters
> + * @C3_ISP_PARAM_BLOCK_DMSC: demosaic parameters
> + * @C3_ISP_PARAM_BLOCK_CCM: Color correction matrix parameters
> + * @C3_ISP_PARAM_BLOCK_CSC: Color space conversion parameters
> + * @C3_ISP_PARAM_BLOCK_BLC: Black level correction parameters
> + * @C3_ISP_PARAM_BLOCK_SENTINEL: First non-valid block index
> + */
> +enum c3_isp_param_block_type {
> +	C3_ISP_PARAM_BLOCK_WB_CHANGE,
> +	C3_ISP_PARAM_BLOCK_WB_LUMA,
> +	C3_ISP_PARAM_BLOCK_WB_TRIANGLE,
> +	C3_ISP_PARAM_BLOCK_AWB_STATS,
> +	C3_ISP_PARAM_BLOCK_AE_STATS,
> +	C3_ISP_PARAM_BLOCK_AF_STATS,
> +	C3_ISP_PARAM_BLOCK_PST_GAMMA,
> +	C3_ISP_PARAM_BLOCK_DMSC,
> +	C3_ISP_PARAM_BLOCK_CCM,
> +	C3_ISP_PARAM_BLOCK_CSC,
> +	C3_ISP_PARAM_BLOCK_BLC,
> +	C3_ISP_PARAM_BLOCK_SENTINEL
> +};
> +
> +/**
> + * struct c3_isp_param_block_header - C3 ISP parameter block header
> + *
> + * This structure represents the common part of all the ISP configuration
> + * blocks. Each parameters block shall embed an instance of this structure type
> + * as its first member, followed by the block-specific configuration data. The
> + * driver inspects this common header to discern the block type and its size and
> + * properly handle the block content by casting it to the correct block-specific
> + * type.
> + *
> + * @type: The parameters block type (enum c3_isp_param_block_type)
> + * @enabled: Block enabled/disabled flag
> + * @size: Size (in bytes) of parameters block
> + */
> +
> +struct c3_isp_param_block_header {
> +	enum c3_isp_param_block_type type;
> +	bool enabled;
> +	size_t size;
> +};
Stick to types with strict size definitions; __u16 type, __u32 size and so on. Some feedback from 
the mali-c55 series was to switch to a "flags" field rather than an enabled bool and use BIT(0) as 
the enable flag; more flexibility for future expansion.
> +
> +/**
> + * struct wb_change_cfg - White Balance configuration
> + *
> + * @header: The C3 ISP parameters block header
> + * @wb_gain: white balance gain of each color
> + *	wb_gain[0]: Gr gain, range 0~0xfff
> + *	wb_gain[1]: R gain, range 0~0xfff
> + *	wb_gain[2]: B gain, range 0~0xfff
> + *	wb_gain[3]: Gb gain, range 0~0xfff
> + *	wb_gain[4]: Ir gain, range 0~0xfff
> + * @wb_limit: white balance limit of each color
> + *	wb_limit[0]: Gr limit, 16 bits float
> + *	wb_limit[1]: R limit, 16 bits float
> + *	wb_limit[2]: B limit, 16 bits float
> + *	wb_limit[3]: Gb limit, 16 bits float
> + *	wb_limit[4]: Ir limit, 16 bits float
> + * @ae_gain_grbgi: Gain of each color before blending to luma
> + *	ae_gain_grbgi[0]: Gr gain, range 0~255
> + *	ae_gain_grbgi[1]: R gain, range 0~255
> + *	ae_gain_grbgi[2]: B gain, range 0~255
> + *	ae_gain_grbgi[3]: Gb gain, range 0~255
> + *	ae_gain_grbgi[4]: Ir gain, range 0~255
> + * @ae_bl12_grbgi: Offset of each color before ae_gain_grbgi
> + *	ae_bl12_grbgi[0]: Gr offset, range 0~4095
> + *	ae_bl12_grbgi[1]: R offset, range 0~4095
> + *	ae_bl12_grbgi[2]: B offset, range 0~4095
> + *	ae_bl12_grbgi[3]: Gb offset, range 0~4095
> + *	ae_bl12_grbgi[4]: Ir offset, range 0~4095
> + */
Much nicer. Could you mention which value from enum c3_isp_param_block_type this struct pertains to 
though please?
> +struct wb_change_cfg {
c3_isp_ prefix please
> +	struct c3_isp_param_block_header header;
> +	u32 wb_gain[5];
> +	u32 wb_limit[5];
> +	u32 ae_gain_grbgi[5];
> +	u32 ae_bl12_grbgi[5];
> +};
> +
> +/**
> + * struct wb_luma_cfg - White Balance Luma-based configuration
> + *
> + * @header: The C3 ISP parameters block header
> + * @awb_stat_blc20: BLC in AWB statistic
> + *	awb_stat_blc20[0]: Gr blc, range 0~0xfffff
> + *	awb_stat_blc20[1]: R blc, range 0~0xfffff
> + *	awb_stat_blc20[2]: B blc, range 0~0xfffff
> + *	awb_stat_blc20[3]: Gb blc, range 0~0xfffff
> + * @awb_stat_gain10: Gain in AWB statistic
> + *	awb_stat_gain10[0]: Gr gain, range 0~1023
> + *	awb_stat_gain10[1]: R gain, range 0~1023
> + *	awb_stat_gain10[2]: B gain, range 0~1023
> + *	awb_stat_gain10[3]: Gb gain, range 0~1023
> + * @awb_stat_satur_low: AWB statistic under-saturation threshold
> + *	value: range 0~65535
> + * @awb_stat_satur_high: AWB statistic over-saturation threshold
> + *	value: range 0~65535
> + */
> +struct wb_luma_cfg {
> +	struct c3_isp_param_block_header header;
> +	u32 awb_stat_blc20[AWB_STAT_BLC20_NUM];
> +	u32 awb_stat_gain10[AWB_STAT_GAIN10_NUM];
> +	u32 awb_stat_satur_low;
> +	u32 awb_stat_satur_high;
> +};
> +
> +/**
> + * struct wb_triangle_cfg - White Balance Triangle
> + *
> + * @header: The C3 ISP parameters block header
> + * @awb_stat_satur_vald: AWB statistic over saturation control
> + *	value: 0: disable, 1: enable
> + * @awb_stat_rg_min: min value of r/g
> + *	value: 0~4095
> + * @awb_stat_rg_max: max value of r/g
> + *	value: 0~4095
> + * @awb_stat_bg_min: min value of b/g
> + *	value: 0~4095
> + * @awb_stat_bg_max: max value of b/g
> + *	value: 0~4095
> + * @awb_stat_rg_low: low value of r/g
> + *	value: 0~4095
> + * @awb_stat_rg_high: high value of r/g
> + *	value: 0~4095
> + * @awb_stat_bg_low: low value of b/g
> + *	value: 0~4095
> + * @awb_stat_bg_high: high value of b/g
> + *	value: 0~4095
> + */
> +struct wb_triangle_cfg {
> +	struct c3_isp_param_block_header header;
> +	u32 awb_stat_satur_vald;
> +	u32 awb_stat_rg_min;
> +	u32 awb_stat_rg_max;
> +	u32 awb_stat_bg_min;
> +	u32 awb_stat_bg_max;
> +	u32 awb_stat_rg_low;
> +	u32 awb_stat_rg_high;
> +	u32 awb_stat_bg_low;
> +	u32 awb_stat_bg_high;
> +};
> +
> +/**
> + * struct awb_stats_cfg - AWB statistics configuration
> + *
> + * This structure contains AWB statistics control information.
> + *
> + * @header: The C3 ISP parameters block header
> + * @awb_stat_switch: the switch of AWB statistics
> + *	value: 0~7
I assume this switch controls where in the ISP pipeline the values are drawn from? Could there be an 
enumeration that documents those 8 values? See for example "enum mali_c55_aexp_hist_tap_points". 
Same comments for the other switches below
> + * @awb_stat_blk_weight: Array of weights for AWB statistics blocks
> + *	value: 0~15
> + */
> +struct awb_stats_cfg {
> +	struct c3_isp_param_block_header header;
> +	u8 awb_stat_switch;
> +	u32 awb_stat_blk_weight[AWB_BLOCK_WT_NUM];
> +};
> +
> +/**
> + * struct ae_stats_cfg - AE statistics configuration
> + *
> + * This structure contains AE statistics control information.
> + *
> + * @header: The C3 ISP parameters block header
> + * @ae_stat_switch: the switch of AE statistics
> + *	value: 0~3
> + * @ae_stat_blk_weight: Array of weights for AE statistics blocks
> + *	value: 0~15
> + */
> +struct ae_stats_cfg {
> +	struct c3_isp_param_block_header header;
> +	u8 ae_stat_switch;
> +	u32 ae_stat_blk_weight[AE_BLOCK_WT_NUM];
> +};
> +
> +/**
> + * struct af_stats_cfg - AF statistics configuration
> + *
> + * This structure contains AF statistics control information.
> + *
> + * @header: The C3 ISP parameters block header
> + * @af_stat_switch: the switch of AF statistics
> + *	value: 0~3
> + */
> +struct af_stats_cfg {
> +	struct c3_isp_param_block_header header;
> +	u8 af_stat_switch;
> +};
> +
> +/**
> + * struct pst_gamma_cfg - Post gamma configuration
> + *
> + * This structure contains post gamma parameters
> + *
> + * @header: The C3 ISP parameters block header
> + * @pst_gamma_lut: LUT for P-Stitch gamma
> + *	value: 0~65535
> + */
> +struct pst_gamma_cfg {
> +	struct c3_isp_param_block_header header;
> +	u32 pst_gamma_lut[GAMMA_LUT_GROUP_NUM][GAMMA_LUT_POINT_NUM];
> +};
> +
> +/**
> + * struct dmsc_cfg - Demosaic configuration
> + *
> + * This structure contains demosaic parameters
You don't need the empty struct if all you're really after is the enable / disable field (or ideally 
flag) - just pass in a header block and ensure that the driver handles that correctly
> + *
> + * @header: The C3 ISP parameters block header
> + */
> +struct dmsc_cfg {
> +	struct c3_isp_param_block_header header;
> +};
> +
> +/**
> + * struct ccm_cfg - ISP CCM configuration
> + *
> + * This structure holds the parameters for configuring the CCM,
> + * which is used for color correction.
> + *
> + * @header: The C3 ISP parameters block header
> + * @ccm_4x3matrix: A 3x4 matrix used for color correction
> + *	value: 0~8191
> + */
> +struct ccm_cfg {
> +	struct c3_isp_param_block_header header;
> +	u32 ccm_4x3matrix[3][4];
> +};
> +
> +/**
> + * struct csc_cfg - ISP Color Space Conversion configuration
> + *
> + * This structure contains settings for color space conversion.
> + *
> + * @header: The C3 ISP parameters block header
> + * @cm0_offset_inp: Input offset values for the 0-order color matrix
> + *	value: 0~8191
> + * @cm0_offset_oup: Output offset values for the 0-order color matrix
> + *	value: 0~8191
> + * @cm0_3x3mtrx_rs: matrix right shift for cm0
> + *	value: 0~3
> + * @cm0_3x3matrix: A 3x3 matrix used for the color matrix
> + *	value: 0~8191
> + */
> +struct csc_cfg {
> +	struct c3_isp_param_block_header header;
> +	u32 cm0_offset_inp[3];
> +	u32 cm0_offset_oup[3];
> +	u32 cm0_3x3mtrx_rs;
> +	u32 cm0_3x3matrix[3][3];
> +};
> +
> +/**
> + * struct blc_cfg - ISP Black Level Correction (BLC) configuration
> + *
> + * This structure holds the parameters for BLC in image processing.
> + *
> + * @header: The C3 ISP parameters block header
> + * @fe_bl_ofst: Array of front-end BLC offsets for each color each channels
> + *	fe_bl_ofst[0]: Gr blc offset, range 0~0x1fffff
> + *	fe_bl_ofst[1]: R blc offset, range 0~0x1fffff
> + *	fe_bl_ofst[2]: B blc offset, range 0~0x1fffff
> + *	fe_bl_ofst[3]: Gb blc offset, range 0~0x1fffff
> + *	fe_bl_ofst[4]: Ir blc offset, range 0~0x1fffff
> + * @blc_ofst: Array of LSWB BLC offsets
LSWB?
> + *	blc_ofst[0]: Gr blc offset, 16 bits float
> + *	blc_ofst[1]: R blc offset, 16 bits float
> + *	blc_ofst[2]: B blc offset, 16 bits float
> + *	blc_ofst[3]: Gb blc offset, 16 bits float
> + *	blc_ofst[4]: Ir blc offset, 16 bits float
> + */
If it's a 16 bit field, how come the member is a u32 array?
> +struct blc_cfg {
> +	struct c3_isp_param_block_header header;
> +	u32 fe_bl_ofst[BLC_OFFSET_NUM];
> +	u32 blc_ofst[BLC_OFFSET_NUM];
> +};
> +
> +/**
> + * define C3_ISP_PARAMS_MAX_SIZE - Maximum size of all C3 ISP Parameters
> + *
> + * Though the parameters for the C3 ISP are passed as optional blocks, the
> + * driver still needs to know the absolute maximum size so that it can allocate
> + * a buffer sized appropriately to accommodate userspace attempting to set all
> + * possible parameters in a single frame.
> + */
> +#define C3_ISP_PARAMS_MAX_SIZE                 \
> +	(sizeof(struct wb_change_cfg) +        \
> +	sizeof(struct wb_luma_cfg)   +         \
> +	sizeof(struct wb_triangle_cfg) +       \
> +	sizeof(struct awb_stats_cfg) +          \
> +	sizeof(struct ae_stats_cfg) +           \
> +	sizeof(struct af_stats_cfg) +           \
> +	sizeof(struct pst_gamma_cfg) +         \
> +	sizeof(struct dmsc_cfg) +              \
> +	sizeof(struct ccm_cfg) +               \
> +	sizeof(struct csc_cfg) +               \
> +	sizeof(struct blc_cfg))
> +
> +/**
> + * struct c3_isp_params_buffer - C3 ISP configuration parameters
> + *
> + * This struct contains the configuration parameters of the C3 ISP
> + * algorithms, serialized by userspace into an opaque data buffer. Each
> + * configuration parameter block is represented by a block-specific structure
> + * which contains a :c:type:`c3_isp_param_block_header` entry as first
> + * member. Userspace populates the @data buffer with configuration parameters
> + * for the blocks that it intends to configure. As a consequence, the data
> + * buffer effective size changes according to the number of ISP blocks that
> + * userspace intends to configure.
> + *
> + * The parameters buffer is versioned by the @version field to allow modifying
> + * and extending its definition. Userspace should populate the @version field to
> + * inform the driver about the version it intends to use. The driver will parse
> + * and handle the @data buffer according to the data layout specific to the
> + * indicated revision and return an error if the desired revision is not
> + * supported.
> + *
> + * For each ISP block that userspace wants to configure, a block-specific
> + * structure is appended to the @data buffer, one after the other without gaps
> + * in between nor overlaps. Userspace shall populate the @total_size field with
> + * the effective size, in bytes, of the @data buffer.
> + *
> + * The expected memory layout of the parameters buffer is::
> + *
> + *	+-------------------- struct c3_isp_params_buffer ------------------+
> + *	| version = C3_ISP_PARAM_BUFFER_V0;                                   |
> + *	| total_size = sizeof(sizeof(struct wb_change_cfg))                   |
> + *	|              sizeof(sizeof(struct wb_luma_cfg));                    |
> + *	| +------------------------- data  ---------------------------------+ |
> + *	| | +------------------ struct wb_change_cfg) --------------------+ | |
> + *	| | | +---------  struct c3_isp_param_block_header header  -----+ | | |
> + *	| | | | type = C3_ISP_PARAM_BLOCK_WB_CHANGE;                    | | | |
> + *	| | | | enabled = true;                                            | | | |
> + *	| | | | size =                                                  | | | |
> + *	| | | |    sizeof(struct c3_isp_param_block_header header);     | | | |
> + *	| | | +---------------------------------------------------------+ | | |
> + *	| | | wb_gain[5] = ...;                                           | | |
> + *	| | | wb_limit[5] = ...;                                          | | |
> + *	| | | ae_gain_grbgi[5] = ...;                                     | | |
> + *	| | | ae_bl12_grbgi[5] = ...;                                     | | |
> + *	| | +------------------ struct wb_luma_cfg -----------------------+ | |
> + *	| | | +---------- struct c3_isp_param_block_header header ------+ | | |
> + *	| | | | type = C3_ISP_PARAM_BLOCK_WB_LUMA;                      | | | |
> + *	| | | | enabled = true;                                            | | | |
> + *	| | | | size = sizeof(struct wb_luma_cfg);                      | | | |
> + *	| | | +---------------------------------------------------------+ | | |
> + *	| | | awb_stat_blc20[4] = ...;                                    | | |
> + *	| | | awb_stat_gain10[4] = ...;                                   | | |
> + *	| | | awb_stat_satur_low = ...;                                   | | |
> + *	| | | awb_stat_satur_high = ...;                                  | | |
> + *	| | +-------------------------------------------------------------+ | |
> + *	| +-----------------------------------------------------------------+ |
> + *	+---------------------------------------------------------------------+
> + *
> + * @version: The C3 ISP parameters buffer version
> + * @total_size: The C3 ISP configuration data effective size,
> + *		excluding this header
> + * @data: The C3 ISP configuration blocks data
> + */
> +struct c3_isp_params_buffer {
> +	enum c3_isp_param_buffer_version version;
> +	size_t total_size;
> +	u8 data[C3_ISP_PARAMS_MAX_SIZE];
> +};
> +
> +#endif
>

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

* Re: [PATCH v3 7/9] media: platform: Add c3 ISP driver
  2024-11-07 16:03   ` Jacopo Mondi
  2024-11-07 16:15     ` Jacopo Mondi
@ 2024-11-08 12:34     ` Keke Li
  2024-11-08 14:04       ` Jacopo Mondi
  1 sibling, 1 reply; 37+ messages in thread
From: Keke Li @ 2024-11-08 12:34 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi Jacopo

Thanks very much for your reply.

On 2024/11/8 00:03, Jacopo Mondi wrote:
> [ EXTERNAL EMAIL ]
>
> Hi Keke
>
>     a first pass of review without going into details about the
> ISP parameters and stats but mostly on architecture.
>
> On Wed, Sep 18, 2024 at 02:07:18PM +0800, Keke Li via B4 Relay wrote:
>> From: Keke Li <keke.li@amlogic.com>
>>
>> The C3 ISP supports multi-camera and muti-exposure
>> high dynamic range (HDR). It brings together some
>> advanced imaging technologies to provide good image quality.
>> This driver mainly responsible for driving ISP pipeline
>> to process raw image.
>>
>> Signed-off-by: Keke Li <keke.li@amlogic.com>
>> ---
>>   drivers/media/platform/amlogic/Kconfig             |   1 +
>>   drivers/media/platform/amlogic/Makefile            |   1 +
>>   drivers/media/platform/amlogic/c3-isp/Kconfig      |  17 +
>>   drivers/media/platform/amlogic/c3-isp/Makefile     |  10 +
>>   .../media/platform/amlogic/c3-isp/c3-isp-capture.c | 759 ++++++++++++++++++
>>   .../media/platform/amlogic/c3-isp/c3-isp-common.h  | 327 ++++++++
>>   .../media/platform/amlogic/c3-isp/c3-isp-core.c    | 675 ++++++++++++++++
>>   drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c | 486 ++++++++++++
>>   .../media/platform/amlogic/c3-isp/c3-isp-params.c  | 857 +++++++++++++++++++++
>>   .../media/platform/amlogic/c3-isp/c3-isp-regs.h    | 683 ++++++++++++++++
>>   .../media/platform/amlogic/c3-isp/c3-isp-resizer.c | 768 ++++++++++++++++++
>>   .../media/platform/amlogic/c3-isp/c3-isp-stats.c   | 488 ++++++++++++
>>   .../amlogic/c3-isp/include/uapi/c3-isp-config.h    | 537 +++++++++++++
> This really should be in include/uapi/linux/media/amlogic
> so that it is made available to userspace.


OK. will move c3-isp-config.h to include/uapi/linux/media/amlogic

>>   13 files changed, 5609 insertions(+)
>>
>> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
>> index df09717b28d0..ebda6b7edc2d 100644
>> --- a/drivers/media/platform/amlogic/Kconfig
>> +++ b/drivers/media/platform/amlogic/Kconfig
>> @@ -2,6 +2,7 @@
>>
>>   comment "Amlogic media platform drivers"
>>
>> +source "drivers/media/platform/amlogic/c3-isp/Kconfig"
>>   source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
>>   source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
> I start wondering if this shouldn't rather be organized in an
> drivers/media/platform/amlogic/c3/ folder with an
> drivers/media/platform/amlogic/c3/isp/ subfolder
>
> Do you know if any of the csi-2-rx, adapter or ISP can
> be used in other SoCs ?
>

Yes, csi-2-rx, adapter and ISP will be used in T7 SoC in the future.

Is there need to create "drivers/media/platform/amlogic/c3/" in this case?

How to handle this case?

>>   source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
>> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
>> index b370154b090c..d0d9363d4d8d 100644
>> --- a/drivers/media/platform/amlogic/Makefile
>> +++ b/drivers/media/platform/amlogic/Makefile
>> @@ -1,5 +1,6 @@
>>   # SPDX-License-Identifier: GPL-2.0-only
>>
>> +obj-y += c3-isp/
>>   obj-y += c3-mipi-adapter/
>>   obj-y += c3-mipi-csi2/
>>   obj-y += meson-ge2d/
>> diff --git a/drivers/media/platform/amlogic/c3-isp/Kconfig b/drivers/media/platform/amlogic/c3-isp/Kconfig
>> new file mode 100644
>> index 000000000000..e317c1e81750
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/Kconfig
>> @@ -0,0 +1,17 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +config VIDEO_C3_ISP
>> +     tristate "Amlogic C3 Image Signal Processor (ISP) driver"
>> +     depends on ARCH_MESON || COMPILE_TEST
>> +     depends on VIDEO_DEV
>> +     depends on OF
>> +     select MEDIA_CONTROLLER
>> +     select V4L2_FWNODE
>> +     select VIDEO_V4L2_SUBDEV_API
>> +     select VIDEOBUF2_DMA_CONTIG
>> +     help
>> +       Video4Linux2 driver for Amlogic C3 ISP pipeline.
>> +       C3 ISP pipeline mainly for processing raw image
>            The C3 ISP is used for processing raw images
>> +       and output result to memory.
> Or something similar, but "C3 ISP pipeline mainly for processing"
> doesn't sound right to be


OK, I see

>> +
>> +       To compile this driver as a module choose m here.
>> diff --git a/drivers/media/platform/amlogic/c3-isp/Makefile b/drivers/media/platform/amlogic/c3-isp/Makefile
>> new file mode 100644
>> index 000000000000..b1b064170b57
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/Makefile
>> @@ -0,0 +1,10 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +c3-isp-objs := c3-isp-dev.o \
>> +            c3-isp-params.o \
>> +            c3-isp-stats.o \
>> +            c3-isp-capture.o \
>> +            c3-isp-core.o \
>> +            c3-isp-resizer.o
>> +
>> +obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
>> new file mode 100644
>> index 000000000000..ee9a7a17a203
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
>> @@ -0,0 +1,759 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/cleanup.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-event.h>
>> +#include <media/v4l2-ioctl.h>
>> +#include <media/v4l2-mc.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +
>> +static const struct c3_isp_capture_format cap_formats[] = {
>> +     {
>> +             .mbus_code = MEDIA_BUS_FMT_Y8_1X8,
>> +             .fourcc = V4L2_PIX_FMT_GREY,
>> +             .depth = 8,
>> +     },
>> +     {
>> +             .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
> Does the 5X8 version represents the format on the internal bus between
> the resizers and the capture device ?
>
> How does format propagation work from the ISP to the resizers and the
> capture devices ? I mean, is there an internal bus where the number of
> samples (5X8 vs 2X8) changes depending on the output format ?
>

There is no internal bus between the resizer and the capture device.

The output format should be only configured in capture device.

>> +             .fourcc = V4L2_PIX_FMT_NV12,
>> +             .depth = 12,
>> +     }, {
>> +             .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
>> +             .fourcc = V4L2_PIX_FMT_NV21,
>> +             .depth = 12,
>> +     }, {
>> +             .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
>> +             .fourcc = V4L2_PIX_FMT_NV16,
>> +             .depth = 16,
>> +     }, {
>> +             .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
>> +             .fourcc = V4L2_PIX_FMT_NV61,
>> +             .depth = 16,
>> +     },
>> +};
>> +
>> +/* Hardware configuration */
>> +
>> +/* Set the address of wrmifx3(write memory interface) */
>> +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
>> +{
>> +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>> +     struct c3_isp_vb2_buffer *buff = cap->buff;
>> +     u32 offset;
>> +
>> +     c3_isp_write(cap->isp,
>> +                  C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
>> +                  WRMIFX3_CH0_BADDR(buff->paddr));
>> +
>> +     if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
>> +         pix->pixelformat == V4L2_PIX_FMT_NV21 ||
>> +         pix->pixelformat == V4L2_PIX_FMT_NV16 ||
>> +         pix->pixelformat == V4L2_PIX_FMT_NV61) {
>> +             offset = pix->width * pix->height;
>> +             c3_isp_write(cap->isp,
>> +                          C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
>> +                          WRMIFX3_CH1_BADDR(buff->paddr + offset));
>> +     }
>> +}
>> +
>> +static void c3_isp_cap_output_size(struct c3_isp_capture *cap)
>> +{
>> +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>> +
>> +     c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
>> +                        DISP_OUT_VSIZE_MASK, pix->height);
>> +     c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
>> +                        DISP_OUT_HSIZE_MASK, pix->width << DISP_OUT_HSIZE_SHIFT);
>> +}
>> +
>> +static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
>> +{
>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>> +     u32 stride;
>> +
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
>> +
>> +     /* Grey has 1 plane*/
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_PLANE_MASK,
>> +                        WRMIFX3_FMT_MTX_PLANE_X1 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
>> +
>> +     /* Set Y only as output format */
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MODE_OUT_MASK,
>> +                        WRMIFX3_FMT_MODE_OUT_Y_ONLY << WRMIFX3_FMT_MODE_OUT_SHIFT);
>> +
>> +     /* The unit of stride is 128 bits */
>> +     stride = DIV_ROUND_UP(fmt->width * 8, 128);
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>> +                        WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
>> +
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>> +                        WRMIFX3_CH0_PIX_BITS_MODE_MASK,
>> +                        WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
>> +}
>> +
>> +static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, u32 swap_uv)
>> +{
>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>> +     u32 stride;
>> +
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_UV_SWAP_MASK,
>> +                        swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
>> +
>> +     /* NV12 or NV21 has 2 planes*/
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_PLANE_MASK,
>> +                        WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
>> +
>> +     /* Set YUV420 as output format */
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MODE_OUT_MASK,
>> +                        WRMIFX3_FMT_MODE_OUT_YUV420 << WRMIFX3_FMT_MODE_OUT_SHIFT);
>> +
>> +     /* The unit of stride is 128 bits */
>> +     stride = DIV_ROUND_UP(fmt->width * 8, 128);
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>> +                        WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
>> +                        WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
>> +
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>> +                        WRMIFX3_CH0_PIX_BITS_MODE_MASK,
>> +                        WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
>> +                        WRMIFX3_CH1_PIX_BITS_MODE_MASK,
>> +                        WRMIFX3_CH1_PIX_BITS_16BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
>> +}
>> +
>> +static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, u32 swap_uv)
>> +{
>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>> +     u32 stride;
>> +
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_16BIT);
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_UV_SWAP_MASK,
>> +                        swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
>> +
>> +     /* NV16 or NV61 has 2 planes*/
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_PLANE_MASK,
>> +                        WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
>> +
>> +     /* Set YUV422 as output format */
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MODE_OUT_MASK,
>> +                        WRMIFX3_FMT_MODE_OUT_YUV422 << WRMIFX3_FMT_MODE_OUT_SHIFT);
>> +
>> +     /* The unit of stride is 128 bits */
>> +     stride = DIV_ROUND_UP(fmt->width * 16, 128);
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>> +                        WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
>> +                        WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
>> +
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>> +                        WRMIFX3_CH0_PIX_BITS_MODE_MASK,
>> +                        WRMIFX3_CH0_PIX_BITS_16BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
>> +                        WRMIFX3_CH1_PIX_BITS_MODE_MASK,
>> +                        WRMIFX3_CH1_PIX_BITS_32BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
>> +}
>> +
>> +static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
>> +{
>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>> +
>> +     if (fmt->pixelformat == V4L2_PIX_FMT_GREY) {
>> +             c3_isp_cap_wrmifx3_grey(cap);
>> +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV12) {
>> +             c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
>> +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV21) {
>> +             c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
>> +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV16) {
>> +             c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
>> +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV61) {
>> +             c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
>> +     } else {
>> +             dev_err(cap->isp->dev, "Invalid pixel format: 0x%x\n", fmt->pixelformat);
>> +             return;
>> +     }
>> +
>> +     c3_isp_write(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id),
>> +                  WRMIFX3_FMT_HSIZE(fmt->width) | WRMIFX3_FMT_VSIZE(fmt->height));
>> +
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
>> +                        WRMIFX3_WIN_LUMA_HEND_MASK,
>> +                        WRMIFX3_WIN_LUMA_HEND(fmt->width) << WRMIFX3_WIN_LUMA_HEND_SHIFT);
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
>> +                        WRMIFX3_WIN_LUMA_VEND_MASK,
>> +                        WRMIFX3_WIN_LUMA_VEND(fmt->height) << WRMIFX3_WIN_LUMA_VEND_SHIFT);
>> +
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_HSIZE, cap->id),
>> +                        WRMIFX3_CROP_HEND_MASK,
>> +                        WRMIFX3_CROP_HEND(fmt->width) << WRMIFX3_CROP_HEND_SHIFT);
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_VSIZE, cap->id),
>> +                        WRMIFX3_CROP_VEND_MASK,
>> +                        WRMIFX3_CROP_VEND(fmt->height) << WRMIFX3_CROP_VEND_SHIFT);
>> +
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
>> +                        WRMIFX3_WIN_CHROM_HEND_MASK,
>> +                        WRMIFX3_WIN_CHROM_HEND(fmt->width) << WRMIFX3_WIN_CHROM_HEND_SHIFT);
>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
>> +                        WRMIFX3_WIN_CHROM_VEND_MASK,
>> +                        WRMIFX3_WIN_CHROM_VEND(fmt->height) << WRMIFX3_WIN_CHROM_VEND_SHIFT);
>> +}
>> +
>> +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)
> This function is called from two locations, one with the buff_lock
> held, the other one without, if I'm not mistaken.
>

Will add the buff_lock in c3_isp_cap_start().

>> +{
>> +     cap->buff = list_first_entry_or_null(&cap->pending,
>> +                                          struct c3_isp_vb2_buffer, list);
>> +     if (cap->buff) {
>> +             c3_isp_cap_wrmifx3_buff(cap);
>> +             list_del(&cap->buff->list);
>> +     }
>> +}
>> +
>> +static void c3_isp_cap_start(struct c3_isp_capture *cap)
>> +{
>> +     c3_isp_cap_cfg_buff(cap);
>> +
>> +     c3_isp_cap_output_size(cap);
>> +     c3_isp_cap_wrmifx3_size(cap);
>> +
>> +     c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN,
>> +                        TOP_WRMIF_EN(cap->id), TOP_WRMIF_EN(cap->id));
>> +
>> +     cap->is_streaming = true;
>> +}
>> +
>> +static void c3_isp_cap_stop(struct c3_isp_capture *cap)
>> +{
>> +     cap->is_streaming = false;
>> +
>> +     c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, TOP_WRMIF_EN(cap->id), 0);
>> +}
>> +
>> +static int c3_isp_cap_done(struct c3_isp_capture *cap)
>> +{
>> +     struct c3_isp_vb2_buffer *buff = cap->buff;
>> +     unsigned long flags;
>> +
>> +     if (!cap->is_streaming)
>> +             return -EINVAL;
> Is this an error condition or 0 should be returnes ?
>

If  return 0,  there is no need to check the return value of 
c3_isp_cap_done().

So will modify this function from "int c3_isp_cap_done()" to

"void c3_isp_cap_done()".

>> +
>> +     spin_lock_irqsave(&cap->buff_lock, flags);
>> +
>> +     if (buff) {
>> +             buff->vb.sequence = cap->isp->frm_sequence;
>> +             buff->vb.vb2_buf.timestamp = ktime_get();
>> +             buff->vb.field = V4L2_FIELD_NONE;
>> +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
>> +     }
>> +
>> +     c3_isp_cap_cfg_buff(cap);
>> +
>> +     spin_unlock_irqrestore(&cap->buff_lock, flags);
>> +
>> +     return 0;
>> +}
>> +
>> +/* V4L2 video operations */
>> +
>> +static const struct c3_isp_capture_format
>> +*c3_cap_find_fmt(u32 fourcc)
>> +{
>> +     unsigned int i;
>> +
>> +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
>> +             if (cap_formats[i].fourcc == fourcc)
>> +                     return &cap_formats[i];
>> +     }
>> +
>> +     return NULL;
>> +}
>> +
>> +static void c3_cap_try_fmt(struct c3_isp_capture *cap,
>> +                        struct v4l2_pix_format *pix)
>> +{
>> +     const struct c3_isp_capture_format *fmt;
>> +
>> +     fmt = c3_cap_find_fmt(pix->pixelformat);
>> +     if (!fmt)
>> +             fmt = &cap_formats[0];
>> +
>> +     pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>> +     pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>> +     pix->pixelformat = fmt->fourcc;
>> +     pix->field = V4L2_FIELD_NONE;
>> +     pix->colorspace = V4L2_COLORSPACE_SRGB;
>> +     pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
>> +     pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>> +
>> +     /* ISP hardware requires 16 bytes alignment */
>> +     pix->bytesperline = ALIGN(pix->width, 16);
>> +     pix->sizeimage = pix->bytesperline * pix->height * fmt->depth / 8;
>> +}
>> +
>> +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
>> +                                   enum vb2_buffer_state state)
>> +{
>> +     unsigned long flags;
>> +     struct c3_isp_vb2_buffer *buff;
>> +
>> +     spin_lock_irqsave(&cap->buff_lock, flags);
>> +
>> +     if (cap->buff) {
>> +             vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
>> +             cap->buff = NULL;
>> +     }
>> +
>> +     while (!list_empty(&cap->pending)) {
>> +             buff = list_first_entry(&cap->pending,
>> +                                     struct c3_isp_vb2_buffer, list);
>> +             list_del(&buff->list);
>> +             vb2_buffer_done(&buff->vb.vb2_buf, state);
>> +     }
>> +
>> +     spin_unlock_irqrestore(&cap->buff_lock, flags);
>> +}
>> +
>> +static int c3_isp_cap_querycap(struct file *file, void *fh,
>> +                            struct v4l2_capability *cap)
>> +{
>> +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>> +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_cap_enum_fmt(struct file *file, void *fh,
>> +                            struct v4l2_fmtdesc *f)
>> +{
>> +     const struct c3_isp_capture_format *fmt;
>> +     unsigned int index = 0;
>> +     unsigned int i;
>> +
>> +     if (!f->mbus_code) {
>> +             if (f->index >= ARRAY_SIZE(cap_formats))
>> +                     return -EINVAL;
>> +
>> +             fmt = &cap_formats[f->index];
>> +             f->pixelformat = fmt->fourcc;
>> +             return 0;
>> +     }
>> +
>> +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
>> +             fmt = &cap_formats[i];
>> +             if (f->mbus_code != fmt->mbus_code)
>> +                     continue;
>> +
>> +             if (index++ == f->index) {
>> +                     f->pixelformat = cap_formats[i].fourcc;
>> +                     return 0;
>> +             }
>> +     }
>> +
>> +     return -EINVAL;
>> +}
>> +
>> +static int c3_isp_cap_g_fmt(struct file *file, void *fh,
>> +                         struct v4l2_format *f)
>> +{
>> +     struct c3_isp_capture *cap = video_drvdata(file);
>> +
>> +     f->fmt.pix = cap->vfmt.fmt.pix;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_cap_s_fmt(struct file *file, void *fh,
>> +                         struct v4l2_format *f)
>> +{
>> +     struct c3_isp_capture *cap = video_drvdata(file);
>> +
>> +     c3_cap_try_fmt(cap, &f->fmt.pix);
>> +     cap->vfmt = *f;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_cap_try_fmt(struct file *file, void *fh,
>> +                           struct v4l2_format *f)
>> +{
>> +     struct c3_isp_capture *cap = video_drvdata(file);
>> +
>> +     c3_cap_try_fmt(cap, &f->fmt.pix);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
>> +                                struct v4l2_frmsizeenum *fsize)
>> +{
>> +     const struct c3_isp_capture_format *fmt;
>> +
>> +     if (fsize->index)
>> +             return -EINVAL;
>> +
>> +     fmt = c3_cap_find_fmt(fsize->pixel_format);
>> +     if (!fmt)
>> +             return -EINVAL;
>> +
>> +     fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
>> +     fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
>> +     fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
>> +     fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
>> +     fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
>> +     fsize->stepwise.step_width = 2;
>> +     fsize->stepwise.step_height = 2;
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
>> +     .vidioc_querycap                = c3_isp_cap_querycap,
>> +     .vidioc_enum_fmt_vid_cap        = c3_isp_cap_enum_fmt,
>> +     .vidioc_g_fmt_vid_cap           = c3_isp_cap_g_fmt,
>> +     .vidioc_s_fmt_vid_cap           = c3_isp_cap_s_fmt,
>> +     .vidioc_try_fmt_vid_cap         = c3_isp_cap_try_fmt,
>> +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>> +     .vidioc_querybuf                = vb2_ioctl_querybuf,
>> +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
>> +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
>> +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>> +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>> +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>> +     .vidioc_streamon                = vb2_ioctl_streamon,
>> +     .vidioc_streamoff               = vb2_ioctl_streamoff,
>> +     .vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
>> +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>> +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>> +};
>> +
>> +static const struct v4l2_file_operations isp_cap_v4l2_fops = {
>> +     .open = v4l2_fh_open,
>> +     .release = vb2_fop_release,
>> +     .poll = vb2_fop_poll,
>> +     .unlocked_ioctl = video_ioctl2,
>> +     .mmap = vb2_fop_mmap,
>> +};
>> +
>> +static int c3_isp_cap_link_validate(struct media_link *link)
>> +{
>> +     struct video_device *vdev =
>> +             media_entity_to_video_device(link->sink->entity);
>> +     struct v4l2_subdev *sd =
>> +             media_entity_to_v4l2_subdev(link->source->entity);
>> +     struct c3_isp_capture *cap = video_get_drvdata(vdev);
>> +     struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
>> +     struct v4l2_subdev_format src_fmt = {
>> +             .which = V4L2_SUBDEV_FORMAT_ACTIVE,
>> +             .pad = link->source->index,
>> +     };
>> +     const struct c3_isp_capture_format *cap_fmt =
>> +                             c3_cap_find_fmt(pix_fmt->pixelformat);
>> +     int ret;
>> +
>> +     ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
>> +     if (ret)
>> +             return ret;
>> +
>> +     if (src_fmt.format.width != pix_fmt->width ||
>> +         src_fmt.format.height != pix_fmt->height ||
>> +         src_fmt.format.code != cap_fmt->mbus_code) {
>> +             dev_err(cap->isp->dev,
>> +                     "link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n",
>> +                     link->source->entity->name, link->source->index,
>> +                     link->sink->entity->name, link->sink->index,
>> +                     src_fmt.format.code, src_fmt.format.width,
>> +                     src_fmt.format.height, cap_fmt->mbus_code,
>> +                     pix_fmt->width, pix_fmt->height);
>> +
>> +             return -EPIPE;
>> +     };
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct media_entity_operations isp_cap_entity_ops = {
>> +     .link_validate = c3_isp_cap_link_validate,
>> +};
>> +
>> +static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
>> +                               unsigned int *num_buffers,
>> +                               unsigned int *num_planes,
>> +                               unsigned int sizes[],
>> +                               struct device *alloc_devs[])
>> +{
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>> +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>> +
>> +     if (*num_planes) {
>> +             if (*num_planes != 1)
>> +                     return -EINVAL;
>> +
>> +             if (sizes[0] < pix->sizeimage)
>> +                     return -EINVAL;
>> +     } else {
>> +             *num_planes = 1;
>> +             sizes[0] = pix->sizeimage;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_vb2_buffer *buf =
>> +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned long flags;
>> +
>> +     spin_lock_irqsave(&cap->buff_lock, flags);
>> +
>> +     list_add_tail(&buf->list, &cap->pending);
>> +
>> +     spin_unlock_irqrestore(&cap->buff_lock, flags);
>> +}
>> +
>> +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
>> +{
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned int size = cap->vfmt.fmt.pix.sizeimage;
>> +
>> +     if (vb2_plane_size(vb, 0) < size) {
>> +             dev_err(cap->isp->dev,
>> +                     "User buffer too small (%ld < %u)\n",
>> +                     vb2_plane_size(vb, 0), size);
>> +             return -EINVAL;
>> +     }
>> +
>> +     vb2_set_plane_payload(vb, 0, size);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_vb2_buffer *buf =
>> +             container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>> +
>> +     buf->vaddr = vb2_plane_vaddr(vb, 0);
>> +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>> +
>> +     memset(buf->vaddr, 0, cap->vfmt.fmt.pix.sizeimage);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
>> +                                   unsigned int count)
>> +{
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>> +     int ret;
>> +
>> +     guard(mutex)(&cap->isp->lock);
>> +
>> +     ret = pm_runtime_resume_and_get(cap->isp->dev);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
>> +     if (ret) {
>> +             dev_err(cap->isp->dev,
>> +                     "Failed to start cap%u pipeline: %d\n", cap->id, ret);
>> +             goto err_pm_put;
>> +     }
>> +
>> +     if (c3_isp_pipeline_ready(cap->isp)) {
>> +             ret = v4l2_subdev_enable_streams(&cap->isp->core.sd,
>> +                                              C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> +                                              BIT(0));
>> +             if (ret)
>> +                     goto err_pipeline_stop;
>> +     }
>> +
>> +     c3_isp_rsz_start(cap->rsz);
>> +     c3_isp_cap_start(cap);
>> +
>> +     return 0;
>> +
>> +err_pipeline_stop:
>> +     video_device_pipeline_stop(&cap->vdev);
>> +err_pm_put:
>> +     pm_runtime_put(cap->isp->dev);
>> +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
>> +{
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>> +
>> +     guard(mutex)(&cap->isp->lock);
>> +
>> +     c3_isp_cap_stop(cap);
>> +     c3_isp_rsz_stop(cap->rsz);
>> +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
>> +
>> +     if (cap->isp->pipe.start_count == 1)
>> +             v4l2_subdev_disable_streams(&cap->isp->core.sd,
>> +                                         C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> +                                         BIT(0));
>> +
>> +     video_device_pipeline_stop(&cap->vdev);
>> +     pm_runtime_put(cap->isp->dev);
>> +}
>> +
>> +static const struct vb2_ops isp_video_vb2_ops = {
>> +     .queue_setup = c3_isp_vb2_queue_setup,
>> +     .buf_queue = c3_isp_vb2_buf_queue,
>> +     .buf_prepare = c3_isp_vb2_buf_prepare,
>> +     .buf_init = c3_isp_vb2_buf_init,
>> +     .wait_prepare = vb2_ops_wait_prepare,
>> +     .wait_finish = vb2_ops_wait_finish,
>> +     .start_streaming = c3_isp_vb2_start_streaming,
>> +     .stop_streaming = c3_isp_vb2_stop_streaming,
>> +};
>> +
>> +static int c3_isp_register_capture(struct c3_isp_capture *cap)
>> +{
>> +     struct video_device *vdev = &cap->vdev;
>> +     struct vb2_queue *vb2_q = &cap->vb2_q;
>> +     int ret;
>> +
>> +     snprintf(vdev->name, sizeof(vdev->name), "isp-cap%u", cap->id);
>> +     vdev->fops = &isp_cap_v4l2_fops;
>> +     vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
>> +     vdev->v4l2_dev = &cap->isp->v4l2_dev;
>> +     vdev->entity.ops = &isp_cap_entity_ops;
>> +     vdev->lock = &cap->lock;
>> +     vdev->minor = -1;
>> +     vdev->queue = vb2_q;
>> +     vdev->release = video_device_release_empty;
>> +     vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
>> +     vdev->vfl_dir = VFL_DIR_RX;
>> +     video_set_drvdata(vdev, cap);
>> +
>> +     vb2_q->drv_priv = cap;
>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>> +     vb2_q->ops = &isp_video_vb2_ops;
>> +     vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
>> +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>> +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>> +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>> +     vb2_q->dev = cap->isp->dev;
>> +     vb2_q->lock = &cap->lock;
>> +     vb2_q->min_queued_buffers = 2;
> I'm not sure what your plans regarding libcamera are, but be aware
> we're going to get stricter about this value. Ideally, we aim to have
> ISP driver set this value to 0 so that the ISP operates (produce
> statistics) even if no buffer is queued to the capture devices. The
> reason is that we want algorithms to run even if there are no capture
> buffers provided.
>
> In order to allow the driver to operate with no buffers, you could
> probably allocate a scratch buffer in the driver and use it whenever
> you receive a frame completed IRQ and have no buffers available in the
> cap->provided queue.
>
> See rkisp1_dummy_buf_create() in the RkISP1 driver and how dummy_buf
> is used there.
>

OK,  will refer rkisp1 driver  and add dummy buffer.

>> +
>> +     ret = vb2_queue_init(vb2_q);
>> +     if (ret < 0)
>> +             goto err_destroy;
>> +
>> +     cap->pad.flags = MEDIA_PAD_FL_SINK;
>> +     ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
>> +     if (ret < 0)
>> +             goto err_queue_release;
>> +
>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>> +     if (ret < 0) {
>> +             dev_err(cap->isp->dev,
>> +                     "Failed to register %s: %d\n", vdev->name, ret);
>> +             goto err_entity_cleanup;
>> +     }
>> +
>> +     return 0;
>> +
>> +err_entity_cleanup:
>> +     media_entity_cleanup(&vdev->entity);
>> +err_queue_release:
>> +     vb2_queue_release(vb2_q);
>> +err_destroy:
>> +     mutex_destroy(&cap->lock);
>> +     return ret;
>> +}
>> +
>> +int c3_isp_captures_register(struct c3_isp_device *isp)
>> +{
>> +     int ret;
>> +     unsigned int i;
>> +     struct c3_isp_capture *cap;
>> +
>> +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>> +             cap = &isp->caps[i];
>> +             memset(cap, 0, sizeof(*cap));
>> +
>> +             cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
>> +             cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
>> +             cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
>> +             cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
>> +             cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
>> +
>> +             c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
>> +
>> +             cap->id = i;
>> +             if (cap->id == C3_ISP_CAP_DEV_0)
>> +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_0];
>> +             else if (cap->id == C3_ISP_CAP_DEV_1)
>> +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_1];
>> +             else
>> +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_2];
>> +
>> +             cap->isp = isp;
>> +             INIT_LIST_HEAD(&cap->pending);
>> +             spin_lock_init(&cap->buff_lock);
>> +             mutex_init(&cap->lock);
>> +
>> +             ret = c3_isp_register_capture(cap);
>> +             if (ret) {
>> +                     cap->isp = NULL;
>> +                     mutex_destroy(&cap->lock);
>> +                     c3_isp_captures_unregister(isp);
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +void c3_isp_captures_unregister(struct c3_isp_device *isp)
>> +{
>> +     unsigned int i;
>> +     struct c3_isp_capture *cap;
>> +
>> +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>> +             cap = &isp->caps[i];
>> +
>> +             if (!cap->isp)
>> +                     continue;
>> +             vb2_queue_release(&cap->vb2_q);
>> +             media_entity_cleanup(&cap->vdev.entity);
>> +             video_unregister_device(&cap->vdev);
>> +             mutex_destroy(&cap->lock);
>> +     }
>> +}
>> +
>> +void c3_isp_captures_done(struct c3_isp_device *isp)
>> +{
>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
>> +}
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
>> new file mode 100644
>> index 000000000000..19f2a3bc29c9
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
>> @@ -0,0 +1,327 @@
>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#ifndef __C3_ISP_COMMON_H__
>> +#define __C3_ISP_COMMON_H__
>> +
>> +#include <linux/clk.h>
>> +
>> +#include <media/media-device.h>
>> +#include <media/videobuf2-core.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-subdev.h>
>> +#include <media/videobuf2-v4l2.h>
>> +
>> +#define C3_ISP_DRIVER_NAME            "c3-isp"
>> +#define C3_ISP_CLOCK_NUM_MAX          3
>> +
>> +#define C3_ISP_CORE_DEF_SINK_PAD_FMT  MEDIA_BUS_FMT_SRGGB10_1X10
>> +#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUYV8_1_5X8
>> +#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUYV8_1_5X8
>> +#define C3_ISP_DEFAULT_WIDTH          1920
>> +#define C3_ISP_DEFAULT_HEIGHT         1080
>> +#define C3_ISP_MAX_WIDTH              2888
>> +#define C3_ISP_MAX_HEIGHT             2240
>> +#define C3_ISP_MIN_WIDTH              160
>> +#define C3_ISP_MIN_HEIGHT             120
>> +
>> +#define C3_DISP_INTER                 0x400
>> +#define C3_DISP_REG(base, id)         ((base) + (id) * C3_DISP_INTER)
>> +#define C3_WRMIFX3_INTER              0x100
>> +#define C3_WRMIFX3_REG(base, id)      ((base) + (id) * C3_WRMIFX3_INTER)
>> +#define C3_PPS_TAP4_S11_H_NUM         33
>> +#define C3_PPS_LUT_CTYPE_0            0
>> +#define C3_PPS_LUT_CTYPE_2            2
>> +#define C3_SCALE_EN                   1
>> +#define C3_SCALE_DIS                  0
>> +
>> +#define C3_ISP_PHASE_OFFSET_0         0
>> +#define C3_ISP_PHASE_OFFSET_1         1
>> +#define C3_ISP_PHASE_OFFSET_NONE      0xff
>> +
>> +enum c3_isp_core_pads {
>> +     C3_ISP_CORE_PAD_SINK_VIDEO,
>> +     C3_ISP_CORE_PAD_SINK_PARAMS,
>> +     C3_ISP_CORE_PAD_SOURCE_STATS,
>> +     C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> +     C3_ISP_CORE_PAD_MAX
>> +};
>> +
>> +enum c3_isp_resizer_ids {
>> +     C3_ISP_RSZ_0,
>> +     C3_ISP_RSZ_1,
>> +     C3_ISP_RSZ_2,
>> +     C3_ISP_NUM_RSZ
>> +};
>> +
>> +enum c3_isp_resizer_pads {
>> +     C3_ISP_RESIZER_PAD_SINK,
>> +     C3_ISP_RESIZER_PAD_SOURCE,
>> +     C3_ISP_RESIZER_PAD_MAX
>> +};
>> +
>> +enum c3_isp_cap_devs {
>> +     C3_ISP_CAP_DEV_0,
>> +     C3_ISP_CAP_DEV_1,
>> +     C3_ISP_CAP_DEV_2,
>> +     C3_ISP_NUM_CAP_DEVS
>> +};
>> +
>> +/**
>> + * struct c3_isp_pps_io_size - isp scaler input and output size
>> + *
>> + * @thsize: input horizontal size of after preprocessing
>> + * @tvsize: input vertical size of after preprocessing
>> + * @ohsize: output horizontal size
>> + * @ovsize: output vertical size
>> + * @ihsize: input horizontal size
>> + * @max_hsize: maximum horizontal size
>> + */
>> +struct c3_isp_pps_io_size {
>> +     u32 thsize;
>> +     u32 tvsize;
>> +     u32 ohsize;
>> +     u32 ovsize;
>> +     u32 ihsize;
>> +     u32 max_hsize;
>> +};
>> +
>> +/**
>> + * @mbus_code: the mbus code
>> + * @pads: save the pad flag of this mbus_code
>> + * @xofst: horizontal phase offset of hardware
>> + * @yofst: vertical phase offset of hardware
>> + */
>> +struct c3_isp_mbus_format_info {
>> +     u32 mbus_code;
>> +     u32 pads;
>> +     u8 xofst;
>> +     u8 yofst;
>> +};
>> +
>> +/**
>> + * @mbus_code: the mbus code
>> + * @fourcc: pixel format
>> + * @depth: pixel width
>> + */
>> +struct c3_isp_capture_format {
>> +     u32 mbus_code;
>> +     u32 fourcc;
>> +     u8 depth;
>> +};
>> +
>> +/**
>> + * struct c3_isp_vb2_buffer - A container of vb2 buffer
>> + *
>> + * @vb: vb2 buffer
>> + * @vaddr: buffer virtual address
>> + * @paddr: buffer physical address
>> + * @list: entry of the buffer in the queue
>> + */
>> +struct c3_isp_vb2_buffer {
>> +     struct vb2_v4l2_buffer vb;
>> +     void *vaddr;
>> +     dma_addr_t paddr;
>> +     struct list_head list;
>> +};
>> +
>> +/**
>> + * struct c3_isp_core - ISP core subdev
>> + *
>> + * @sd: ISP sub-device
>> + * @pads: ISP sub-device pads
>> + * @src_sd: source sub-device
>> + * @isp: pointer to c3_isp_device
>> + * @src_sd_pad: source sub-device pad
>> + */
>> +struct c3_isp_core {
>> +     struct v4l2_subdev sd;
>> +     struct media_pad pads[C3_ISP_CORE_PAD_MAX];
>> +     struct v4l2_subdev *src_sd;
>> +     u16 src_sd_pad;
>> +     struct c3_isp_device *isp;
>> +};
>> +
>> +/**
>> + * struct c3_isp_resizer - ISP resizer subdev
>> + *
>> + * @id: resizer id
>> + * @sd: resizer sub-device
>> + * @pads: resizer sub-device pads
>> + * @isp: pointer to c3_isp_device
>> + * @cap: pointer to c3_isp_capture
>> + */
>> +struct c3_isp_resizer {
>> +     enum c3_isp_resizer_ids id;
>> +     struct v4l2_subdev sd;
>> +     struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
>> +     struct c3_isp_device *isp;
>> +     struct c3_isp_capture *cap;
>> +};
>> +
>> +/**
>> + * struct c3_isp_stats - ISP statistics device
>> + *
>> + * @vb2_q: vb2 buffer queue
>> + * @vdev: video node
>> + * @vfmt: v4l2_format of the metadata format
>> + * @pad: media pad
>> + * @lock: protects vb2_q, vdev
>> + * @is_streaming: stats status
>> + * @isp: pointer to c3_isp_device
>> + * @buff: in use buffer
>> + * @buff_lock: protects stats buffer
>> + * @pending: stats buffer list head
>> + */
>> +struct c3_isp_stats {
>> +     struct vb2_queue vb2_q;
>> +     struct video_device vdev;
>> +     struct v4l2_format vfmt;
>> +     struct media_pad pad;
>> +
>> +     struct mutex lock; /* Protects vb2_q, vdev */
>> +     bool is_streaming;
>> +     struct c3_isp_device *isp;
>> +
>> +     struct c3_isp_vb2_buffer *buff;
>> +     spinlock_t buff_lock; /* Protects stream buffer */
>> +     struct list_head pending;
>> +};
>> +
>> +/**
>> + * struct c3_isp_params - ISP parameters device
>> + *
>> + * @vb2_q: vb2 buffer queue
>> + * @vdev: video node
>> + * @vfmt: v4l2_format of the metadata format
>> + * @pad: media pad
>> + * @lock: protects vb2_q, vdev
>> + * @isp: pointer to c3_isp_device
>> + * @buff: in use buffer
>> + * @buff_lock: protects stats buffer
>> + * @pending: stats buffer list head
>> + */
>> +struct c3_isp_params {
>> +     struct vb2_queue vb2_q;
>> +     struct video_device vdev;
>> +     struct v4l2_format vfmt;
>> +     struct media_pad pad;
>> +
>> +     struct mutex lock; /* Protects vb2_q, vdev */
>> +     struct c3_isp_device *isp;
>> +
>> +     struct c3_isp_vb2_buffer *buff;
>> +     spinlock_t buff_lock; /* Protects stream buffer */
>> +     struct list_head pending;
>> +};
>> +
>> +/**
>> + * struct c3_isp_capture - ISP capture device
>> + *
>> + * @id: capture device ID
>> + * @vb2_q: vb2 buffer queue
>> + * @vdev: video node
>> + * @vfmt: v4l2_format of the capture format
>> + * @pad: media pad
>> + * @lock: protects vb2_q, vdev
>> + * @is_streaming: capture device status
>> + * @isp: pointer to c3_isp_device
>> + * @rsz: pointer to c3_isp_resizer
>> + * @buff: in use buffer
>> + * @buff_lock: protects capture buffer
>> + * @pending: capture buffer list head
>> + */
>> +struct c3_isp_capture {
>> +     enum c3_isp_cap_devs id;
>> +     struct vb2_queue vb2_q;
>> +     struct video_device vdev;
>> +     struct v4l2_format vfmt;
>> +     struct media_pad pad;
>> +
>> +     struct mutex lock; /* Protects vb2_q, vdev */
>> +     bool is_streaming;
>> +     struct c3_isp_device *isp;
>> +     struct c3_isp_resizer *rsz;
>> +
>> +     struct c3_isp_vb2_buffer *buff;
>> +     spinlock_t buff_lock; /* Protects stream buffer */
>> +     struct list_head pending;
>> +};
>> +
>> +/**
>> + * struct c3_isp_info - ISP information
>> + *
>> + * @clocks: array of ISP clock names
>> + * @clock_rates: array of ISP clock rate
>> + * @clock_num: actual clock number
>> + */
>> +struct c3_isp_info {
>> +     char *clocks[C3_ISP_CLOCK_NUM_MAX];
>> +     u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
>> +     u32 clock_num;
>> +};
>> +
>> +/**
>> + * struct c3_isp_device - ISP platform device
>> + *
>> + * @dev: pointer to the struct device
>> + * @base: base register address
>> + * @clks: array of clocks
>> + * @notifier: notifier to register on the v4l2-async API
>> + * @v4l2_dev: v4l2_device variable
>> + * @media_dev: media device variable
>> + * @pipe: media pipeline
>> + * @core: ISP core subdev
>> + * @resizer: ISP resizer subdev
>> + * @stats: ISP stats device
>> + * @params: ISP params device
>> + * @caps: array of ISP capture device
>> + * @frm_sequence: used to record frame id
>> + * @lock: protect ISP device
>> + * @info: version-specific ISP information
>> + */
>> +struct c3_isp_device {
>> +     struct device *dev;
>> +     void __iomem *base;
>> +     struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
>> +
>> +     struct v4l2_async_notifier notifier;
>> +     struct v4l2_device v4l2_dev;
>> +     struct media_device media_dev;
>> +     struct media_pipeline pipe;
>> +
>> +     struct c3_isp_core core;
>> +     struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
>> +     struct c3_isp_stats stats;
>> +     struct c3_isp_params params;
>> +     struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
>> +
>> +     u32 frm_sequence;
>> +     struct mutex lock; /* Protect ISP device */
>> +     const struct c3_isp_info *info;
>> +};
>> +
>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
>> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp);
>> +
>> +int c3_isp_core_register(struct c3_isp_device *isp);
>> +void c3_isp_core_unregister(struct c3_isp_device *isp);
>> +int c3_isp_resizers_register(struct c3_isp_device *isp);
>> +void c3_isp_resizers_unregister(struct c3_isp_device *isp);
>> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz);
>> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz);
>> +int c3_isp_captures_register(struct c3_isp_device *isp);
>> +void c3_isp_captures_unregister(struct c3_isp_device *isp);
>> +void c3_isp_captures_done(struct c3_isp_device *isp);
>> +int c3_isp_stats_register(struct c3_isp_device *isp);
>> +void c3_isp_stats_unregister(struct c3_isp_device *isp);
>> +int c3_isp_stats_done(struct c3_isp_device *isp);
>> +int c3_isp_params_register(struct c3_isp_device *isp);
>> +void c3_isp_params_unregister(struct c3_isp_device *isp);
>> +int c3_isp_params_done(struct c3_isp_device *isp);
>> +
>> +#endif
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
>> new file mode 100644
>> index 000000000000..d3672aff9fd2
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
>> @@ -0,0 +1,675 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/pm_runtime.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +#include "include/uapi/c3-isp-config.h"
>> +
>> +#define C3_ISP_CORE_SUBDEV_NAME        "isp-core"
>> +
>> +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
>> +     /* RAW formats */
>> +     {
>> +             .mbus_code      = MEDIA_BUS_FMT_SBGGR10_1X10,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SGBRG10_1X10,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SGRBG10_1X10,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SRGGB10_1X10,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SBGGR12_1X12,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SGBRG12_1X12,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SGRBG12_1X12,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SRGGB12_1X12,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>> +     },
>> +     /* YUV formats */
>> +     {
>> +             .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_1_5X8,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_2X8,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +     },
>> +};
>> +
>> +static const struct c3_isp_mbus_format_info
>> +*core_find_format_by_code(u32 code, u32 pad)
>> +{
>> +     int i;
> unsigned


Will use unsigned int i.

>> +
>> +     for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>> +             const struct c3_isp_mbus_format_info *info =
>> +                     &c3_isp_core_mbus_formats[i];
>> +
>> +             if (info->mbus_code == code && info->pads & BIT(pad))
>> +                     return info;
>> +     }
>> +
>> +     return NULL;
>> +}
>> +
>> +static const struct c3_isp_mbus_format_info
>> +*core_find_format_by_index(u32 index, u32 pad)
>> +{
>> +     int i;
> unsigned
>

Will use unsigned int i.

>> +
>> +     for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>> +             const struct c3_isp_mbus_format_info *info =
>> +                     &c3_isp_core_mbus_formats[i];
>> +
>> +             if (!(info->pads & BIT(pad)))
>> +                     continue;
>> +
>> +             if (!index)
>> +                     return info;
>> +
>> +             index--;
>> +     }
>> +
>> +     return NULL;
>> +}
>> +
>> +static void c3_isp_core_enable(struct c3_isp_device *isp)
>> +{
>> +     /* Select the line sync signal */
>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_LINE_THRD,
>> +                        TOP_IRQ_DIN_HSYNC, TOP_IRQ_DIN_HSYNC);
>> +
>> +     /* Enable frame done and stats error irq */
>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
>> +                        TOP_IRQ_FRAME_DONE, TOP_IRQ_FRAME_DONE);
>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
>> +                        TOP_IRQ_STATS_ERR, TOP_IRQ_STATS_ERR);
>> +
>> +     /* Enable image data to ISP core */
>> +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
>> +                        TOP_DATA_PATH_MASK, TOP_PATH_MIPI_TO_CORE);
>> +}
>> +
>> +static void c3_isp_core_disable(struct c3_isp_device *isp)
>> +{
>> +     /* Disable image data to ISP core */
>> +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
>> +                        TOP_DATA_PATH_MASK, 0x0);
>> +
>> +     /* Disable all irq */
>> +     c3_isp_write(isp, ISP_TOP_IRQ_EN, 0x0);
>> +}
>> +
>> +/* Set the phase offset of blc, wb and lns */
>> +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
>> +                               u8 xofst, u8 yofst)
>> +{
>> +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
>> +                        LSWB_BLC_XPHS_OFST_MASK,
>> +                        xofst << LSWB_BLC_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
>> +                        LSWB_BLC_YPHS_OFST_MASK, yofst);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
>> +                        LSWB_WB_XPHS_OFST_MASK,
>> +                        xofst << LSWB_WB_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
>> +                        LSWB_WB_YPHS_OFST_MASK, yofst);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
>> +                        LSWB_LNS_XPHS_OFST_MASK,
>> +                        xofst << LSWB_LNS_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
>> +                        LSWB_LNS_YPHS_OFST_MASK, yofst);
>> +}
>> +
>> +/* Set the phase offset of af, ae and awb */
>> +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
>> +                             u8 xofst, u8 yofst)
>> +{
>> +     c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_XPHS_OFST_MASK,
>> +                        xofst << AF_CTRL_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_YPHS_OFST_MASK,
>> +                        yofst << AF_CTRL_YPHS_OFST_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_XPHS_OFST_MASK,
>> +                        xofst << AE_CTRL_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_YPHS_OFST_MASK,
>> +                        yofst << AE_CTRL_YPHS_OFST_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_XPHS_OFST_MASK,
>> +                        xofst << AWB_CTRL_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_YPHS_OFST_MASK, yofst);
>> +}
>> +
>> +/* Set the phase offset of demosaic */
>> +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
>> +                              u8 xofst, u8 yofst)
>> +{
>> +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_XPHS_OFST_MASK,
>> +                        xofst << DMS_COMMON_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_YPHS_OFST_MASK, yofst);
>> +}
>> +
>> +static void c3_isp_core_cfg_ofst(struct c3_isp_device *isp,
>> +                              struct v4l2_mbus_framefmt *fmt)
>> +{
>> +     const struct c3_isp_mbus_format_info *isp_fmt =
>> +                     core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
>> +
>> +     c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>> +     c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>> +     c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>> +}
>> +
>> +/* Set format of the hardware control module */
>> +static void c3_isp_core_top_fmt(struct c3_isp_device *isp,
>> +                             struct v4l2_mbus_framefmt *fmt)
>> +{
>> +     c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
>> +                  TOP_INPUT_HSIZE(fmt->width) | TOP_INPUT_VSIZE(fmt->height));
>> +
>> +     c3_isp_write(isp, ISP_TOP_FRM_SIZE,
>> +                  TOP_FRM_CORE_IHSIZE(fmt->width) | TOP_FRM_CORE_IVSIZE(fmt->height));
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE, TOP_HOLD_HSIZE_MASK,
>> +                        fmt->width << TOP_HOLD_HSIZE_SHIFT);
>> +}
>> +
>> +static void c3_isp_core_af_fmt(struct c3_isp_device *isp,
>> +                            struct v4l2_mbus_framefmt *fmt)
>> +{
>> +     u32 hidx;
>> +     u32 vidx;
>> +     int i;
>> +
>> +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, AF_HV_STAT_HBLK_NUM_MASK,
>> +                        AF_STAT_BLKH_NUM << AF_HV_STAT_HBLK_NUM_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM,
>> +                        AF_HV_STAT_VBLK_NUM_MASK, AF_STAT_BLKV_NUM);
>> +
>> +     c3_isp_write(isp, ISP_AF_HV_SIZE,
>> +                  AF_HV_STAT_HSIZE(fmt->width) | AF_HV_STAT_VSIZE(fmt->height));
>> +
>> +     /* Set the index address to 0 position */
>> +     c3_isp_write(isp, ISP_AF_IDX_ADDR, 0);
>> +     /*
>> +      * Calculate and set the coordinates of points in the grid.
>> +      * hidx and vidx need to be aligned with 2.
>> +      */
>> +     for (i = 0; i <= AF_STAT_BLKH_NUM; i++) {
>> +             hidx = i * fmt->width / AF_STAT_BLKH_NUM;
>> +             hidx = ALIGN_DOWN(hidx, 2);
>> +
>> +             vidx = i * fmt->height / AF_STAT_BLKV_NUM;
>> +             vidx = min(vidx, fmt->height);
>> +             vidx = ALIGN_DOWN(vidx, 2);
>> +             c3_isp_write(isp, ISP_AF_IDX_DATA,
>> +                          AF_IDX_HIDX_DATA(hidx) | AF_IDX_VIDX_DATA(vidx));
>> +     }
>> +}
>> +
>> +static void c3_isp_core_ae_fmt(struct c3_isp_device *isp,
>> +                            struct v4l2_mbus_framefmt *fmt)
>> +{
>> +     u32 hidx;
>> +     u32 vidx;
>> +     int i;
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, AE_HV_STAT_HBLK_NUM_MASK,
>> +                        AE_STAT_BLKH_NUM << AE_HV_STAT_HBLK_NUM_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM,
>> +                        AE_HV_STAT_VBLK_NUM_MASK, AE_STAT_BLKV_NUM);
>> +
>> +     c3_isp_write(isp, ISP_AE_HV_SIZE,
>> +                  AE_HV_STAT_HSIZE(fmt->width) | AE_HV_STAT_VSIZE(fmt->height));
>> +
>> +     /* Set the index address to 0 position */
>> +     c3_isp_write(isp, ISP_AE_IDX_ADDR, 0);
>> +     /*
>> +      * Calculate and set the coordinates of points in the grid.
>> +      * hidx and vidx need to be aligned with 2.
>> +      */
>> +     for (i = 0; i <= AE_STAT_BLKH_NUM; i++) {
>> +             hidx = i * fmt->width / AE_STAT_BLKH_NUM;
>> +             hidx = ALIGN_DOWN(hidx, 2);
>> +
>> +             vidx = i * fmt->height / AE_STAT_BLKV_NUM;
>> +             vidx = min(vidx, fmt->height);
>> +             vidx = ALIGN_DOWN(vidx, 2);
>> +
>> +             c3_isp_write(isp, ISP_AE_IDX_DATA,
>> +                          AE_IDX_HIDX_DATA(hidx) | AE_IDX_VIDX_DATA(vidx));
>> +     }
>> +}
>> +
>> +static void c3_isp_core_awb_fmt(struct c3_isp_device *isp,
>> +                             struct v4l2_mbus_framefmt *fmt)
>> +{
>> +     u32 hidx;
>> +     u32 vidx;
>> +     int i;
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, AWB_HV_STAT_HBLK_NUM_MASK,
>> +                        AWB_STAT_BLKH_NUM << AWB_HV_STAT_HBLK_NUM_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM,
>> +                        AWB_HV_STAT_VBLK_NUM_MASK, AWB_STAT_BLKV_NUM);
>> +
>> +     c3_isp_write(isp, ISP_AWB_HV_SIZE,
>> +                  AWB_HV_STAT_HSIZE(fmt->width) | AWB_HV_STAT_VSIZE(fmt->height));
>> +
>> +     /* Set the index address to 0 position */
>> +     c3_isp_write(isp, ISP_AWB_IDX_ADDR, 0);
>> +     /*
>> +      * Calculate and set the coordinates of points in the grid.
>> +      * hidx and vidx need to be aligned with 2.
>> +      */
>> +     for (i = 0; i <= AWB_STAT_BLKH_NUM; i++) {
>> +             hidx = i * fmt->width / AWB_STAT_BLKH_NUM;
>> +             hidx = ALIGN_DOWN(hidx, 2);
>> +
>> +             vidx = i * fmt->height / AWB_STAT_BLKV_NUM;
>> +             vidx = min(vidx, fmt->height);
>> +             vidx = ALIGN_DOWN(vidx, 2);
>> +
>> +             c3_isp_write(isp, ISP_AWB_IDX_DATA,
>> +                          AWB_IDX_HIDX_DATA(hidx) | AWB_IDX_VIDX_DATA(vidx));
>> +     }
>> +}
>> +
>> +static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
>> +                                struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +
>> +     fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>> +
>> +     c3_isp_core_cfg_ofst(isp, fmt);
>> +     c3_isp_core_top_fmt(isp, fmt);
>> +     c3_isp_core_af_fmt(isp, fmt);
>> +     c3_isp_core_ae_fmt(isp, fmt);
>> +     c3_isp_core_awb_fmt(isp, fmt);
>> +}
>> +
>> +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
>> +                                   struct v4l2_subdev_state *state,
>> +                                   u32 pad, u64 streams_mask)
>> +{
>> +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     core->isp->frm_sequence = 0;
>> +     c3_isp_core_cfg_format(core->isp, state);
>> +     c3_isp_core_enable(core->isp);
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +                                                    C3_ISP_CORE_PAD_SINK_VIDEO,
>> +                                                    &streams_mask);
>> +     ret = v4l2_subdev_enable_streams(core->src_sd,
>> +                                      core->src_sd_pad, sink_streams);
>> +     if (ret) {
>> +             c3_isp_core_disable(core->isp);
>> +             return ret;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
>> +                                    struct v4l2_subdev_state *state,
>> +                                    u32 pad, u64 streams_mask)
>> +{
>> +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +                                                    C3_ISP_CORE_PAD_SINK_VIDEO,
>> +                                                    &streams_mask);
>> +     ret = v4l2_subdev_disable_streams(core->src_sd,
>> +                                       core->src_sd_pad, sink_streams);
>> +     if (ret)
>> +             return ret;
>> +
>> +     c3_isp_core_disable(core->isp);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state,
>> +                                struct v4l2_subdev_krouting *routing)
>> +{
>> +     static const struct v4l2_mbus_framefmt format = {
>> +             .width = C3_ISP_DEFAULT_WIDTH,
>> +             .height = C3_ISP_DEFAULT_HEIGHT,
>> +             .code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
>> +             .field = V4L2_FIELD_NONE,
>> +             .colorspace = V4L2_COLORSPACE_SRGB,
>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>> +             .xfer_func = V4L2_XFER_FUNC_SRGB,
>> +     };
>> +     int ret;
>> +
>> +     ret = v4l2_subdev_routing_validate(sd, routing,
>> +                                        V4L2_SUBDEV_ROUTING_NO_SOURCE_STREAM_MIX);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
>> +                                 struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_subdev_route routes[2];
>> +     struct v4l2_subdev_krouting routing;
>> +
>> +     routes[0].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
>> +     routes[0].sink_stream = 0;
>> +     routes[0].source_pad = C3_ISP_CORE_PAD_SOURCE_STATS;
>> +     routes[0].source_stream = 0;
>> +     routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>> +
>> +     routes[1].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
>> +     routes[1].sink_stream = 0;
>> +     routes[1].source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
>> +     routes[1].source_stream = 0;
>> +     routes[1].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>> +
>> +     routing.num_routes = ARRAY_SIZE(routes);
>> +     routing.routes = routes;
>> +
>> +     return c3_isp_core_cfg_routing(sd, state, &routing);
>> +}
>> +
>> +static int c3_isp_core_set_routing(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state,
>> +                                enum v4l2_subdev_format_whence which,
>> +                                struct v4l2_subdev_krouting *routing)
> I'm not sure I see a reason for the ISP subdev to implement routing.
> In my understanding it will only receive a single image stream and
> processes it to 3 DMA output nodes, and to control which output is
> enabled you use media links.
>

There is no need to use routing.

Will remove this interface.

>> +{
>> +     bool is_streaming = v4l2_subdev_is_streaming(sd);
>> +
>> +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>> +             return -EBUSY;
>> +
>> +     return c3_isp_core_cfg_routing(sd, state, routing);
>> +}
>> +
>> +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
>> +                                   struct v4l2_subdev_state *state,
>> +                                   struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +     const struct c3_isp_mbus_format_info *info;
>> +     int ret = 0;
>> +
>> +     switch (code->pad) {
>> +     case C3_ISP_CORE_PAD_SINK_VIDEO:
>> +     case C3_ISP_CORE_PAD_SOURCE_VIDEO:
>> +             info = core_find_format_by_index(code->index, code->pad);
>> +             if (!info)
>> +                     ret = -EINVAL;
>> +             else
>> +                     code->code = info->mbus_code;
>> +
>> +             break;
>> +     case C3_ISP_CORE_PAD_SINK_PARAMS:
>> +     case C3_ISP_CORE_PAD_SOURCE_STATS:
>> +             if (code->index)
>> +                     ret = -EINVAL;
>> +             else
>> +                     code->code = MEDIA_BUS_FMT_METADATA_FIXED;
>> +
>> +             break;
>> +     default:
>> +             ret = -EINVAL;
>> +             break;
>> +     }
>> +
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_core_set_sink_fmt(struct v4l2_subdev_state *state,
>> +                                  struct v4l2_subdev_format *format)
>> +{
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +
>> +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
>> +     if (!isp_fmt)
>> +             sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>> +     else
>> +             sink_fmt->code = format->format.code;
>> +
>> +     sink_fmt->width = clamp_t(u32, format->format.width,
>> +                               C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>> +     sink_fmt->height = clamp_t(u32, format->format.height,
>> +                                C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>> +
>> +     format->format = *sink_fmt;
>> +}
>> +
>> +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
>> +                                    struct v4l2_subdev_format *format)
>> +{
>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>> +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +
>> +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
>> +     if (!isp_fmt)
>> +             src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>> +     else
>> +             src_fmt->code = format->format.code;
>> +
>> +     /* The source size must be same with the sink size. */
>> +     src_fmt->width  = sink_fmt->width;
>> +     src_fmt->height = sink_fmt->height;
>> +
>> +     format->format = *src_fmt;
>> +}
>> +
>> +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
>> +                            struct v4l2_subdev_state *state,
>> +                            struct v4l2_subdev_format *format)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +
>> +     switch (format->pad) {
>> +     case C3_ISP_CORE_PAD_SINK_VIDEO:
>> +             c3_isp_core_set_sink_fmt(state, format);
>> +             break;
>> +     case C3_ISP_CORE_PAD_SINK_PARAMS:
>> +     case C3_ISP_CORE_PAD_SOURCE_STATS:
>> +             fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +             format->format = *fmt;
>> +             break;
>> +     case C3_ISP_CORE_PAD_SOURCE_VIDEO:
>> +             c3_isp_core_set_source_fmt(state, format);
>> +             break;
>> +     default:
> I don't think this can happen. The core validates that format->pad is
> correct.
>
Will remove this branch or use "if" to replace "switch".
>> +             dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
>> +             return -ENOTTY;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_core_init_state(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
>> +
>> +     /* Video sink pad */
>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>> +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>> +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>> +     sink_fmt->field = V4L2_FIELD_NONE;
>> +     sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>> +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>> +     sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
>> +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>> +     sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
>> +
>> +     /* Video source pad */
>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
>> +     src_fmt->width = C3_ISP_DEFAULT_WIDTH;
>> +     src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>> +     src_fmt->field = V4L2_FIELD_NONE;
>> +     src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>> +     src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>> +     src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>> +     src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>> +     src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>> +
>> +     /* Parameters pad */
>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
>> +     sink_fmt->width = 0;
>> +     sink_fmt->height = 0;
>> +     sink_fmt->field = V4L2_FIELD_NONE;
>> +     sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>> +
>> +     /* Statistics pad */
>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
>> +     src_fmt->width = 0;
>> +     src_fmt->height = 0;
>> +     src_fmt->field = V4L2_FIELD_NONE;
>> +     src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>> +
>> +     return c3_isp_core_init_routing(sd, state);
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
>> +     .enum_mbus_code = c3_isp_core_enum_mbus_code,
>> +     .get_fmt = v4l2_subdev_get_fmt,
>> +     .set_fmt = c3_isp_core_set_fmt,
>> +     .enable_streams = c3_isp_core_enable_streams,
>> +     .disable_streams = c3_isp_core_disable_streams,
>> +     .set_routing = c3_isp_core_set_routing,
>> +};
>> +
>> +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
>> +     .pad = &c3_isp_core_pad_ops,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
>> +     .init_state = c3_isp_core_init_state,
>> +};
>> +
>> +static int c3_isp_core_link_validate(struct media_link *link)
>> +{
>> +     if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
>> +             return 0;
>> +
>> +     return v4l2_subdev_link_validate(link);
>> +}
>> +
>> +/* Media entity operations */
>> +static const struct media_entity_operations c3_isp_core_entity_ops = {
>> +     .link_validate = c3_isp_core_link_validate,
>> +};
>> +
>> +int c3_isp_core_register(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_core *core = &isp->core;
>> +     struct v4l2_subdev *sd = &core->sd;
>> +     int ret;
>> +
>> +     v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
>> +     sd->owner = THIS_MODULE;
>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +     sd->internal_ops = &c3_isp_core_internal_ops;
>> +     snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
>> +
>> +     sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
>> +     sd->entity.ops = &c3_isp_core_entity_ops;
>> +
>> +     core->isp = isp;
>> +     sd->dev = isp->dev;
>> +     v4l2_set_subdevdata(sd, core);
>> +
>> +     core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
>> +     core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
>> +     core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
>> +     core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
>> +     ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_init_finalize(sd);
>> +     if (ret)
>> +             goto err_entity_cleanup;
>> +
>> +     ret = v4l2_device_register_subdev(&isp->v4l2_dev, sd);
>> +     if (ret)
>> +             goto err_subdev_cleanup;
>> +
>> +     return 0;
>> +
>> +err_subdev_cleanup:
>> +     v4l2_subdev_cleanup(sd);
>> +err_entity_cleanup:
>> +     media_entity_cleanup(&sd->entity);
>> +     return ret;
>> +}
>> +
>> +void c3_isp_core_unregister(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_core *core = &isp->core;
>> +     struct v4l2_subdev *sd = &core->sd;
>> +
>> +     v4l2_device_unregister_subdev(sd);
>> +     v4l2_subdev_cleanup(sd);
>> +     media_entity_cleanup(&sd->entity);
>> +}
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
>> new file mode 100644
>> index 000000000000..a57b9f8dbc3c
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
>> @@ -0,0 +1,486 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/device.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-common.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-fwnode.h>
>> +#include <media/v4l2-mc.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +
>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
>> +{
>> +     return readl(isp->base + reg);
>> +}
>> +
>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
>> +{
>> +     writel(val, isp->base + reg);
>> +}
>> +
>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
>> +{
>> +     u32 orig, tmp;
>> +
>> +     orig = c3_isp_read(isp, reg);
>> +
>> +     tmp = orig & ~mask;
>> +     tmp |= val & mask;
>> +
>> +     if (tmp != orig)
>> +             c3_isp_write(isp, reg, tmp);
>> +}
>> +
>> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp)
>> +{
>> +     struct media_pipeline_entity_iter iter;
>> +     unsigned int n_video_devices = 0;
>> +     struct media_entity *entity;
>> +     int ret;
>> +
>> +     ret = media_pipeline_entity_iter_init(&isp->pipe, &iter);
>> +     if (ret)
>> +             return ret;
>> +
>> +     media_pipeline_for_each_entity(&isp->pipe, &iter, entity) {
>> +             if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
>> +                     n_video_devices++;
>> +     }
>> +
>> +     media_pipeline_entity_iter_cleanup(&iter);
>> +
>> +     return n_video_devices == isp->pipe.start_count;
>> +}
> As suggested in the review of the -params.c module, I suggest to
> implement start_streaming on the capture nodes only. From there you
> can call enable_streams on the ISP subdevice. The ISP subdevice can
> count how many links to resizers are enabled, and actually start its
> operation when all the enabled ones have been started.
>
> The idea is to use enabled media links to identify how many capture video
> devices are expected to be used, and only start the ISP (and the
> downstream subdevices like the CSI-2 Adap and RX and the sensor) when
> all of linked ones have been started.
>

OK, Will implement start_streaming on the capture nodes only and count 
how many

links to resizer are enabled in ISP sub device.

>> +
>> +/* PM runtime suspend */
>> +static int __maybe_unused c3_isp_runtime_suspend(struct device *dev)
>> +{
>> +     struct c3_isp_device *isp = dev_get_drvdata(dev);
>> +
>> +     clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
>> +
>> +     return 0;
>> +}
>> +
>> +/* PM runtime resume */
>> +static int __maybe_unused c3_isp_runtime_resume(struct device *dev)
>> +{
>> +     struct c3_isp_device *isp = dev_get_drvdata(dev);
>> +
>> +     return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
>> +}
>> +
>> +static const struct dev_pm_ops c3_isp_pm_ops = {
>> +     SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>> +                             pm_runtime_force_resume)
>> +     SET_RUNTIME_PM_OPS(c3_isp_runtime_suspend,
>> +                        c3_isp_runtime_resume, NULL)
>> +};
>> +
>> +/* IRQ handling */
>> +static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
>> +{
>> +     struct c3_isp_device *isp = dev;
>> +     u32 status;
>> +
>> +     /* Get irq status and clear irq status */
>> +     status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
>> +     c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
>> +
>> +     if (status & TOP_IRQ_FRAME_DONE) {
>> +             c3_isp_stats_done(isp);
>> +             c3_isp_params_done(isp);
>> +             c3_isp_captures_done(isp);
>> +             isp->frm_sequence++;
>> +     }
>> +
>> +     if (status & TOP_IRQ_STATS_ERR)
>> +             dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
>> +
>> +     return IRQ_HANDLED;
>> +}
>> +
>> +/* Subdev notifier register */
>> +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
>> +                            struct v4l2_subdev *sd,
>> +                            struct v4l2_async_connection *asc)
>> +{
>> +     struct c3_isp_device *isp =
>> +             container_of(notifier, struct c3_isp_device, notifier);
>> +     struct c3_isp_core *core = &isp->core;
>> +     struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
>> +     int ret;
>> +
>> +     ret = media_entity_get_fwnode_pad(&sd->entity,
>> +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
>> +     if (ret < 0) {
>> +             dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
>> +             return ret;
>> +     }
>> +
>> +     core->src_sd = sd;
>> +     core->src_sd_pad = ret;
>> +
>> +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
>> +                                            MEDIA_LNK_FL_IMMUTABLE);
>> +}
>> +
>> +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
>> +{
>> +     struct c3_isp_device *isp =
>> +             container_of(notifier, struct c3_isp_device, notifier);
>> +     int ret;
>> +
>> +     ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
>> +     if (ret < 0) {
>> +             dev_err(isp->dev,
>> +                     "Failed to register subdev nodes: %d\n", ret);
>> +             return ret;
>> +     }
>> +
>> +     dev_info(isp->dev, "notify complete\n");
>> +
>> +     return media_device_register(&isp->media_dev);
>> +}
>> +
>> +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
>> +{
>> +     struct c3_isp_device *isp =
>> +             container_of(asc->notifier, struct c3_isp_device, notifier);
>> +
>> +     media_device_unregister(&isp->media_dev);
>> +}
>> +
>> +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
>> +     .bound = c3_isp_notify_bound,
>> +     .complete = c3_isp_notify_complete,
>> +     .destroy = c3_isp_notify_destroy,
>> +};
>> +
>> +static int c3_isp_async_nf_register(struct c3_isp_device *isp)
>> +{
>> +     struct v4l2_async_connection *asc;
>> +     struct fwnode_handle *ep;
>> +     int ret;
>> +
>> +     v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
>> +
>> +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
>> +                                          FWNODE_GRAPH_ENDPOINT_NEXT);
>> +     if (!ep)
>> +             return -ENOTCONN;
>> +
>> +     asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
>> +                                           struct v4l2_async_connection);
>> +     if (IS_ERR(asc)) {
>> +             fwnode_handle_put(ep);
>> +             return PTR_ERR(asc);
>> +     }
>> +
>> +     fwnode_handle_put(ep);
>> +
>> +     isp->notifier.ops = &c3_isp_notify_ops;
>> +     ret = v4l2_async_nf_register(&isp->notifier);
>> +     if (ret)
>> +             v4l2_async_nf_cleanup(&isp->notifier);
>> +
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
>> +{
>> +     v4l2_async_nf_unregister(&isp->notifier);
>> +     v4l2_async_nf_cleanup(&isp->notifier);
>> +}
>> +
>> +static int c3_isp_v4l2_register(struct c3_isp_device *isp)
>> +{
>> +     struct media_device *media_dev = &isp->media_dev;
>> +     struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
>> +     int ret;
>> +
>> +     /* Initialize media device */
>> +     strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
>> +             sizeof(media_dev->model));
>> +     media_dev->dev = isp->dev;
>> +
>> +     media_device_init(media_dev);
>> +
>> +     /* Initialize v4l2 device */
>> +     v4l2_dev->mdev = media_dev;
>> +     strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
>> +             sizeof(v4l2_dev->name));
>> +
>> +     ret = v4l2_device_register(isp->dev, v4l2_dev);
>> +     if (ret) {
>> +             media_device_cleanup(media_dev);
>> +             dev_err(isp->dev,
>> +                     "Failed to register V4L2 device: %d\n", ret);
>> +     }
>> +
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
>> +{
>> +     v4l2_device_unregister(&isp->v4l2_dev);
>> +     media_device_cleanup(&isp->media_dev);
>> +}
>> +
>> +static void c3_isp_remove_links(struct c3_isp_device *isp)
>> +{
>> +     unsigned int i;
>> +
>> +     media_entity_remove_links(&isp->core.sd.entity);
>> +
>> +     for (i = 0; i < C3_ISP_NUM_RSZ; i++)
>> +             media_entity_remove_links(&isp->resizers[i].sd.entity);
>> +
>> +     for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
>> +             media_entity_remove_links(&isp->caps[i].vdev.entity);
>> +}
>> +
>> +static int c3_isp_create_links(struct c3_isp_device *isp)
>> +{
>> +     unsigned int i;
>> +     int ret;
>> +
>> +     for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
>> +             ret = media_create_pad_link(&isp->resizers[i].sd.entity,
>> +                                         C3_ISP_RESIZER_PAD_SOURCE,
>> +                                         &isp->resizers[i].cap->vdev.entity,
>> +                                         0, MEDIA_LNK_FL_ENABLED);
> This could be made IMMUTABLE
>

OK,  will add IMMUTABLE flag.

>> +             if (ret) {
>> +                     dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
>> +                     goto err_remove_links;
>> +             }
>> +
>> +             ret = media_create_pad_link(&isp->core.sd.entity,
>> +                                         C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> +                                         &isp->resizers[i].sd.entity,
>> +                                         C3_ISP_RESIZER_PAD_SINK,
>> +                                         MEDIA_LNK_FL_ENABLED);
>> +             if (ret) {
>> +                     dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
>> +                     goto err_remove_links;
>> +             }
>> +     }
>> +
>> +     ret = media_create_pad_link(&isp->core.sd.entity,
>> +                                 C3_ISP_CORE_PAD_SOURCE_STATS,
>> +                                 &isp->stats.vdev.entity,
>> +                                 0, MEDIA_LNK_FL_ENABLED);
>> +     if (ret) {
>> +             dev_err(isp->dev, "Failed to link core and stats\n");
>> +             goto err_remove_links;
>> +     }
>> +
>> +     ret = media_create_pad_link(&isp->params.vdev.entity, 0,
>> +                                 &isp->core.sd.entity,
>> +                                 C3_ISP_CORE_PAD_SINK_PARAMS,
>> +                                 MEDIA_LNK_FL_ENABLED);
>> +     if (ret) {
>> +             dev_err(isp->dev, "Failed to link params and core\n");
>> +             goto err_remove_links;
>> +     }
>> +
>> +     return 0;
>> +
>> +err_remove_links:
>> +     c3_isp_remove_links(isp);
>> +     return ret;
>> +}
>> +
>> +static int c3_isp_videos_register(struct c3_isp_device *isp)
>> +{
>> +     int ret;
>> +
>> +     ret = c3_isp_captures_register(isp);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = c3_isp_stats_register(isp);
>> +     if (ret)
>> +             goto err_captures_unregister;
>> +
>> +     ret = c3_isp_params_register(isp);
>> +     if (ret)
>> +             goto err_stats_unregister;
>> +
>> +     ret = c3_isp_create_links(isp);
>> +     if (ret)
>> +             goto err_params_unregister;
>> +
>> +     return 0;
>> +
>> +err_params_unregister:
>> +     c3_isp_params_unregister(isp);
>> +err_stats_unregister:
>> +     c3_isp_stats_unregister(isp);
>> +err_captures_unregister:
>> +     c3_isp_captures_unregister(isp);
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_videos_unregister(struct c3_isp_device *isp)
>> +{
>> +     c3_isp_remove_links(isp);
>> +     c3_isp_params_unregister(isp);
>> +     c3_isp_stats_unregister(isp);
>> +     c3_isp_captures_unregister(isp);
>> +}
>> +
>> +static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
>> +{
>> +     const struct c3_isp_info *info = isp->info;
>> +     int ret;
>> +     u32 i;
>> +
>> +     for (i = 0; i < info->clock_num; i++)
>> +             isp->clks[i].id = info->clocks[i];
>> +
>> +     ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
>> +     if (ret)
>> +             return ret;
>> +
>> +     for (i = 0; i < info->clock_num; i++) {
>> +             if (!info->clock_rates[i])
>> +                     continue;
>> +             ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);
>> +             if (ret) {
>> +                     dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
>> +                             info->clock_rates[i]);
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_probe(struct platform_device *pdev)
>> +{
>> +     struct device *dev = &pdev->dev;
>> +     struct c3_isp_device *isp;
>> +     int irq;
>> +     int ret;
>> +
>> +     isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
>> +     if (!isp)
>> +             return -ENOMEM;
>> +
>> +     isp->info = of_device_get_match_data(dev);
>> +     isp->dev = dev;
>> +
>> +     isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
>> +     if (IS_ERR(isp->base))
>> +             return dev_err_probe(dev, PTR_ERR(isp->base),
>> +                                  "Failed to ioremap resource\n");
>> +
>> +     irq = platform_get_irq(pdev, 0);
>> +     if (irq < 0)
>> +             return irq;
>> +
>> +     ret = c3_isp_cfg_clocks(isp);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
>> +
>> +     platform_set_drvdata(pdev, isp);
>> +
>> +     pm_runtime_enable(dev);
>> +
>> +     ret = c3_isp_v4l2_register(isp);
>> +     if (ret)
>> +             goto err_runtime_disable;
>> +
>> +     ret = c3_isp_core_register(isp);
>> +     if (ret)
>> +             goto err_v4l2_unregister;
>> +
>> +     ret = c3_isp_resizers_register(isp);
>> +     if (ret)
>> +             goto err_core_unregister;
>> +
>> +     ret = c3_isp_async_nf_register(isp);
>> +     if (ret)
>> +             goto err_resizers_unregister;
>> +
>> +     ret = c3_isp_videos_register(isp);
>> +     if (ret)
>> +             goto err_nf_unregister;
>> +
>> +     ret = devm_request_irq(dev, irq,
>> +                            c3_isp_irq_handler, IRQF_SHARED,
>> +                            dev_driver_string(dev), isp);
>> +     if (ret)
>> +             goto err_streams_unregister;
> I would request the IRQ before registering devices to userspace.


Will move IRQ to the front of registering devices.

>> +
>> +     mutex_init(&isp->lock);
>> +
>> +     return 0;
>> +
>> +err_streams_unregister:
>> +     c3_isp_videos_unregister(isp);
>> +err_nf_unregister:
>> +     c3_isp_async_nf_unregister(isp);
>> +err_resizers_unregister:
>> +     c3_isp_resizers_unregister(isp);
>> +err_core_unregister:
>> +     c3_isp_core_unregister(isp);
>> +err_v4l2_unregister:
>> +     c3_isp_v4l2_unregister(isp);
>> +err_runtime_disable:
>> +     pm_runtime_disable(dev);
>> +     return ret;
>> +};
>> +
>> +static void c3_isp_remove(struct platform_device *pdev)
>> +{
>> +     struct c3_isp_device *isp = platform_get_drvdata(pdev);
>> +
>> +     mutex_destroy(&isp->lock);
>> +     c3_isp_videos_unregister(isp);
>> +     c3_isp_async_nf_unregister(isp);
>> +     c3_isp_core_unregister(isp);
>> +     c3_isp_resizers_unregister(isp);
>> +     c3_isp_v4l2_unregister(isp);
>> +     pm_runtime_disable(isp->dev);
>> +};
>> +
>> +static const struct c3_isp_info isp_info = {
>> +     .clocks = {"vapb", "isp0"},
>> +     .clock_rates = {0, 400000000},
>> +     .clock_num = 2
>> +};
>> +
>> +static const struct of_device_id c3_isp_of_match[] = {
>> +     { .compatible = "amlogic,c3-isp",
>> +       .data = &isp_info },
>> +     { },
>> +};
>> +MODULE_DEVICE_TABLE(of, c3_isp_of_match);
>> +
>> +static struct platform_driver c3_isp_driver = {
>> +     .probe = c3_isp_probe,
>> +     .remove = c3_isp_remove,
>> +     .driver = {
>> +             .name = "c3-isp",
>> +             .of_match_table = c3_isp_of_match,
>> +             .pm = &c3_isp_pm_ops,
>> +     },
>> +};
>> +
>> +module_platform_driver(c3_isp_driver);
>> +
>> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
>> +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
>> new file mode 100644
>> index 000000000000..8a6b7ce86eaf
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
>> @@ -0,0 +1,857 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-event.h>
> Do you need these ?
>

Will remove these two lines.

>> +#include <media/v4l2-ioctl.h>
>> +#include <media/v4l2-mc.h>
>> +#include <media/videobuf2-dma-contig.h>
> See below about using the dma-contig vb2 ops


Will use vmalloc ops.

>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +#include "include/uapi/c3-isp-config.h"
>> +
>> +typedef void (*block_handler)(struct c3_isp_device *isp,
>> +                           struct c3_isp_param_block_header *block);
>> +
>> +struct c3_isp_block_handler {
>> +     size_t size;
>> +     block_handler handler;
>> +};
>> +
>> +/* Hardware configuration */
>> +
>> +static void c3_isp_params_cfg_wb_change(struct c3_isp_device *isp,
>> +                                     struct c3_isp_param_block_header *block)
>> +{
>> +     struct wb_change_cfg *wb = (struct wb_change_cfg *)block;
>> +
>> +     if (!block->enabled) {
>> +             c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
>> +                                TOP_BEO_CTRL_WB_EN, false);
>> +             return;
>> +     }
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
>> +                        TOP_BEO_CTRL_WB_EN, TOP_BEO_CTRL_WB_EN);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, LSWB_WB_GAIN0_MASK,
>> +                        wb->wb_gain[0] << LSWB_WB_GAIN0_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0,
>> +                        LSWB_WB_GAIN1_MASK, wb->wb_gain[1]);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, LSWB_WB_GAIN2_MASK,
>> +                        wb->wb_gain[2] << LSWB_WB_GAIN2_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1,
>> +                        LSWB_WB_GAIN3_MASK, wb->wb_gain[3]);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN2,
>> +                        LSWB_WB_GAIN4_MASK, wb->wb_gain[4]);
>> +
>> +     c3_isp_write(isp, ISP_LSWB_WB_LIMIT0,
>> +                  LSWB_WB_LIMIT0(wb->wb_limit[0]) | LSWB_WB_LIMIT1(wb->wb_limit[1]));
>> +
>> +     c3_isp_write(isp, ISP_LSWB_WB_LIMIT1,
>> +                  LSWB_WB_LIMIT2(wb->wb_limit[2]) | LSWB_WB_LIMIT3(wb->wb_limit[3]));
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_LIMIT2, LSWB_WB_LIMIT4_MASK,
>> +                        wb->wb_limit[4]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_BL12_GRBGI0_MASK,
>> +                        wb->ae_bl12_grbgi[0]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_GAIN_GRBGI0_MASK,
>> +                        wb->ae_gain_grbgi[0] << AE_CRTL2_GAIN_GRBGI0_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_BL12_GRBGI1_MASK,
>> +                        wb->ae_bl12_grbgi[1]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_GAIN_GRBGI1_MASK,
>> +                        wb->ae_gain_grbgi[1] << AE_CRTL2_GAIN_GRBGI1_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_BL12_GRBGI2_MASK,
>> +                        wb->ae_bl12_grbgi[2]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_GAIN_GRBGI2_MASK,
>> +                        wb->ae_gain_grbgi[2] << AE_CRTL2_GAIN_GRBGI2_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_BL12_GRBGI3_MASK,
>> +                        wb->ae_bl12_grbgi[3]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_GAIN_GRBGI3_MASK,
>> +                        wb->ae_gain_grbgi[3] << AE_CRTL2_GAIN_GRBGI3_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_BL12_GRBGI4_MASK,
>> +                        wb->ae_bl12_grbgi[4]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_GAIN_GRBGI4_MASK,
>> +                        wb->ae_gain_grbgi[4] << AE_CRTL2_GAIN_GRBGI4_SHIFT);
>> +}
>> +
>> +static void c3_isp_params_cfg_wb_luma(struct c3_isp_device *isp,
>> +                                   struct c3_isp_param_block_header *block)
>> +{
>> +     struct wb_luma_cfg *wb_luma = (struct wb_luma_cfg *)block;
>> +
>> +     if (!block->enabled)
>> +             return;
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_0,
>> +                        AWB_STAT_BLC20_GR_MASK, wb_luma->awb_stat_blc20[0]);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_1,
>> +                        AWB_STAT_BLC20_R_MASK, wb_luma->awb_stat_blc20[1]);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_2,
>> +                        AWB_STAT_BLC20_B_MASK, wb_luma->awb_stat_blc20[2]);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_3,
>> +                        AWB_STAT_BLC20_GB_MASK, wb_luma->awb_stat_blc20[3]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_0,
>> +                        AWB_STAT_GAIN10_GR_MASK, wb_luma->awb_stat_gain10[0]);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_1,
>> +                        AWB_STAT_GAIN10_R_MASK, wb_luma->awb_stat_gain10[1]);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_2,
>> +                        AWB_STAT_GAIN10_B_MASK, wb_luma->awb_stat_gain10[2]);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_3,
>> +                        AWB_STAT_GAIN10_GB_MASK, wb_luma->awb_stat_gain10[3]);
>> +
>> +     c3_isp_write(isp, ISP_AWB_STAT_SATUR_CTRL,
>> +                  AWB_STAT_SATUR_LOW(wb_luma->awb_stat_satur_low) |
>> +                  AWB_STAT_SATUR_HIGH(wb_luma->awb_stat_satur_high));
>> +}
>> +
>> +static void c3_isp_params_cfg_wb_triangle(struct c3_isp_device *isp,
>> +                                       struct c3_isp_param_block_header *block)
>> +{
>> +     struct wb_triangle_cfg *wb = (struct wb_triangle_cfg *)block;
>> +
>> +     if (!block->enabled)
>> +             return;
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_CTRL2, AWB_STAT_SATUR_VALID_MASK,
>> +                        wb->awb_stat_satur_vald);
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MIN_MASK,
>> +                        wb->awb_stat_rg_min);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MAX_MASK,
>> +                        wb->awb_stat_rg_max << AWB_STAT_RG_MAX_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MIN_MASK,
>> +                        wb->awb_stat_bg_min);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MAX_MASK,
>> +                        wb->awb_stat_bg_max << AWB_STAT_BG_MAX_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_LOW_MASK,
>> +                        wb->awb_stat_rg_low);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_HIGH_MASK,
>> +                        wb->awb_stat_rg_high << AWB_STAT_RG_HIGH_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_LOW_MASK,
>> +                        wb->awb_stat_bg_low);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_HIGH_MASK,
>> +                        wb->awb_stat_bg_high << AWB_STAT_BG_HIGH_SHIFT);
>> +}
>> +
>> +static void c3_isp_params_cfg_awb_stats(struct c3_isp_device *isp,
>> +                                     struct c3_isp_param_block_header *block)
>> +{
>> +     struct awb_stats_cfg *awb_stats = (struct awb_stats_cfg *)block;
>> +     u32 *weight = awb_stats->awb_stat_blk_weight;
>> +     int idx_base;
>> +     int group;
>> +     int i;
>> +
>> +     if (!block->enabled)
>> +             return;
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AWB_STAT_SWITCH_MASK,
>> +                        awb_stats->awb_stat_switch << TOP_3A_AWB_STAT_SWITCH_SHIFT);
>> +
>> +     /* Calculate the group number */
>> +     group = AWB_BLOCK_WT_NUM / AWB_BLK_WT_DATA_NUM_OF_GROUP;
>> +
>> +     /* Set the weight address to 0 position */
>> +     c3_isp_write(isp, ISP_AWB_BLK_WT_ADDR, 0);
>> +     for (i = 0; i < group; i++) {
> you can now use
>          for (unsigned int i = 0; ...)
>
> if 'i' is not needed outside of the loop


Will use unsigned int i in for loop.

>> +             idx_base = i * AWB_BLK_WT_DATA_NUM_OF_GROUP;
>> +             c3_isp_write(isp, ISP_AWB_BLK_WT_DATA,
>> +                          AWB_BLK_WT_DATA0(weight[idx_base + 0]) |
>> +                          AWB_BLK_WT_DATA1(weight[idx_base + 1]) |
>> +                          AWB_BLK_WT_DATA2(weight[idx_base + 2]) |
>> +                          AWB_BLK_WT_DATA3(weight[idx_base + 3]) |
>> +                          AWB_BLK_WT_DATA4(weight[idx_base + 4]) |
>> +                          AWB_BLK_WT_DATA5(weight[idx_base + 5]) |
>> +                          AWB_BLK_WT_DATA6(weight[idx_base + 6]) |
>> +                          AWB_BLK_WT_DATA7(weight[idx_base + 7]));
>> +     }
>> +}
>> +
>> +static void c3_isp_params_cfg_ae_stats(struct c3_isp_device *isp,
>> +                                    struct c3_isp_param_block_header *block)
>> +{
>> +     struct ae_stats_cfg *ae_stats = (struct ae_stats_cfg *)block;
>> +     u32 *weight = ae_stats->ae_stat_blk_weight;
>> +     int idx_base;
>> +     int group;
>> +     int i;
>> +
>> +     if (!block->enabled)
>> +             return;
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AE_STAT_SWITCH_MASK,
>> +                        ae_stats->ae_stat_switch << TOP_3A_AE_STAT_SWITCH_SHIFT);
>> +
>> +     /* Calculate the group number */
>> +     group = AE_BLOCK_WT_NUM / AE_BLK_WT_DATA_NUM_OF_GROUP;
>> +
>> +     /* Set the weight address to 0 position */
>> +     c3_isp_write(isp, ISP_AE_BLK_WT_ADDR, 0);
>> +     for (i = 0; i < group; i++) {
>> +             idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
>> +             c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
>> +                          AE_BLK_WT_DATA0(weight[idx_base + 0]) |
>> +                          AE_BLK_WT_DATA1(weight[idx_base + 1]) |
>> +                          AE_BLK_WT_DATA2(weight[idx_base + 2]) |
>> +                          AE_BLK_WT_DATA3(weight[idx_base + 3]) |
>> +                          AE_BLK_WT_DATA4(weight[idx_base + 4]) |
>> +                          AE_BLK_WT_DATA5(weight[idx_base + 5]) |
>> +                          AE_BLK_WT_DATA6(weight[idx_base + 6]) |
>> +                          AE_BLK_WT_DATA7(weight[idx_base + 7]));
>> +     }
>> +
>> +     /* Write the last weight data */
>> +     idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
>> +     c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
>> +                  AE_BLK_WT_DATA0(weight[idx_base + 0]) |
>> +                  AE_BLK_WT_DATA1(weight[idx_base + 1]) |
>> +                  AE_BLK_WT_DATA2(weight[idx_base + 2]) |
>> +                  AE_BLK_WT_DATA3(weight[idx_base + 3]) |
>> +                  AE_BLK_WT_DATA4(weight[idx_base + 4]) |
>> +                  AE_BLK_WT_DATA5(weight[idx_base + 5]) |
>> +                  AE_BLK_WT_DATA6(weight[idx_base + 6]));
>> +}
>> +
>> +static void c3_isp_params_cfg_af_stats(struct c3_isp_device *isp,
>> +                                    struct c3_isp_param_block_header *block)
>> +{
>> +     struct af_stats_cfg *af_stats = (struct af_stats_cfg *)block;
>> +
>> +     if (!block->enabled)
>> +             return;
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AF_STAT_SWITCH_MASK,
>> +                        af_stats->af_stat_switch << TOP_3A_AF_STAT_SWITCH_SHIFT);
>> +}
>> +
>> +static void c3_isp_params_cfg_pst_gamma(struct c3_isp_device *isp,
>> +                                     struct c3_isp_param_block_header *block)
>> +{
>> +     struct pst_gamma_cfg *gamma = (struct pst_gamma_cfg *)block;
>> +     int idx_base;
>> +     int i, j;
>> +
>> +     if (!block->enabled) {
>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, false);
>> +             return;
>> +     }
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, TOP_BED_GAMMA_EN);
>> +
>> +     for (j = 0; j < GAMMA_LUT_GROUP_NUM; j++) {
>> +             c3_isp_write(isp, ISP_PST_GAMMA_LUT_ADDR, PST_GAMMA_LUT_ADDR(j));
>> +
>> +             /* Calculate the block number */
>> +             for (i = 0; i < GAMMA_LUT_POINT_NUM / GAMMA_LUT_DATA_NUM_OF_GROUP; i++) {
>> +                     idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
>> +                     c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>> +                                  PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]) |
>> +                                  PST_GAMMA_LUT_DATA1(gamma->pst_gamma_lut[j][idx_base + 1]));
>> +             }
>> +
>> +             /* Write the last one lut data of group j */
>> +             idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
>> +             c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>> +                          PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]));
>> +     }
>> +}
>> +
>> +static void c3_isp_params_cfg_dmsc(struct c3_isp_device *isp,
>> +                                struct c3_isp_param_block_header *block)
>> +{
>> +     if (!block->enabled) {
>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, false);
>> +             return;
>> +     }
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, TOP_BED_DMSC_EN);
>> +}
>> +
>> +/* Configure 4 x 3 ccm matrix */
>> +static void c3_isp_params_cfg_ccm(struct c3_isp_device *isp,
>> +                               struct c3_isp_param_block_header *block)
>> +{
>> +     struct ccm_cfg *ccm = (struct ccm_cfg *)block;
>> +
>> +     if (!block->enabled) {
>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, false);
>> +             return;
>> +     }
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, TOP_BED_CCM_EN);
>> +
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_00_MASK,
>> +                        ccm->ccm_4x3matrix[0][0]);
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_01_MASK,
>> +                        ccm->ccm_4x3matrix[0][1] << CCM_MTX_01_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_02_MASK,
>> +                        ccm->ccm_4x3matrix[0][2]);
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_03_MASK,
>> +                        ccm->ccm_4x3matrix[0][3] << CCM_MTX_03_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_10_MASK,
>> +                        ccm->ccm_4x3matrix[1][0]);
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_11_MASK,
>> +                        ccm->ccm_4x3matrix[1][1] << CCM_MTX_11_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_12_MASK,
>> +                        ccm->ccm_4x3matrix[1][2]);
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_13_MASK,
>> +                        ccm->ccm_4x3matrix[1][3] << CCM_MTX_13_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_20_MASK,
>> +                        ccm->ccm_4x3matrix[2][0]);
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_21_MASK,
>> +                        ccm->ccm_4x3matrix[2][1] << CCM_MTX_21_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_22_MASK,
>> +                        ccm->ccm_4x3matrix[2][2]);
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_23_MASK,
>> +                        ccm->ccm_4x3matrix[2][3] << CCM_MTX_23_SHIFT);
>> +}
>> +
>> +/* Configure color space conversion matrix parameters */
>> +static void c3_isp_params_cfg_csc(struct c3_isp_device *isp,
>> +                               struct c3_isp_param_block_header *block)
>> +{
>> +     struct csc_cfg *csc = (struct csc_cfg *)block;
>> +
>> +     if (!block->enabled) {
>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, false);
>> +             return;
>> +     }
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, TOP_BED_CM0_EN);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST0_MASK,
>> +                        csc->cm0_offset_inp[0]);
>> +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST1_MASK,
>> +                        csc->cm0_offset_inp[1] << CM0_INP_OFST1_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST2, CM0_INP_OFST2_MASK,
>> +                        csc->cm0_offset_inp[2]);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_00_MASK,
>> +                        csc->cm0_3x3matrix[0][0]);
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_01_MASK,
>> +                        csc->cm0_3x3matrix[0][1] << CM0_MTX_01_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_02_MASK,
>> +                        csc->cm0_3x3matrix[0][2]);
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_10_MASK,
>> +                        csc->cm0_3x3matrix[1][0] << CM0_MTX_10_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_11_MASK,
>> +                        csc->cm0_3x3matrix[1][1]);
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_12_MASK,
>> +                        csc->cm0_3x3matrix[1][2] << CM0_MTX_12_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_20_MASK,
>> +                        csc->cm0_3x3matrix[2][0]);
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_21_MASK,
>> +                        csc->cm0_3x3matrix[2][1] << CM0_MTX_21_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_MTX_22_MASK,
>> +                        csc->cm0_3x3matrix[2][2]);
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_OFST_OUP0_MASK,
>> +                        csc->cm0_offset_oup[0] << CM0_OFST_OUP0_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP1_MASK,
>> +                        csc->cm0_offset_oup[1]);
>> +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP2_MASK,
>> +                        csc->cm0_offset_oup[2] << CM0_OFST_OUP2_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_MTX_RS_MASK,
>> +                        csc->cm0_3x3mtrx_rs << CM0_MTX_RS_SHIFT);
>> +}
>> +
>> +/* Set blc offset of each color channel */
>> +static void c3_isp_params_cfg_blc(struct c3_isp_device *isp,
>> +                               struct c3_isp_param_block_header *block)
>> +{
>> +     struct blc_cfg *blc = (struct blc_cfg *)block;
>> +
>> +     if (!block->enabled) {
>> +             c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN, false);
>> +             return;
>> +     }
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN,
>> +                        TOP_BEO_CTRL_BLC_EN);
>> +
>> +     c3_isp_write(isp, ISP_FED_BL_OFST_GR, FED_BL_GR_OFST(blc->fe_bl_ofst[0]));
>> +     c3_isp_write(isp, ISP_FED_BL_OFST_R, FED_BL_R_OFST(blc->fe_bl_ofst[1]));
>> +     c3_isp_write(isp, ISP_FED_BL_OFST_B, FED_BL_B_OFST(blc->fe_bl_ofst[2]));
>> +     c3_isp_write(isp, ISP_FED_BL_OFST_GB, FED_BL_GB_OFST(blc->fe_bl_ofst[3]));
>> +     c3_isp_write(isp, ISP_FED_BL_OFST_IR, FED_BL_IR_OFST(blc->fe_bl_ofst[4]));
>> +
>> +     c3_isp_write(isp, ISP_LSWB_BLC_OFST0,
>> +                  LSWB_BLC_OFST0(blc->blc_ofst[0]) | LSWB_BLC_OFST1(blc->blc_ofst[1]));
>> +     c3_isp_write(isp, ISP_LSWB_BLC_OFST1,
>> +                  LSWB_BLC_OFST2(blc->blc_ofst[2]) | LSWB_BLC_OFST3(blc->blc_ofst[3]));
>> +     c3_isp_write(isp, ISP_LSWB_BLC_OFST2, LSWB_BLC_OFST4(blc->blc_ofst[4]));
>> +}
>> +
>> +static const struct c3_isp_block_handler c3_isp_block_handlers[] = {
>> +     [C3_ISP_PARAM_BLOCK_WB_CHANGE] = {
>> +             .size = sizeof(struct wb_change_cfg),
>> +             .handler = c3_isp_params_cfg_wb_change,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_WB_LUMA] = {
>> +             .size = sizeof(struct wb_luma_cfg),
>> +             .handler = c3_isp_params_cfg_wb_luma,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_WB_TRIANGLE] = {
>> +             .size = sizeof(struct wb_triangle_cfg),
>> +             .handler = c3_isp_params_cfg_wb_triangle,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_AWB_STATS] = {
>> +             .size = sizeof(struct awb_stats_cfg),
>> +             .handler = c3_isp_params_cfg_awb_stats,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_AE_STATS] = {
>> +             .size = sizeof(struct ae_stats_cfg),
>> +             .handler = c3_isp_params_cfg_ae_stats,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_AF_STATS] = {
>> +             .size = sizeof(struct af_stats_cfg),
>> +             .handler = c3_isp_params_cfg_af_stats,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_PST_GAMMA] = {
>> +             .size = sizeof(struct pst_gamma_cfg),
>> +             .handler = c3_isp_params_cfg_pst_gamma,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_DMSC] = {
>> +             .size = sizeof(struct dmsc_cfg),
>> +             .handler = c3_isp_params_cfg_dmsc,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_CCM] = {
>> +             .size = sizeof(struct ccm_cfg),
>> +             .handler = c3_isp_params_cfg_ccm,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_CSC] = {
>> +             .size = sizeof(struct csc_cfg),
>> +             .handler = c3_isp_params_cfg_csc,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_BLC] = {
>> +             .size = sizeof(struct blc_cfg),
>> +             .handler = c3_isp_params_cfg_blc,
>> +     },
>> +};
>> +
>> +static enum vb2_buffer_state
>> +c3_isp_params_cfg_blocks(struct c3_isp_params *params)
>> +{
>> +     struct c3_isp_params_buffer *config = params->buff->vaddr;
>> +     enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
>> +     size_t block_offset = 0;
>> +     size_t max_offset = 0;
>> +
>> +     if (config->total_size > C3_ISP_PARAMS_MAX_SIZE) {
>> +             dev_dbg(params->isp->dev, "Invalid parameters buffer size %lu\n",
>> +                     config->total_size);
>> +             state = VB2_BUF_STATE_ERROR;
>> +             goto err_return_state;
>> +     }
> I suggest to move validation of the parameters buffer to .buf_prepare
> time.
>
> This function is called in irq context, and it's better to do all
> validation check at buffer queuing time instead.
>
> You can have a look at the rkisp1-params.c module, where
> rkisp1_params_prepare_ext_params does the validation in the
> .buf_prepare call path
>

Will refer the rkisp1-params.c module.

>> +
>> +     /* Ensure config->data has a full struct c3_isp_param_block_header */
>> +     max_offset = config->total_size - sizeof(struct c3_isp_param_block_header);
>> +
>> +     while (block_offset <= max_offset) {
>> +             const struct c3_isp_block_handler *block_handler;
>> +             struct c3_isp_param_block_header *block;
>> +
>> +             block = (struct c3_isp_param_block_header *)
>> +                      &config->data[block_offset];
>> +
>> +             if (block->type >= C3_ISP_PARAM_BLOCK_SENTINEL) {
>> +                     dev_dbg(params->isp->dev, "Invalid parameters block type\n");
>> +                     state = VB2_BUF_STATE_ERROR;
>> +                     goto err_return_state;
>> +             }
>> +
>> +             block_handler = &c3_isp_block_handlers[block->type];
>> +             if (block->size != block_handler->size) {
>> +                     dev_dbg(params->isp->dev, "Invalid parameters block size\n");
>> +                     state = VB2_BUF_STATE_ERROR;
>> +                     goto err_return_state;
>> +             }
>> +
>> +             block_handler->handler(params->isp, block);
>> +
>> +             block_offset += block->size;
>> +     }
>> +
>> +err_return_state:
>> +     return state;
>> +}
>> +
>> +/* Initialize ISP pipeline */
>> +static int c3_isp_params_start(struct c3_isp_params *params)
>> +{
>> +     enum vb2_buffer_state state;
>> +     unsigned long flags;
>> +
>> +     /* Reset these controllers */
>> +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL0, TOP_FEO_CTRL0_ALL_DIS);
>> +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_0, TOP_FEO_CTRL1_0_ALL_DIS);
>> +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_1, TOP_FEO_CTRL1_1_ALL_DIS);
>> +     c3_isp_write(params->isp, ISP_TOP_FED_CTRL, TOP_FED_CTRL_ALL_DIS);
>> +     c3_isp_write(params->isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_ALL_DIS);
>> +     c3_isp_write(params->isp, ISP_TOP_BED_CTRL, TOP_BED_CTRL_ALL_DIS);
>> +
>> +     spin_lock_irqsave(&params->buff_lock, flags);
>> +
>> +     /* Only use the first buffer to initialize ISP */
>> +     params->buff = list_first_entry_or_null(&params->pending,
>> +                                             struct c3_isp_vb2_buffer, list);
>> +     if (!params->buff) {
>> +             spin_unlock_irqrestore(&params->buff_lock, flags);
>> +             return -EINVAL;
>> +     }
>> +
>> +     state = c3_isp_params_cfg_blocks(params);
>> +
>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>> +
>> +     return 0;
>> +}
>> +
>> +/* V4L2 video operations */
>> +
>> +static void c3_isp_params_return_buffers(struct c3_isp_params *params,
>> +                                      enum vb2_buffer_state state)
>> +{
>> +     unsigned long flags;
>> +     struct c3_isp_vb2_buffer *buff;
>> +
>> +     spin_lock_irqsave(&params->buff_lock, flags);
>> +
>> +     while (!list_empty(&params->pending)) {
>> +             buff = list_first_entry(&params->pending,
>> +                                     struct c3_isp_vb2_buffer, list);
>> +             list_del(&buff->list);
>> +             vb2_buffer_done(&buff->vb.vb2_buf, state);
>> +     }
>> +
>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>> +}
>> +
>> +static int c3_isp_params_querycap(struct file *file, void *fh,
>> +                               struct v4l2_capability *cap)
>> +{
>> +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>> +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_params_enum_fmt(struct file *file, void *fh,
>> +                               struct v4l2_fmtdesc *f)
>> +{
>> +     if (f->index)
>> +             return -EINVAL;
>> +
>> +     f->pixelformat = V4L2_META_FMT_C3ISP_PARAMS;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_params_g_fmt(struct file *file, void *fh,
>> +                            struct v4l2_format *f)
>> +{
>> +     struct c3_isp_params *params = video_drvdata(file);
>> +
>> +     f->fmt.meta = params->vfmt.fmt.meta;
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct v4l2_ioctl_ops isp_params_v4l2_ioctl_ops = {
>> +     .vidioc_querycap                = c3_isp_params_querycap,
>> +     .vidioc_enum_fmt_meta_out       = c3_isp_params_enum_fmt,
>> +     .vidioc_g_fmt_meta_out          = c3_isp_params_g_fmt,
>> +     .vidioc_s_fmt_meta_out          = c3_isp_params_g_fmt,
>> +     .vidioc_try_fmt_meta_out        = c3_isp_params_g_fmt,
>> +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>> +     .vidioc_querybuf                = vb2_ioctl_querybuf,
>> +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
>> +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
>> +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>> +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>> +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>> +     .vidioc_streamon                = vb2_ioctl_streamon,
>> +     .vidioc_streamoff               = vb2_ioctl_streamoff,
>> +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>> +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>> +};
>> +
>> +static const struct v4l2_file_operations isp_params_v4l2_fops = {
>> +     .open = v4l2_fh_open,
>> +     .release = vb2_fop_release,
>> +     .poll = vb2_fop_poll,
>> +     .unlocked_ioctl = video_ioctl2,
>> +     .mmap = vb2_fop_mmap,
>> +};
>> +
>> +static int c3_isp_params_vb2_queue_setup(struct vb2_queue *q,
>> +                                      unsigned int *num_buffers,
>> +                                      unsigned int *num_planes,
>> +                                      unsigned int sizes[],
>> +                                      struct device *alloc_devs[])
>> +{
>> +     if (*num_planes) {
>> +             if (*num_planes != 1)
>> +                     return -EINVAL;
>> +
>> +             if (sizes[0] < sizeof(struct c3_isp_params_buffer))
>> +                     return -EINVAL;
>> +     } else {
>> +             *num_planes = 1;
>> +             sizes[0] = sizeof(struct c3_isp_params_buffer);
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_isp_params_vb2_buf_queue(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_vb2_buffer *buf =
>> +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>> +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned long flags;
>> +
>> +     spin_lock_irqsave(&params->buff_lock, flags);
>> +
>> +     list_add_tail(&buf->list, &params->pending);
>> +
>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>> +}
>> +
>> +static int c3_isp_params_vb2_buf_prepare(struct vb2_buffer *vb)
>> +{
>> +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned int size = params->vfmt.fmt.meta.buffersize;
>> +
>> +     if (vb2_plane_size(vb, 0) < size) {
> How does this work ?
>
> 'size' is
>          params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
>
> if I got this right
>
> Now, as you're using an extensible parameters implementation,
> userspace is allowed to submit buffers of a smaller size, with only
> the "interesting" blocks there.
>
> This check instead makes sure that userspace always fill the
> paramteres buffer with all blocks, am I wrong ?
>
> This defeates the purpose of extensible formats, where instead
> userspace is allowed to only fill the buffers with a subset of the
> configuration blocks.
>
> Have I missed something ?


Didn't notice this issue and will check this issue.

>
>> +             dev_err(params->isp->dev,
>> +                     "User buffer too small (%ld < %u)\n",
>> +                     vb2_plane_size(vb, 0), size);
>> +             return -EINVAL;
>> +     }
>> +
>> +     vb2_set_plane_payload(vb, 0, size);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_params_vb2_buf_init(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_vb2_buffer *buf =
>> +             container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>> +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>> +
>> +     buf->vaddr = vb2_plane_vaddr(vb, 0);
>> +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
> This is not used
>

OK, will remove this line.

>> +
>> +     memset(buf->vaddr, 0, params->vfmt.fmt.meta.buffersize);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_params_vb2_start_streaming(struct vb2_queue *q,
>> +                                          unsigned int count)
>> +{
>> +     struct c3_isp_params *params = vb2_get_drv_priv(q);
>> +     int ret;
>> +
>> +     guard(mutex)(&params->isp->lock);
>> +
>> +     ret = pm_runtime_resume_and_get(params->isp->dev);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = video_device_pipeline_start(&params->vdev, &params->isp->pipe);
>> +     if (ret) {
>> +             dev_err(params->isp->dev,
>> +                     "Failed to start params pipeline: %d\n", ret);
>> +             goto err_pm_put;
>> +     }
>> +
>> +     if (c3_isp_pipeline_ready(params->isp)) {
> I understand this counts how many video devices have been started...
>
Yes.
>> +             ret = v4l2_subdev_enable_streams(&params->isp->core.sd,
>> +                                              C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> +                                              BIT(0));
> ... but does it need to be called by this module ? it's not like there is a
> parameters stream to be enabled in the ISP (see also the suggestion to
> drop routing support from there).
>
>

Will remove this function  from here.

>> +             if (ret)
>> +                     goto err_pipeline_stop;
>> +     }
>> +
>> +     c3_isp_params_start(params);
> Does this perform initialization and apply the first parameters from a
> queued buffer ?
>
> I'm wondering if it wouldn't be better to:
>
> - Implement a start_streaming operation on capture nodes only
> - Call the ISP's enable_streams
> - The ISP driver counts how many enabled links to resizers are there
> - If the number of enable_streams calls matches the number of enabled
>    links:
>    - call c3_isp_params_start() and any function that does the stats or
>      params setup
>    - actually start the ISP
>    - propagate the enable_streams call to the downstream subdevs (Adap,
>      then csi2-rx then sensor)
>

Will refer your suggestion.

>> +
>> +     return 0;
>> +
>> +err_pipeline_stop:
>> +     video_device_pipeline_stop(&params->vdev);
>> +err_pm_put:
>> +     pm_runtime_put(params->isp->dev);
>> +     c3_isp_params_return_buffers(params, VB2_BUF_STATE_QUEUED);
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_params_vb2_stop_streaming(struct vb2_queue *q)
>> +{
>> +     struct c3_isp_params *params = vb2_get_drv_priv(q);
>> +
>> +     guard(mutex)(&params->isp->lock);
>> +
>> +     c3_isp_params_return_buffers(params, VB2_BUF_STATE_ERROR);
> Even if you don't implement start_streaming as I've suggested, you
> will need this one to return buffers to userspace.
>

OK.

>> +
>> +     if (params->isp->pipe.start_count == 1)
>> +             v4l2_subdev_disable_streams(&params->isp->core.sd,
>> +                                         C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> +                                         BIT(0));
>> +
>> +     video_device_pipeline_stop(&params->vdev);
>> +     pm_runtime_put(params->isp->dev);
>> +}
>> +
>> +static const struct vb2_ops isp_params_vb2_ops = {
>> +     .queue_setup = c3_isp_params_vb2_queue_setup,
>> +     .buf_queue = c3_isp_params_vb2_buf_queue,
>> +     .buf_prepare = c3_isp_params_vb2_buf_prepare,
>> +     .buf_init = c3_isp_params_vb2_buf_init,
>> +     .wait_prepare = vb2_ops_wait_prepare,
>> +     .wait_finish = vb2_ops_wait_finish,
>> +     .start_streaming = c3_isp_params_vb2_start_streaming,
>> +     .stop_streaming = c3_isp_params_vb2_stop_streaming,
>> +};
>> +
>> +int c3_isp_params_register(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_params *params = &isp->params;
>> +     struct video_device *vdev = &params->vdev;
>> +     struct vb2_queue *vb2_q = &params->vb2_q;
>> +     int ret;
>> +
>> +     memset(params, 0, sizeof(*params));
>> +     params->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_PARAMS;
>> +     params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
>> +     params->isp = isp;
>> +     INIT_LIST_HEAD(&params->pending);
>> +     spin_lock_init(&params->buff_lock);
>> +     mutex_init(&params->lock);
>> +
>> +     snprintf(vdev->name, sizeof(vdev->name), "isp-params");
> Here and in all other names, I would prefix them with "c3-"
>
>> +     vdev->fops = &isp_params_v4l2_fops;
>> +     vdev->ioctl_ops = &isp_params_v4l2_ioctl_ops;
>> +     vdev->v4l2_dev = &isp->v4l2_dev;
>> +     vdev->lock = &params->lock;
>> +     vdev->minor = -1;
>> +     vdev->queue = vb2_q;
>> +     vdev->release = video_device_release_empty;
>> +     vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING;
>> +     vdev->vfl_dir = VFL_DIR_TX;
>> +     video_set_drvdata(vdev, params);
>> +
>> +     vb2_q->drv_priv = params;
>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
> Do you need to use DMA ?
>
> This implementation seems to rather receive a buffer of parameters,
> inspect its content and write registers according to values there.
>
> I don't see direct DMA transfers to a memory mapped register area, but
> instead single writes to registers.
>
> Should you use vb2_vmalloc_memops instead ?
> Make sure to change the headers inclusions and Kconfig dependencies
> accordingly.


Will use vb2_vmalloc_memops instead.

>> +     vb2_q->ops = &isp_params_vb2_ops;
>> +     vb2_q->type = V4L2_BUF_TYPE_META_OUTPUT;
>> +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>> +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>> +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>> +     vb2_q->dev = isp->dev;
>> +     vb2_q->lock = &params->lock;
>> +     vb2_q->min_queued_buffers = 1;
>> +
>> +     ret = vb2_queue_init(vb2_q);
>> +     if (ret)
>> +             goto err_detroy;
>> +
>> +     params->pad.flags = MEDIA_PAD_FL_SOURCE;
>> +     ret = media_entity_pads_init(&vdev->entity, 1, &params->pad);
>> +     if (ret)
>> +             goto err_queue_release;
>> +
>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>> +     if (ret < 0) {
>> +             dev_err(isp->dev,
>> +                     "Failed to register %s: %d\n", vdev->name, ret);
>> +             goto err_entity_cleanup;
>> +     }
>> +
>> +     return 0;
>> +
>> +err_entity_cleanup:
>> +     media_entity_cleanup(&vdev->entity);
>> +err_queue_release:
>> +     vb2_queue_release(vb2_q);
>> +err_detroy:
>> +     mutex_destroy(&params->lock);
>> +     return ret;
>> +}
>> +
>> +void c3_isp_params_unregister(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_params *params = &isp->params;
>> +
>> +     vb2_queue_release(&params->vb2_q);
>> +     media_entity_cleanup(&params->vdev.entity);
>> +     video_unregister_device(&params->vdev);
>> +     mutex_destroy(&params->lock);
>> +}
>> +
>> +int c3_isp_params_done(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_params *params = &isp->params;
>> +     enum vb2_buffer_state state;
>> +     unsigned long flags;
>> +
>> +     spin_lock_irqsave(&params->buff_lock, flags);
>> +
>> +     params->buff = list_first_entry_or_null(&params->pending,
>> +                                             struct c3_isp_vb2_buffer, list);
>> +     if (!params->buff) {
>> +             spin_unlock_irqrestore(&params->buff_lock, flags);
>> +             return -EINVAL;
>> +     }
>> +
>> +     list_del(&params->buff->list);
>> +
>> +     state = c3_isp_params_cfg_blocks(params);
>> +
>> +     params->buff->vb.sequence = params->isp->frm_sequence;
>> +     params->buff->vb.vb2_buf.timestamp = ktime_get();
>> +     params->buff->vb.field = V4L2_FIELD_NONE;
>> +     vb2_buffer_done(&params->buff->vb.vb2_buf, state);
>> +
>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>> +
>> +     return 0;
>> +}
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
>> new file mode 100644
>> index 000000000000..de1938f7c354
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
>> @@ -0,0 +1,683 @@
>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#ifndef __C3_ISP_REGS_H__
>> +#define __C3_ISP_REGS_H__
>> +
>> +#define ISP_TOP_INPUT_SIZE                       0x0000
>> +#define TOP_INPUT_HSIZE(x)                       (((x) & 0xffff) << 16)
>> +#define TOP_INPUT_VSIZE(x)                       ((x) & 0xffff)
>> +
>> +#define ISP_TOP_FRM_SIZE                         0x0004
>> +#define TOP_FRM_CORE_IHSIZE(x)                   (((x) & 0xffff) << 16)
>> +#define TOP_FRM_CORE_IVSIZE(x)                   ((x) & 0xffff)
>> +
>> +#define ISP_TOP_HOLD_SIZE                        0x0008
>> +#define TOP_HOLD_HSIZE_MASK                      GENMASK(31, 16)
>> +#define TOP_HOLD_HSIZE_SHIFT                     16
>> +
>> +#define ISP_TOP_PATH_EN                          0x0010
>> +#define TOP_DISP_EN(x)                           BIT((x))
>> +#define TOP_WRMIF_EN(x)                          BIT((x) + 8)
>> +
>> +#define ISP_TOP_PATH_SEL                         0x0014
>> +#define TOP_DATA_PATH_MASK                       GENMASK(18, 16)
>> +#define TOP_PATH_MIPI_TO_CORE                    BIT(16)
>> +
>> +#define ISP_TOP_IRQ_EN                           0x0080
>> +#define TOP_IRQ_FRAME_DONE                       BIT(0)
>> +#define TOP_IRQ_STATS_ERR                        BIT(5)
>> +
>> +#define ISP_TOP_IRQ_CLR                          0x0084
>> +#define ISP_TOP_IRQ_LINE_THRD                    0x0088
>> +#define TOP_IRQ_DIN_HSYNC                        BIT(16)
>> +
>> +#define ISP_TOP_RO_IRQ_STAT                      0x01c4
>> +#define ISP_TOP_MODE_CTRL                        0x0400
>> +#define ISP_TOP_FEO_CTRL0                        0x040c
>> +#define TOP_FEO_CTRL0_ALL_DIS                    0
>> +#define TOP_FEO_INPUT_FMT_EN                     BIT(8)
>> +
>> +#define ISP_TOP_FEO_CTRL1_0                      0x0410
>> +#define TOP_FEO_CTRL1_0_ALL_DIS                  0
>> +
>> +#define ISP_TOP_FEO_CTRL1_1                      0x0414
>> +#define TOP_FEO_CTRL1_1_ALL_DIS                  0
>> +
>> +#define ISP_TOP_FED_CTRL                         0x0418
>> +#define TOP_FED_CTRL_ALL_DIS                     0
>> +
>> +#define ISP_TOP_BEO_CTRL                         0x041c
>> +#define TOP_BEO_CTRL_ALL_DIS                     0
>> +#define TOP_BEO_CTRL_GTM_EN                      BIT(2)
>> +#define TOP_BEO_CTRL_WB_EN                       BIT(6)
>> +#define TOP_BEO_CTRL_BLC_EN                      BIT(7)
>> +#define TOP_BEO_CTRL_IDGAIN_EN                   BIT(8)
>> +#define TOP_BEO_CTRL_EOTF_EN                     BIT(9)
>> +
>> +#define ISP_TOP_BED_CTRL                         0x0420
>> +#define TOP_BED_CTRL_ALL_DIS                     0
>> +#define TOP_BED_CM0_EN                           BIT(14)
>> +#define TOP_BED_GAMMA_EN                         BIT(16)
>> +#define TOP_BED_CCM_EN                           BIT(18)
>> +#define TOP_BED_DMSC_EN                          BIT(19)
>> +
>> +#define ISP_TOP_3A_STAT_CRTL                     0x0424
>> +#define TOP_3A_AE_STAT_EN                        BIT(0)
>> +#define TOP_3A_AWB_STAT_EN                       BIT(1)
>> +#define TOP_3A_AF_STAT_EN                        BIT(2)
>> +#define TOP_3A_AWB_STAT_SWITCH_MASK              GENMASK(6, 4)
>> +#define TOP_3A_AWB_STAT_SWITCH_SHIFT             4
>> +#define TOP_3A_AWB_STAT_SWITCH_BEFORE_WB         2
>> +
>> +#define TOP_3A_AE_STAT_SWITCH_MASK               GENMASK(9, 8)
>> +#define TOP_3A_AE_STAT_SWITCH_SHIFT              8
>> +#define TOP_3A_AE_STAT_SWITCH_FROM_MLS           1
>> +#define TOP_3A_AF_STAT_SWITCH_MASK               GENMASK(13, 12)
>> +#define TOP_3A_AF_STAT_SWITCH_SHIFT              12
>> +#define TOP_3A_AF_STAT_SWITCH_FROM_SNR            0
>> +
>> +#define ISP_FED_BL_OFST_GR                       0x2018
>> +#define FED_BL_GR_OFST(x)                        ((x) & 0x1fffff)
>> +
>> +#define ISP_FED_BL_OFST_R                        0x201c
>> +#define FED_BL_R_OFST(x)                         ((x) & 0x1fffff)
>> +
>> +#define ISP_FED_BL_OFST_B                        0x2020
>> +#define FED_BL_B_OFST(x)                         ((x) & 0x1fffff)
>> +
>> +#define ISP_FED_BL_OFST_GB                       0x2024
>> +#define FED_BL_GB_OFST(x)                        ((x) & 0x1fffff)
>> +
>> +#define ISP_FED_BL_OFST_IR                       0x2028
>> +#define FED_BL_IR_OFST(x)                        ((x) & 0x1fffff)
>> +
>> +#define ISP_LSWB_BLC_OFST0                       0x4028
>> +#define LSWB_BLC_OFST1_MASK                      GENMASK(15, 0)
>> +#define LSWB_BLC_OFST1(x)                        ((x) & 0xffff)
>> +#define LSWB_BLC_OFST1_EXT(x)                    ((x) & 0xffff)
>> +#define LSWB_BLC_OFST0_MASK                      GENMASK(31, 16)
>> +#define LSWB_BLC_OFST0(x)                        (((x) & 0xffff) << 16)
>> +#define LSWB_BLC_OFST0_EXT(x)                    (((x) >> 16) & 0xffff)
>> +
>> +#define ISP_LSWB_BLC_OFST1                       0x402c
>> +#define LSWB_BLC_OFST3_MASK                      GENMASK(15, 0)
>> +#define LSWB_BLC_OFST3(x)                        ((x) & 0xffff)
>> +#define LSWB_BLC_OFST3_EXT(x)                    ((x) & 0xffff)
>> +#define LSWB_BLC_OFST2_MASK                      GENMASK(31, 16)
>> +#define LSWB_BLC_OFST2(x)                        (((x) & 0xffff) << 16)
>> +#define LSWB_BLC_OFST2_EXT(x)                    (((x) >> 16) & 0xffff)
>> +
>> +#define ISP_LSWB_BLC_OFST2                       0x4030
>> +#define LSWB_BLC_OFST4_MASK                      GENMASK(15, 0)
>> +#define LSWB_BLC_OFST4(x)                        ((x) & 0xffff)
>> +#define LSWB_BLC_OFST4_EXT(x)                    ((x) & 0xffff)
>> +
>> +#define ISP_LSWB_BLC_PHSOFST                     0x4034
>> +#define LSWB_BLC_YPHS_OFST_MASK                  GENMASK(1, 0)
>> +#define LSWB_BLC_XPHS_OFST_MASK                  GENMASK(3, 2)
>> +#define LSWB_BLC_XPHS_OFST_SHIFT                 2
>> +
>> +#define ISP_LSWB_WB_GAIN0                        0x4038
>> +#define LSWB_WB_GAIN1_MASK                       GENMASK(11, 0)
>> +#define LSWB_WB_GAIN0_MASK                       GENMASK(27, 16)
>> +#define LSWB_WB_GAIN0_SHIFT                      16
>> +
>> +#define ISP_LSWB_WB_GAIN1                        0x403c
>> +#define LSWB_WB_GAIN3_MASK                       GENMASK(11, 0)
>> +#define LSWB_WB_GAIN2_MASK                       GENMASK(27, 16)
>> +#define LSWB_WB_GAIN2_SHIFT                      16
>> +
>> +#define ISP_LSWB_WB_GAIN2                        0x4040
>> +#define LSWB_WB_GAIN4_MASK                       GENMASK(11, 0)
>> +
>> +#define ISP_LSWB_WB_LIMIT0                       0x4044
>> +#define LSWB_WB_LIMIT1(x)                        ((x) & 0xffff)
>> +#define LSWB_WB_LIMIT0(x)                        (((x) & 0xffff) << 16)
>> +
>> +#define ISP_LSWB_WB_LIMIT1                       0x4048
>> +#define LSWB_WB_LIMIT3(x)                        ((x) & 0xffff)
>> +#define LSWB_WB_LIMIT2(x)                        (((x) & 0xffff) << 16)
>> +
>> +#define ISP_LSWB_WB_LIMIT2                       0x404c
>> +#define LSWB_WB_LIMIT4_MASK                      GENMASK(15, 0)
>> +
>> +#define ISP_LSWB_WB_PHSOFST                      0x4050
>> +#define LSWB_WB_YPHS_OFST_MASK                   GENMASK(1, 0)
>> +#define LSWB_WB_XPHS_OFST_MASK                   GENMASK(3, 2)
>> +#define LSWB_WB_XPHS_OFST_SHIFT                  2
>> +
>> +#define ISP_LSWB_LNS_PHSOFST                     0x4054
>> +#define LSWB_LNS_YPHS_OFST_MASK                  GENMASK(1, 0)
>> +#define LSWB_LNS_XPHS_OFST_MASK                  GENMASK(3, 2)
>> +#define LSWB_LNS_XPHS_OFST_SHIFT                 2
>> +
>> +#define ISP_DMS_COMMON_PARAM0                    0x5000
>> +#define DMS_COMMON_YPHS_OFST_MASK                GENMASK(1, 0)
>> +#define DMS_COMMON_XPHS_OFST_MASK                GENMASK(3, 2)
>> +#define DMS_COMMON_XPHS_OFST_SHIFT               2
>> +
>> +#define ISP_CM0_INP_OFST01                       0x6040
>> +#define CM0_INP_OFST0_MASK                       GENMASK(12, 0)
>> +#define CM0_INP_OFST1_MASK                       GENMASK(28, 16)
>> +#define CM0_INP_OFST1_SHIFT                      16
>> +
>> +#define ISP_CM0_INP_OFST2                        0x6044
>> +#define CM0_INP_OFST2_MASK                       GENMASK(12, 0)
>> +
>> +#define ISP_CM0_COEF00_01                        0x6048
>> +#define CM0_MTX_00_MASK                          GENMASK(12, 0)
>> +#define CM0_MTX_01_MASK                          GENMASK(28, 16)
>> +#define CM0_MTX_01_SHIFT                         16
>> +
>> +#define ISP_CM0_COEF02_10                        0x604c
>> +#define CM0_MTX_02_MASK                          GENMASK(12, 0)
>> +#define CM0_MTX_10_MASK                          GENMASK(28, 16)
>> +#define CM0_MTX_10_SHIFT                         16
>> +
>> +#define ISP_CM0_COEF11_12                        0x6050
>> +#define CM0_MTX_11_MASK                          GENMASK(12, 0)
>> +#define CM0_MTX_12_MASK                          GENMASK(28, 16)
>> +#define CM0_MTX_12_SHIFT                         16
>> +
>> +#define ISP_CM0_COEF20_21                        0x6054
>> +#define CM0_MTX_20_MASK                          GENMASK(12, 0)
>> +#define CM0_MTX_21_MASK                          GENMASK(28, 16)
>> +#define CM0_MTX_21_SHIFT                         16
>> +
>> +#define ISP_CM0_COEF22_OUP_OFST0                 0x6058
>> +#define CM0_MTX_22_MASK                          GENMASK(12, 0)
>> +#define CM0_OFST_OUP0_MASK                       GENMASK(28, 16)
>> +#define CM0_OFST_OUP0_SHIFT                      16
>> +
>> +#define ISP_CM0_OUP_OFST12_RS                    0x605c
>> +#define CM0_OFST_OUP1_MASK                       GENMASK(12, 0)
>> +#define CM0_OFST_OUP2_MASK                       GENMASK(28, 16)
>> +#define CM0_OFST_OUP2_SHIFT                      16
>> +#define CM0_MTX_RS_MASK                          GENMASK(31, 30)
>> +#define CM0_MTX_RS_SHIFT                         30
>> +
>> +#define ISP_CCM_MTX_00_01                        0x6098
>> +#define CCM_MTX_00_MASK                          GENMASK(12, 0)
>> +#define CCM_MTX_01_MASK                          GENMASK(28, 16)
>> +#define CCM_MTX_01_SHIFT                         16
>> +
>> +#define ISP_CCM_MTX_02_03                        0x609c
>> +#define CCM_MTX_02_MASK                          GENMASK(12, 0)
>> +#define CCM_MTX_03_MASK                          GENMASK(28, 16)
>> +#define CCM_MTX_03_SHIFT                         16
>> +
>> +#define ISP_CCM_MTX_10_11                        0x60A0
>> +#define CCM_MTX_10_MASK                          GENMASK(12, 0)
>> +#define CCM_MTX_11_MASK                          GENMASK(28, 16)
>> +#define CCM_MTX_11_SHIFT                         16
>> +
>> +#define ISP_CCM_MTX_12_13                        0x60A4
>> +#define CCM_MTX_12_MASK                          GENMASK(12, 0)
>> +#define CCM_MTX_13_MASK                          GENMASK(28, 16)
>> +#define CCM_MTX_13_SHIFT                         16
>> +
>> +#define ISP_CCM_MTX_20_21                        0x60A8
>> +#define CCM_MTX_20_MASK                          GENMASK(12, 0)
>> +#define CCM_MTX_21_MASK                          GENMASK(28, 16)
>> +#define CCM_MTX_21_SHIFT                         16
>> +
>> +#define ISP_CCM_MTX_22_23_RS                     0x60Ac
>> +#define CCM_MTX_22_MASK                          GENMASK(12, 0)
>> +#define CCM_MTX_23_MASK                          GENMASK(28, 16)
>> +#define CCM_MTX_23_SHIFT                         16
>> +
>> +#define ISP_PST_GAMMA_MODE                       0x60C0
>> +#define PST_GAMMA_MODE                           BIT(0)
>> +#define PST_GAMMA_MODE_EXT(x)                    ((x) & 0x1)
>> +
>> +#define ISP_PST_GAMMA_LUT_ADDR                   0x60cc
>> +#define PST_GAMMA_LUT_ADDR(x)                    ((x) << 7)
>> +
>> +#define ISP_PST_GAMMA_LUT_DATA                   0x60d0
>> +#define PST_GAMMA_LUT_DATA0(x)                   ((x) & 0xffff)
>> +#define PST_GAMMA_LUT_DATA1(x)                   (((x) & 0xffff) << 16)
>> +
>> +#define DISP0_TOP_TOP_CTRL                       0x8000
>> +#define DISP_CRP2_EN                             BIT(5)
>> +
>> +#define DISP0_TOP_CRP2_START                     0x8004
>> +#define DISP_CRP2_VSTART_MASK                    GENMASK(15, 0)
>> +#define DISP_CRP2_VSTART(x)                      ((x) & 0xffff)
>> +#define DISP_CRP2_HSTART_MASK                    GENMASK(31, 16)
>> +#define DISP_CRP2_HSTART(x)                      (((x) & 0xffff) << 16)
>> +
>> +#define DISP0_TOP_CRP2_SIZE                      0x8008
>> +#define DISP_CRP2_VSIZE_MASK                     GENMASK(15, 0)
>> +#define DISP_CRP2_VSIZE(x)                       ((x) & 0xffff)
>> +#define DISP_CRP2_HSIZE_MASK                     GENMASK(31, 16)
>> +#define DISP_CRP2_HSIZE(x)                       (((x) & 0xffff) << 16)
>> +
>> +#define DISP0_TOP_OUT_SIZE                       0x800c
>> +#define DISP_OUT_VSIZE_MASK                      GENMASK(12, 0)
>> +#define DISP_OUT_HSIZE_MASK                      GENMASK(28, 16)
>> +#define DISP_OUT_HSIZE_SHIFT                     16
>> +
>> +#define ISP_DISP0_TOP_IN_SIZE                    0x804c
>> +#define DISP_TOP_IN_VSIZE(x)                     ((x) & 0x1fff)
>> +#define DISP_TOP_IN_HSIZE(x)                     (((x) & 0x1fff) << 16)
>> +
>> +#define DISP0_PPS_SCALE_EN                       0x8200
>> +#define PPS_VSC_TAP_NUM_MASK                     GENMASK(3, 0)
>> +#define PPS_HSC_TAP_NUM_MASK                     GENMASK(7, 4)
>> +#define PPS_HSC_TAP_NUM_SHIFT                    4
>> +#define PPS_HSC_TAP_NUM_INIT                     4
>> +#define PPS_PREVSC_FLT_NUM_MASK                  GENMASK(11, 8)
>> +#define PPS_PREVSC_FLT_NUM_SHIFT                 8
>> +#define PPS_PREHSC_FLT_NUM_MASK                  GENMASK(15, 12)
>> +#define PPS_PREHSC_FLT_NUM_SHIFT                 12
>> +#define PPS_PREHSC_FLT_NUM_INIT                  8
>> +#define PPS_PREVSC_RATE_MASK                     GENMASK(17, 16)
>> +#define PPS_PREVSC_RATE_SHIFT                    16
>> +#define PPS_PREHSC_RATE_MASK                     GENMASK(19, 18)
>> +#define PPS_PREHSC_RATE_SHIFT                    18
>> +#define PPS_HSC_EN_MASK                          BIT(20)
>> +#define PPS_HSC_EN_SHIFT                         20
>> +#define PPS_VSC_EN_MASK                          BIT(21)
>> +#define PPS_VSC_EN_SHIFT                         21
>> +#define PPS_PREVSC_EN_MASK                       BIT(22)
>> +#define PPS_PREVSC_EN_SHIFT                      22
>> +#define PPS_PREHSC_EN_MASK                       BIT(23)
>> +#define PPS_PREHSC_EN_SHIFT                      23
>> +#define PPS_HSC_NOR_RS_BITS_MASK                 GENMASK(27, 24)
>> +#define PPS_HSC_NOR_RS_BITS_SHIFT                24
>> +#define PPS_HSC_NOR_RS_BITS_INIT                 9
>> +#define PPS_VSC_NOR_RS_BITS_MASK                 GENMASK(31, 28)
>> +#define PPS_VSC_NOR_RS_BITS_SHIFT                28
>> +#define PPS_VSC_NOR_RS_BITS_INIT                 9
>> +
>> +#define DISP0_PPS_PRE_HSCALE_COEF_0              0x8204
>> +#define PPS_PREHSC_LUMA_COEF0_MASK               GENMASK(9, 0)
>> +#define PPS_PREHSC_LUMA_COEF0_INIT               128
>> +#define PPS_PREHSC_LUMA_COEF1_MASK               GENMASK(25, 16)
>> +#define PPS_PREHSC_LUMA_COEF1_SHIFT              16
>> +#define PPS_PREHSC_LUMA_COEF1_INIT               128
>> +
>> +#define DISP0_PPS_PRE_HSCALE_COEF_1              0x8208
>> +#define PPS_PREHSC_LUMA_COEF2_MASK               GENMASK(9, 0)
>> +#define PPS_PREHSC_LUMA_COEF2_INIT               32
>> +#define PPS_PREHSC_LUMA_COEF3_MASK               GENMASK(25, 16)
>> +#define PPS_PREHSC_LUMA_COEF3_SHIFT              16
>> +#define PPS_PREHSC_LUMA_COEF3_INIT               32
>> +
>> +#define DISP0_PPS_VSC_START_PHASE_STEP           0x8224
>> +#define PPS_VSC_FRACTION_PART_MASK               GENMASK(23, 0)
>> +#define PPS_VSC_INTEGER_PART_MASK                GENMASK(27, 24)
>> +#define PPS_VSC_INTEGER_PART_SHIFT               24
>> +
>> +#define DISP0_PPS_HSC_START_PHASE_STEP           0x8230
>> +#define PPS_HSC_FRACTION_PART_MASK               GENMASK(23, 0)
>> +#define PPS_HSC_INTEGER_PART_MASK                GENMASK(27, 24)
>> +#define PPS_HSC_INTEGER_PART_SHIFT               24
>> +
>> +#define DISP0_PPS_444TO422                       0x823c
>> +#define PPS_444TO422_EN_MASK                     BIT(0)
>> +
>> +#define ISP_SCALE0_COEF_IDX_LUMA                 0x8240
>> +#define SCALE_LUMA_COEF_S11_MODE                 BIT(9)
>> +#define SCALE_LUMA_CTYPE(x)                      (((x) & 0x7) << 10)
>> +
>> +#define ISP_SCALE0_COEF_LUMA                     0x8244
>> +#define SCALE_COEF_LUMA_DATA1(x)                 ((x) & 0x7ff)
>> +#define SCALE_COEF_LUMA_DATA0(x)                 (((x) & 0x7ff) << 16)
>> +
>> +#define ISP_SCALE0_COEF_IDX_CHRO                 0x8248
>> +#define SCALE_CHRO_COEF_S11_MODE                 BIT(9)
>> +#define SCALE_CHRO_CTYPE(x)                      (((x) & 0x7) << 10)
>> +
>> +#define ISP_SCALE0_COEF_CHRO                     0x824c
>> +#define SCALE_COEF_CHRO_DATA1(x)                 ((x) & 0x7ff)
>> +#define SCALE_COEF_CHRO_DATA0(x)                 (((x) & 0x7ff) << 16)
>> +
>> +#define ISP_AF_ROI0_WIN01                        0xa00c
>> +#define AF_ROI_XYWIN_00_INIT                     (50 & 0xffff)
>> +#define AF_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
>> +
>> +#define ISP_AF_ROI1_WIN01                        0xa010
>> +#define AF_ROI_XYWIN_10_INIT                     (100 & 0xffff)
>> +#define AF_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
>> +
>> +#define ISP_AF_ROI0_WIN23                        0xa014
>> +#define AF_ROI_XYWIN_02_INIT                     (80 & 0xffff)
>> +#define AF_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
>> +
>> +#define ISP_AF_ROI1_WIN23                        0xa018
>> +#define AF_ROI_XYWIN_12_INIT                     (80 & 0xffff)
>> +#define AF_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
>> +
>> +#define ISP_AF_CTRL                              0xa044
>> +#define AF_CTRL_YPHS_OFST_MASK                   GENMASK(15, 14)
>> +#define AF_CTRL_YPHS_OFST_SHIFT                  14
>> +#define AF_CTRL_XPHS_OFST_MASK                   GENMASK(17, 16)
>> +#define AF_CTRL_XPHS_OFST_SHIFT                  16
>> +
>> +#define ISP_AF_HV_SIZE                           0xa04c
>> +#define AF_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
>> +#define AF_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AF_HV_BLKNUM                         0xa050
>> +#define AF_HV_STAT_VBLK_NUM_MASK                 GENMASK(5, 0)
>> +#define AF_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x3f)
>> +#define AF_HV_STAT_HBLK_NUM_MASK                 GENMASK(21, 16)
>> +#define AF_HV_STAT_HBLK_NUM_SHIFT                16
>> +
>> +#define ISP_AF_EN_CTRL                           0xa054
>> +#define AF_STAT_SELECT                           BIT(21)
>> +#define AF_STAT_SELECT_SHIFT                     21
>> +
>> +#define ISP_RO_AF_GLB_STAT_PCK0                  0xa0e0
>> +#define ISP_RO_AF_GLB_STAT_PCK1                  0xa0e4
>> +#define ISP_RO_AF_GLB_STAT_PCK2                  0xa0e8
>> +#define ISP_RO_AF_GLB_STAT_PCK3                  0xa0eC
>> +#define ISP_AF_IDX_ADDR                          0xa1c0
>> +#define ISP_AF_IDX_DATA                          0xa1c4
>> +#define AF_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
>> +#define AF_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AE_ROI0_WIN01                        0xa40c
>> +#define AE_ROI_XYWIN_00_INIT                     (50 & 0xffff)
>> +#define AE_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
>> +
>> +#define ISP_AE_ROI1_WIN01                        0xa410
>> +#define AE_ROI_XYWIN_10_INIT                     (100 & 0xffff)
>> +#define AE_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
>> +
>> +#define ISP_AE_ROI0_WIN23                        0xa414
>> +#define AE_ROI_XYWIN_02_INIT                     (80 & 0xffff)
>> +#define AE_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
>> +
>> +#define ISP_AE_ROI1_WIN23                        0xa418
>> +#define AE_ROI_XYWIN_12_INIT                     (80 & 0xffff)
>> +#define AE_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
>> +
>> +#define ISP_RO_AE_ROI_STAT_PCK0_0                0xa424
>> +#define ISP_RO_AE_ROI_STAT_PCK1_0                0xa428
>> +#define ISP_RO_AE_ROI_STAT_PCK0_1                0xa42c
>> +#define ISP_RO_AE_ROI_STAT_PCK1_1                0xa430
>> +#define ISP_AE_CTRL                              0xa448
>> +#define AE_CTRL_AE_STAT_LOCAL_MODE               GENMASK(3, 2)
>> +#define AE_CTRL_AE_STAT_LOCAL_MODE_EXT(x)        (((x) >> 2) & 0x3)
>> +#define AE_CTRL_INPUT_2LINE_TOGETHER             BIT(7)
>> +#define AE_CTRL_LUMA_MODE_MASK                   GENMASK(9, 8)
>> +#define AE_CTRL_LUMA_MODE_SHIFT                  8
>> +#define AE_CTRL_LUMA_MODE_FILTER                 2
>> +#define AE_CTRL_YPHS_OFST_MASK                   GENMASK(25, 24)
>> +#define AE_CTRL_YPHS_OFST_SHIFT                  24
>> +#define AE_CTRL_XPHS_OFST_MASK                   GENMASK(27, 26)
>> +#define AE_CTRL_XPHS_OFST_SHIFT                  26
>> +
>> +#define ISP_AE_CRTL2_0                           0xa44c
>> +#define AE_CRTL2_BL12_GRBGI0_MASK                GENMASK(11, 0)
>> +#define AE_CRTL2_GAIN_GRBGI0_MASK                GENMASK(23, 16)
>> +#define AE_CRTL2_GAIN_GRBGI0_SHIFT               16
>> +
>> +#define ISP_AE_CRTL2_1                           0xa450
>> +#define AE_CRTL2_BL12_GRBGI1_MASK                GENMASK(11, 0)
>> +#define AE_CRTL2_GAIN_GRBGI1_MASK                GENMASK(23, 16)
>> +#define AE_CRTL2_GAIN_GRBGI1_SHIFT               16
>> +
>> +#define ISP_AE_CRTL2_2                           0xa454
>> +#define AE_CRTL2_BL12_GRBGI2_MASK                GENMASK(11, 0)
>> +#define AE_CRTL2_GAIN_GRBGI2_MASK                GENMASK(23, 16)
>> +#define AE_CRTL2_GAIN_GRBGI2_SHIFT               16
>> +
>> +#define ISP_AE_CRTL2_3                           0xa458
>> +#define AE_CRTL2_BL12_GRBGI3_MASK                GENMASK(11, 0)
>> +#define AE_CRTL2_GAIN_GRBGI3_MASK                GENMASK(23, 16)
>> +#define AE_CRTL2_GAIN_GRBGI3_SHIFT               16
>> +
>> +#define ISP_AE_CRTL2_4                           0xa45C
>> +#define AE_CRTL2_BL12_GRBGI4_MASK                GENMASK(11, 0)
>> +#define AE_CRTL2_GAIN_GRBGI4_MASK                GENMASK(23, 16)
>> +#define AE_CRTL2_GAIN_GRBGI4_SHIFT               16
>> +
>> +#define ISP_AE_HV_SIZE                           0xa464
>> +#define AE_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
>> +#define AE_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AE_HV_BLKNUM                         0xa468
>> +#define AE_HV_STAT_VBLK_NUM_MASK                 GENMASK(6, 0)
>> +#define AE_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x7f)
>> +#define AE_HV_STAT_HBLK_NUM_MASK                 GENMASK(22, 16)
>> +#define AE_HV_STAT_HBLK_NUM_SHIFT                16
>> +
>> +#define ISP_AE_STAT_THD01                        0xa46c
>> +#define AE_STAT_THRD0_INIT                       (13107 & 0xffff)
>> +#define AE_STAT_THRD1_INIT                       ((26214 & 0xffff) << 16)
>> +
>> +#define ISP_AE_STAT_THD23                        0xa470
>> +#define AE_STAT_THRD2_INIT                       (39321 & 0xffff)
>> +#define AE_STAT_THRD3_INIT                       ((52428 & 0xffff) << 16)
>> +
>> +#define ISP_RO_AE_STAT_GPNUM                     0xa49c
>> +#define ISP_AE_IDX_ADDR                          0xa600
>> +#define ISP_AE_IDX_DATA                          0xa604
>> +#define AE_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
>> +#define AE_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AE_BLK_WT_ADDR                       0xa608
>> +#define ISP_AE_BLK_WT_DATA                       0xa60c
>> +#define AE_BLK_WT_DATA0(x)                       ((x) & 0xf)
>> +#define AE_BLK_WT_DATA1(x)                       (((x) & 0xf) << 4)
>> +#define AE_BLK_WT_DATA2(x)                       (((x) & 0xf) << 8)
>> +#define AE_BLK_WT_DATA3(x)                       (((x) & 0xf) << 12)
>> +#define AE_BLK_WT_DATA4(x)                       (((x) & 0xf) << 16)
>> +#define AE_BLK_WT_DATA5(x)                       (((x) & 0xf) << 20)
>> +#define AE_BLK_WT_DATA6(x)                       (((x) & 0xf) << 24)
>> +#define AE_BLK_WT_DATA7(x)                       (((x) & 0xf) << 28)
>> +
>> +#define ISP_AWB_CTRL                             0xa834
>> +#define AWB_CTRL_YPHS_OFST_MASK                  GENMASK(1, 0)
>> +#define AWB_CTRL_XPHS_OFST_MASK                  GENMASK(3, 2)
>> +#define AWB_CTRL_XPHS_OFST_SHIFT                 2
>> +#define AWB_CTRL_STAT_RATIO_MODE                 BIT(4)
>> +
>> +#define ISP_AWB_HV_SIZE                          0xa83c
>> +#define AWB_HV_STAT_VSIZE(x)                     ((x) & 0xffff)
>> +#define AWB_HV_STAT_HSIZE(x)                     (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AWB_STAT_ROI0_WIN01                  0xa80c
>> +#define AWB_ROI_XYWIN_00_INIT                    (32 & 0xffff)
>> +#define AWB_ROI_XYWIN_01_INIT                    ((62 & 0xffff) << 16)
>> +
>> +#define ISP_AWB_STAT_ROI1_WIN01                  0xa810
>> +#define AWB_ROI_XYWIN_10_INIT                    (32 & 0xffff)
>> +#define AWB_ROI_XYWIN_11_INIT                    ((82 & 0xffff) << 16)
>> +
>> +#define ISP_AWB_STAT_ROI0_WIN23                  0xa814
>> +#define AWB_ROI_XYWIN_02_INIT                    (32 & 0xffff)
>> +#define AWB_ROI_XYWIN_03_INIT                    ((42 & 0xffff) << 16)
>> +
>> +#define ISP_AWB_STAT_ROI1_WIN23                  0xa818
>> +#define AWB_ROI_XYWIN_12_INIT                    (64 & 0xffff)
>> +#define AWB_ROI_XYWIN_13_INIT                    ((52 & 0xffff) << 16)
>> +
>> +#define ISP_AWB_HV_BLKNUM                        0xa840
>> +#define AWB_HV_STAT_VBLK_NUM_MASK                GENMASK(5, 0)
>> +#define AWB_HV_STAT_VBLK_NUM_EXT(x)              ((x) & 0x3f)
>> +#define AWB_HV_STAT_HBLK_NUM_MASK                GENMASK(21, 16)
>> +#define AWB_HV_STAT_HBLK_NUM_SHIFT               16
>> +
>> +#define ISP_AWB_STAT_RG                          0xa848
>> +#define AWB_STAT_RG_MIN_MASK                     GENMASK(11, 0)
>> +#define AWB_STAT_RG_MAX_MASK                     GENMASK(27, 16)
>> +#define AWB_STAT_RG_MAX_SHIFT                    16
>> +
>> +#define ISP_AWB_STAT_BG                          0xa84c
>> +#define AWB_STAT_BG_MIN_MASK                     GENMASK(11, 0)
>> +#define AWB_STAT_BG_MAX_MASK                     GENMASK(27, 16)
>> +#define AWB_STAT_BG_MAX_SHIFT                    16
>> +
>> +#define ISP_AWB_STAT_RG_HL                       0xa850
>> +#define AWB_STAT_RG_LOW_MASK                     GENMASK(11, 0)
>> +#define AWB_STAT_RG_HIGH_MASK                    GENMASK(27, 16)
>> +#define AWB_STAT_RG_HIGH_SHIFT                   16
>> +
>> +#define ISP_AWB_STAT_BG_HL                       0xa854
>> +#define AWB_STAT_BG_LOW_MASK                     GENMASK(11, 0)
>> +#define AWB_STAT_BG_HIGH_MASK                    GENMASK(27, 16)
>> +#define AWB_STAT_BG_HIGH_SHIFT                   16
>> +
>> +#define ISP_AWB_STAT_CTRL2                       0xa858
>> +#define AWB_STAT_SATUR_VALID_MASK                BIT(0)
>> +#define AWB_STAT_LOCAL_MODE                      BIT(2)
>> +#define AWB_STAT_LOCAL_MODE_EXT(x)               (((x) >> 2) & 0x1)
>> +#define AWB_STAT_LUMA_DIV_MODE                   GENMASK(4, 3)
>> +#define AWB_STAT_LUMA_DIV_MODE_EXT(x)            (((x) >> 3) & 0x3)
>> +
>> +#define ISP_AWB_STAT_BLC20_0                     0xa85c
>> +#define AWB_STAT_BLC20_GR_MASK                   GENMASK(19, 0)
>> +
>> +#define ISP_AWB_STAT_BLC20_1                     0xa860
>> +#define AWB_STAT_BLC20_R_MASK                    GENMASK(19, 0)
>> +
>> +#define ISP_AWB_STAT_BLC20_2                     0xa864
>> +#define AWB_STAT_BLC20_B_MASK                    GENMASK(19, 0)
>> +
>> +#define ISP_AWB_STAT_BLC20_3                     0xa868
>> +#define AWB_STAT_BLC20_GB_MASK                   GENMASK(19, 0)
>> +
>> +#define ISP_AWB_STAT_GAIN10_0                    0xa86c
>> +#define AWB_STAT_GAIN10_GR_MASK                  GENMASK(9, 0)
>> +
>> +#define ISP_AWB_STAT_GAIN10_1                    0xa870
>> +#define AWB_STAT_GAIN10_R_MASK                   GENMASK(9, 0)
>> +
>> +#define ISP_AWB_STAT_GAIN10_2                    0xa874
>> +#define AWB_STAT_GAIN10_B_MASK                   GENMASK(9, 0)
>> +
>> +#define ISP_AWB_STAT_GAIN10_3                    0xa878
>> +#define AWB_STAT_GAIN10_GB_MASK                  GENMASK(9, 0)
>> +
>> +#define ISP_AWB_STAT_SATUR_CTRL                  0xa884
>> +#define AWB_STAT_SATUR_LOW(x)                    ((x) & 0xffff)
>> +#define AWB_STAT_SATUR_HIGH(x)                   (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AWB_IDX_ADDR                         0xaa00
>> +#define ISP_AWB_IDX_DATA                         0xaa04
>> +#define AWB_IDX_VIDX_DATA(x)                     ((x) & 0xffff)
>> +#define AWB_IDX_HIDX_DATA(x)                     (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AWB_BLK_WT_ADDR                      0xaa08
>> +#define ISP_AWB_BLK_WT_DATA                      0xaa0c
>> +#define AWB_BLK_WT_DATA0(x)                      ((x) & 0xf)
>> +#define AWB_BLK_WT_DATA1(x)                      (((x) & 0xf) << 4)
>> +#define AWB_BLK_WT_DATA2(x)                      (((x) & 0xf) << 8)
>> +#define AWB_BLK_WT_DATA3(x)                      (((x) & 0xf) << 12)
>> +#define AWB_BLK_WT_DATA4(x)                      (((x) & 0xf) << 16)
>> +#define AWB_BLK_WT_DATA5(x)                      (((x) & 0xf) << 20)
>> +#define AWB_BLK_WT_DATA6(x)                      (((x) & 0xf) << 24)
>> +#define AWB_BLK_WT_DATA7(x)                      (((x) & 0xf) << 28)
>> +
>> +#define ISP_WRMIFX3_0_CH0_CTRL0                  0xc400
>> +#define WRMIFX3_CH0_STRIDE_MASK                  GENMASK(28, 16)
>> +#define WRMIFX3_CH0_STRIDE_SHIFT                 16
>> +
>> +#define ISP_WRMIFX3_0_CH0_CTRL1                  0xc404
>> +#define WRMIFX3_CH0_PIX_BITS_MODE_MASK           GENMASK(30, 27)
>> +#define WRMIFX3_CH0_PIX_BITS_MODE_SHIFT          27
>> +#define WRMIFX3_CH0_PIX_BITS_8BITS               1
>> +#define WRMIFX3_CH0_PIX_BITS_16BITS              2
>> +
>> +#define ISP_WRMIFX3_0_CH1_CTRL0                  0xc408
>> +#define WRMIFX3_CH1_STRIDE_MASK                  GENMASK(28, 16)
>> +#define WRMIFX3_CH1_STRIDE_SHIFT                 16
>> +
>> +#define ISP_WRMIFX3_0_CH1_CTRL1                  0xc40c
>> +#define WRMIFX3_CH1_PIX_BITS_MODE_MASK           GENMASK(30, 27)
>> +#define WRMIFX3_CH1_PIX_BITS_MODE_SHIFT          27
>> +#define WRMIFX3_CH1_PIX_BITS_16BITS              2
>> +#define WRMIFX3_CH1_PIX_BITS_32BITS              3
>> +
>> +#define ISP_WRMIFX3_0_CH0_BADDR                  0xc440
>> +/* WRMIF base address need 16 bits alignment */
>> +#define WRMIFX3_CH0_BADDR(x)                     (((x) >> 4) & 0xffffffff)
>> +
>> +#define ISP_WRMIFX3_0_CH1_BADDR                  0xc444
>> +#define WRMIFX3_CH1_BADDR(x)                     (((x) >> 4) & 0xffffffff)
>> +
>> +#define ISP_WRMIFX3_0_FMT_SIZE                   0xc464
>> +#define WRMIFX3_FMT_HSIZE(x)                     ((x) & 0xffff)
>> +#define WRMIFX3_FMT_VSIZE(x)                     (((x) & 0xffff) << 16)
>> +
>> +#define ISP_WRMIFX3_0_FMT_CTRL                   0xc468
>> +#define WRMIFX3_FMT_MTX_IBITS_MASK               GENMASK(1, 0)
>> +#define WRMIFX3_FMT_MTX_IBITS_8BIT               0
>> +#define WRMIFX3_FMT_MTX_IBITS_10BIT              1
>> +#define WRMIFX3_FMT_MTX_IBITS_12BIT              2
>> +#define WRMIFX3_FMT_MTX_IBITS_16BIT              3
>> +#define WRMIFX3_FMT_MTX_UV_SWAP_MASK             BIT(2)
>> +#define WRMIFX3_FMT_MTX_UV_SWAP_SHIFT            2
>> +#define WRMIFX3_FMT_MTX_UV_SWAP_VU               0
>> +#define WRMIFX3_FMT_MTX_UV_SWAP_UV               1
>> +#define WRMIFX3_FMT_MTX_PLANE_MASK               GENMASK(5, 4)
>> +#define WRMIFX3_FMT_MTX_PLANE_SHIFT              4
>> +#define WRMIFX3_FMT_MTX_PLANE_X1                 0
>> +#define WRMIFX3_FMT_MTX_PLANE_X2                 1
>> +#define WRMIFX3_FMT_MODE_OUT_MASK                GENMASK(18, 16)
>> +#define WRMIFX3_FMT_MODE_OUT_SHIFT               16
>> +#define WRMIFX3_FMT_MODE_OUT_YUV422              1
>> +#define WRMIFX3_FMT_MODE_OUT_YUV420              2
>> +#define WRMIFX3_FMT_MODE_OUT_Y_ONLY              3
>> +
>> +#define ISP_WRMIFX3_0_WIN_LUMA_H                 0xc420
>> +#define WRMIFX3_WIN_LUMA_HEND_MASK               GENMASK(28, 16)
>> +#define WRMIFX3_WIN_LUMA_HEND_SHIFT              16
>> +#define WRMIFX3_WIN_LUMA_HEND(x)                 ((x) - 1)
>> +
>> +#define ISP_WRMIFX3_0_WIN_LUMA_V                 0xc424
>> +#define WRMIFX3_WIN_LUMA_VEND_MASK               GENMASK(28, 16)
>> +#define WRMIFX3_WIN_LUMA_VEND_SHIFT              16
>> +#define WRMIFX3_WIN_LUMA_VEND(x)                 ((x) - 1)
>> +
>> +#define ISP_WRMIFX3_0_WIN_CHROM_H                0xc428
>> +#define WRMIFX3_WIN_CHROM_HEND_MASK              GENMASK(28, 16)
>> +#define WRMIFX3_WIN_CHROM_HEND_SHIFT             16
>> +#define WRMIFX3_WIN_CHROM_HEND(x)                (((x) >> 1) - 1)
>> +
>> +#define ISP_WRMIFX3_0_WIN_CHROM_V                0xc42c
>> +#define WRMIFX3_WIN_CHROM_VEND_MASK              GENMASK(28, 16)
>> +#define WRMIFX3_WIN_CHROM_VEND_SHIFT             16
>> +#define WRMIFX3_WIN_CHROM_VEND(x)                (((x) >> 1) - 1)
>> +
>> +#define ISP_WRMIFX3_0_CRP_HSIZE                  0xc48c
>> +#define WRMIFX3_CROP_HEND_MASK                   GENMASK(31, 16)
>> +#define WRMIFX3_CROP_HEND_SHIFT                  16
>> +#define WRMIFX3_CROP_HEND(x)                     ((x) - 1)
>> +
>> +#define ISP_WRMIFX3_0_CRP_VSIZE                  0xc490
>> +#define WRMIFX3_CROP_VEND_MASK                   GENMASK(31, 16)
>> +#define WRMIFX3_CROP_VEND_SHIFT                  16
>> +#define WRMIFX3_CROP_VEND(x)                     ((x) - 1)
>> +
>> +#define VIU_DMAWR_BADDR0                         0xc840
>> +#define VIU_DMAWR_AF_BADDR_MASK                  GENMASK(27, 0)
>> +/* AF base address need 16 bits alignment */
>> +#define VIU_DMAWR_AF_BADDR(x)                    ((x) >> 4)
>> +
>> +#define VIU_DMAWR_BADDR1                         0xc844
>> +#define VIU_DMAWR_AWB_BADDR_MASK                  GENMASK(27, 0)
>> +/* AWB base address need 16 bits alignment */
>> +#define VIU_DMAWR_AWB_BADDR(x)                    ((x) >> 4)
>> +
>> +#define VIU_DMAWR_BADDR2                         0xc848
>> +#define VIU_DMAWR_AE_BADDR_MASK                  GENMASK(27, 0)
>> +/* AE base address need 16 bits alignment */
>> +#define VIU_DMAWR_AE_BADDR(x)                    ((x) >> 4)
>> +
>> +#define VIU_DMAWR_SIZE0                          0xc854
>> +#define VIU_DMAWR_SIZE_AF_MASK                   GENMASK(15, 0)
>> +#define VIU_DMAWR_SIZE_AWB_MASK                  GENMASK(31, 16)
>> +#define VIU_DMAWR_SIZE_AWB_SHIFT                 16
>> +
>> +#define VIU_DMAWR_SIZE1                          0xc858
>> +#define VIU_DMAWR_SIZE_AE_MASK                   GENMASK(15, 0)
>> +
>> +#endif
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
>> new file mode 100644
>> index 000000000000..01d99b66cb32
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
>> @@ -0,0 +1,768 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/pm_runtime.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +
>> +static const struct c3_isp_mbus_format_info c3_isp_rsz_mbus_formats[] = {
>> +     /* YUV formats */
>> +     {
>> +             .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
>> +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>> +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_1_5X8,
>> +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>> +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_2X8,
> These mbus_codes come from the ISP, I presume after debayering.
> Is the different samples number (5X8, 2X8) a representation of the
> format on the internal bus between the ISP and the resizers ?
>

No,  there is no internal bus.

>> +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>> +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +     },
>> +};
>> +
>> +/* The normal parameters of pps module */
>> +static const int pps_lut_tap4_s11[C3_PPS_TAP4_S11_H_NUM][4] =  {
>> +     {  0, 511,   0,   0}, { -5, 511,   5,   0}, {-10, 511,  11,   0},
>> +     {-14, 510,  17,  -1}, {-18, 508,  23,  -1}, {-22, 506,  29,  -1},
>> +     {-25, 503,  36,  -2}, {-28, 500,  43,  -3}, {-32, 496,  51,  -3},
>> +     {-34, 491,  59,  -4}, {-37, 487,  67,  -5}, {-39, 482,  75,  -6},
>> +     {-41, 476,  84,  -7}, {-42, 470,  92,  -8}, {-44, 463, 102,  -9},
>> +     {-45, 456, 111, -10}, {-45, 449, 120, -12}, {-47, 442, 130, -13},
>> +     {-47, 434, 140, -15}, {-47, 425, 151, -17}, {-47, 416, 161, -18},
>> +     {-47, 407, 172, -20}, {-47, 398, 182, -21}, {-47, 389, 193, -23},
>> +     {-46, 379, 204, -25}, {-45, 369, 215, -27}, {-44, 358, 226, -28},
>> +     {-43, 348, 237, -30}, {-43, 337, 249, -31}, {-41, 326, 260, -33},
>> +     {-40, 316, 271, -35}, {-39, 305, 282, -36}, {-37, 293, 293, -37}
>> +};
>> +
>> +static const struct c3_isp_mbus_format_info
>> +*rsz_find_format_by_code(u32 code, u32 pad)
>> +{
>> +     int i;
> unsigned


Will use unsigned int.

>> +
>> +     for (i = 0; i < ARRAY_SIZE(c3_isp_rsz_mbus_formats); i++) {
>> +             const struct c3_isp_mbus_format_info *info =
>> +                     &c3_isp_rsz_mbus_formats[i];
>> +
>> +             if (info->mbus_code == code && info->pads & BIT(pad))
>> +                     return info;
>> +     }
>> +
>> +     return NULL;
>> +}
>> +
>> +static void c3_isp_rsz_cfg_fmt(struct c3_isp_resizer *rsz,
>> +                            struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +
>> +     fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>> +
>> +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_DISP0_TOP_IN_SIZE, rsz->id),
>> +                  DISP_TOP_IN_HSIZE(fmt->width) | DISP_TOP_IN_VSIZE(fmt->height));
>> +}
>> +
>> +static void c3_isp_rsz_crop_enable(struct c3_isp_resizer *rsz,
>> +                                struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_rect *crop;
>> +
>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>> +
>> +     c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_START, rsz->id),
>> +                  DISP_CRP2_VSTART(crop->top) | DISP_CRP2_HSTART(crop->left));
>> +     c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_SIZE, rsz->id),
>> +                  DISP_CRP2_VSIZE(crop->height) | DISP_CRP2_HSIZE(crop->width));
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
>> +                        DISP_CRP2_EN, DISP_CRP2_EN);
>> +}
>> +
>> +static void c3_isp_rsz_pps_size(struct c3_isp_resizer *rsz,
>> +                             struct c3_isp_pps_io_size *io_size)
>> +{
>> +     int thsize = io_size->thsize;
>> +     int tvsize = io_size->tvsize;
>> +     u32 ohsize = io_size->ohsize;
>> +     u32 ovsize = io_size->ovsize;
>> +     u32 ihsize = io_size->ihsize;
>> +     u32 max_hsize = io_size->max_hsize;
>> +     int step_h_integer, step_v_integer;
>> +     int step_h_fraction, step_v_fraction;
>> +     int yuv444to422_en;
>> +
>> +     /* Calculate the integer part of horizonal scaler step */
>> +     step_h_integer = thsize / ohsize;
>> +
>> +     /* Calculate the vertical part of horizonal scaler step */
>> +     step_v_integer = tvsize / ovsize;
>> +
>> +     /*
>> +      * Calculate the fraction part of horizonal scaler step.
>> +      * step_h_fraction = (source / dest) * 2^24,
>> +      * so step_h_fraction = ((source << 12) / dest) << 12.
>> +      */
>> +     step_h_fraction = ((thsize << 12) / ohsize) << 12;
>> +
>> +     /*
>> +      * Calculate the fraction part of vertical scaler step
>> +      * step_v_fraction = (source / dest) * 2^24,
>> +      * so step_v_fraction = ((source << 12) / dest) << 12.
>> +      */
>> +     step_v_fraction = ((tvsize << 12) / ovsize) << 12;
>> +
>> +     yuv444to422_en = ihsize > (max_hsize / 2) ? 1 : 0;
>> +
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_444TO422, rsz->id),
>> +                        PPS_444TO422_EN_MASK, yuv444to422_en);
>> +
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>> +                        PPS_VSC_FRACTION_PART_MASK, step_v_fraction);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>> +                        PPS_VSC_INTEGER_PART_MASK,
>> +                        step_v_integer << PPS_VSC_INTEGER_PART_SHIFT);
>> +
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>> +                        PPS_HSC_FRACTION_PART_MASK, step_h_fraction);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>> +                        PPS_HSC_INTEGER_PART_MASK,
>> +                        step_h_integer << PPS_HSC_INTEGER_PART_SHIFT);
>> +
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
>> +                        PPS_PREHSC_LUMA_COEF0_MASK, PPS_PREHSC_LUMA_COEF0_INIT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
>> +                        PPS_PREHSC_LUMA_COEF1_MASK,
>> +                        PPS_PREHSC_LUMA_COEF1_INIT << PPS_PREHSC_LUMA_COEF1_SHIFT);
>> +
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
>> +                        PPS_PREHSC_LUMA_COEF2_MASK, PPS_PREHSC_LUMA_COEF2_INIT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
>> +                        PPS_PREHSC_LUMA_COEF3_MASK,
>> +                        PPS_PREHSC_LUMA_COEF3_INIT << PPS_PREHSC_LUMA_COEF3_SHIFT);
>> +}
>> +
>> +static void c3_isp_rsz_pps_lut(struct c3_isp_resizer *rsz, u32 ctype)
>> +{
>> +     int i;
> unsigned


Will use unsigned int.

>> +
>> +     /*
>> +      * Default value of this register is 0,
>> +      * so only need to set SCALE_LUMA_COEF_S11_MODE
>> +      * and SCALE_LUMA_CTYPE.
> You can fit this in 2 lines ?


OK, will fit this in 2 lines.

>> +      * This register needs to be written in one time.
>> +      */
>> +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_LUMA, rsz->id),
>> +                  SCALE_LUMA_COEF_S11_MODE | SCALE_LUMA_CTYPE(ctype));
>> +
>> +     for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
>> +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>> +                          SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
>> +                          SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
>> +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>> +                          SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
>> +                          SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][3]));
>> +     }
>> +
>> +     /*
>> +      * Default value of this register is 0,
>> +      * so only need to set SCALE_CHRO_COEF_S11_MODE
>> +      * and SCALE_CHRO_CTYPE.
> same
>

OK, will fit this in 2 lines.

>> +      * This register needs to be written in one time.
>> +      */
>> +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_CHRO, rsz->id),
>> +                  SCALE_CHRO_COEF_S11_MODE | SCALE_CHRO_CTYPE(ctype));
>> +
>> +     for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
>> +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>> +                          SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
>> +                          SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
>> +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>> +                          SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
>> +                          SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][3]));
>> +     }
>> +}
>> +
>> +static void c3_isp_rsz_pps_disable(struct c3_isp_resizer *rsz)
>> +{
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_HSC_EN_MASK, 0);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_VSC_EN_MASK, 0);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_PREVSC_EN_MASK, 0);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_PREHSC_EN_MASK, 0);
>> +}
>> +
>> +static int c3_isp_rsz_pps_enable(struct c3_isp_resizer *rsz,
>> +                              struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_rect *crop;
>> +     struct v4l2_rect *cmps;
>> +     int max_hsize;
>> +     int hsc_en, vsc_en;
>> +     int preh_en, prev_en;
>> +     u32 reg_prehsc_rate;
>> +     u32 reg_prevsc_flt_num;
>> +     int pre_vscale_max_hsize;
>> +     u32 ihsize_after_pre_hsc;
>> +     u32 ihsize_after_pre_hsc_alt;
>> +     u32 reg_vsc_tap_num_alt;
>> +     u32 ihsize;
>> +     u32 ivsize;
>> +     struct c3_isp_pps_io_size io_size;
>> +
>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>> +     cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>> +
>> +     ihsize = crop->width;
>> +     ivsize = crop->height;
>> +
>> +     hsc_en = (ihsize == cmps->width) ? C3_SCALE_DIS : C3_SCALE_EN;
>> +     vsc_en = (ivsize == cmps->height) ? C3_SCALE_DIS : C3_SCALE_EN;
>> +
>> +     /* Disable pps when there no need to use pps */
>> +     if (!hsc_en && !vsc_en) {
>> +             c3_isp_rsz_pps_disable(rsz);
>> +             return 0;
>> +     }
>> +
>> +     /*
>> +      * Pre-scale needs to be enable
>> +      * if the down scaling factor exceeds 4.
>> +      */
>> +     preh_en = (ihsize > cmps->width * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
>> +     prev_en = (ivsize > cmps->height * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
>> +
>> +     if (rsz->id == C3_ISP_RSZ_2) {
>> +             max_hsize = C3_ISP_MAX_WIDTH;
>> +             /*
>> +              * Set vertical tap number and
>> +              * the max hsize of pre-vertical scale.
>> +              */
>> +             reg_prevsc_flt_num = 4;
>> +             pre_vscale_max_hsize = max_hsize / 2;
>> +     } else {
>> +             max_hsize = C3_ISP_DEFAULT_WIDTH;
>> +             preh_en  = (ihsize > max_hsize) ? C3_SCALE_EN : C3_SCALE_DIS;
>> +             /*
>> +              * Set vertical tap number and
>> +              * the max hsize of pre-vertical scale.
>> +              */
>> +             if (ihsize > (max_hsize / 2) &&
>> +                 ihsize <= max_hsize && prev_en) {
>> +                     reg_prevsc_flt_num = 2;
>> +                     pre_vscale_max_hsize = max_hsize;
>> +             } else {
>> +                     reg_prevsc_flt_num = 4;
>> +                     pre_vscale_max_hsize = max_hsize / 2;
>> +             }
>> +     }
>> +
>> +     /*
>> +      * Set pre-horizonal scale rate and
>> +      * the hsize of after pre-horizonal scale.
>> +      */
>> +     if (preh_en) {
>> +             reg_prehsc_rate = 1;
>> +             ihsize_after_pre_hsc = DIV_ROUND_UP(ihsize, 2);
>> +     } else {
>> +             reg_prehsc_rate = 0;
>> +             ihsize_after_pre_hsc = ihsize;
>> +     }
>> +
>> +     /* Change pre-horizonal scale rate */
>> +     if (prev_en && ihsize_after_pre_hsc >= pre_vscale_max_hsize)
>> +             reg_prehsc_rate += 1;
>> +
>> +     /* Set the actual hsize of after pre-horizonal scale */
>> +     if (preh_en)
>> +             ihsize_after_pre_hsc_alt =
>> +                     DIV_ROUND_UP(ihsize, 1 << reg_prehsc_rate);
>> +     else
>> +             ihsize_after_pre_hsc_alt = ihsize;
>> +
>> +     /* Set vertical scaler bank length */
>> +     if (ihsize_after_pre_hsc_alt <= (max_hsize / 2))
>> +             reg_vsc_tap_num_alt = 4;
>> +     else if (ihsize_after_pre_hsc_alt <= max_hsize)
>> +             reg_vsc_tap_num_alt = prev_en ? 2 : 4;
>> +     else
>> +             reg_vsc_tap_num_alt = prev_en ? 4 : 2;
>> +
>> +     io_size.thsize = ihsize_after_pre_hsc_alt;
>> +     io_size.tvsize = prev_en ? DIV_ROUND_UP(ivsize, 2) : ivsize;
>> +     io_size.ohsize = cmps->width;
>> +     io_size.ovsize = cmps->height;
>> +     io_size.ihsize = ihsize;
>> +     io_size.max_hsize = max_hsize;
>> +
>> +     c3_isp_rsz_pps_size(rsz, &io_size);
>> +     c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_0);
>> +     c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_2);
>> +
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_VSC_TAP_NUM_MASK, reg_vsc_tap_num_alt);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_HSC_TAP_NUM_MASK,
>> +                        PPS_HSC_TAP_NUM_INIT << PPS_HSC_TAP_NUM_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_PREVSC_FLT_NUM_MASK,
>> +                        reg_prevsc_flt_num << PPS_PREVSC_FLT_NUM_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_PREHSC_FLT_NUM_MASK,
>> +                        PPS_PREHSC_FLT_NUM_INIT << PPS_PREHSC_FLT_NUM_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_PREVSC_RATE_MASK, prev_en << PPS_PREVSC_RATE_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_PREHSC_RATE_MASK, reg_prehsc_rate << PPS_PREHSC_RATE_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_HSC_EN_MASK, hsc_en << PPS_HSC_EN_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_VSC_EN_MASK, vsc_en << PPS_VSC_EN_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_PREVSC_EN_MASK, prev_en << PPS_PREVSC_EN_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_PREHSC_EN_MASK, preh_en << PPS_PREHSC_EN_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_HSC_NOR_RS_BITS_MASK,
>> +                        PPS_HSC_NOR_RS_BITS_INIT << PPS_HSC_NOR_RS_BITS_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>> +                        PPS_VSC_NOR_RS_BITS_MASK,
>> +                        PPS_VSC_NOR_RS_BITS_INIT << PPS_VSC_NOR_RS_BITS_SHIFT);
>> +
>> +     return 0;
>> +}
>> +
>> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz)
>> +{
>> +     struct v4l2_subdev_state *state;
>> +
>> +     state = v4l2_subdev_lock_and_get_active_state(&rsz->sd);
>> +
>> +     c3_isp_rsz_cfg_fmt(rsz, state);
>> +     c3_isp_rsz_crop_enable(rsz, state);
>> +     c3_isp_rsz_pps_enable(rsz, state);
>> +
>> +     c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN,
>> +                        TOP_DISP_EN(rsz->id), TOP_DISP_EN(rsz->id));
>> +
>> +     v4l2_subdev_unlock_state(state);
>> +}
>> +
>> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
>> +{
>> +     c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, TOP_DISP_EN(rsz->id), 0);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
>> +                        DISP_CRP2_EN, 0x0);
>> +
>> +     c3_isp_rsz_pps_disable(rsz);
>> +}
>> +
>> +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *state,
>> +                               struct v4l2_subdev_krouting *routing)
>> +{
>> +     static const struct v4l2_mbus_framefmt format = {
>> +             .width = C3_ISP_DEFAULT_WIDTH,
>> +             .height = C3_ISP_DEFAULT_HEIGHT,
>> +             .code = C3_ISP_RSZ_DEF_PAD_FMT,
>> +             .field = V4L2_FIELD_NONE,
>> +             .colorspace = V4L2_COLORSPACE_SRGB,
>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>> +             .xfer_func = V4L2_XFER_FUNC_SRGB,
>> +     };
>> +     int ret;
>> +
>> +     ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_subdev_route routes;
>> +     struct v4l2_subdev_krouting routing;
>> +
>> +     routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
>> +     routes.sink_stream = 0;
>> +     routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
>> +     routes.source_stream = 0;
>> +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>> +
>> +     routing.num_routes = 1;
>> +     routing.routes = &routes;
>> +
>> +     return c3_isp_rsz_cfg_routing(sd, state, &routing);
>> +}
>> +
>> +static int c3_isp_rsz_set_routing(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *state,
>> +                               enum v4l2_subdev_format_whence which,
>> +                               struct v4l2_subdev_krouting *routing)
>> +{
>> +     bool is_streaming = v4l2_subdev_is_streaming(sd);
>> +
>> +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>> +             return -EBUSY;
>> +
>> +     return c3_isp_rsz_cfg_routing(sd, state, routing);
>> +}
> Unless there are reasons I missed, I would drop routing support from
> the resizers
>


Will remove this interface.

>> +
>> +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
>> +                                  struct v4l2_subdev_state *state,
>> +                                  struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +     if (code->index >= ARRAY_SIZE(c3_isp_rsz_mbus_formats))
>> +             return -EINVAL;
>> +
>> +     code->code = c3_isp_rsz_mbus_formats[code->index].mbus_code;
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_isp_rsz_set_sink_fmt(struct v4l2_subdev_state *state,
>> +                                 struct v4l2_subdev_format *format)
>> +{
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>> +     struct v4l2_rect *sink_crop;
>> +     struct v4l2_rect *sink_cmps;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +     sink_crop = v4l2_subdev_state_get_crop(state, format->pad);
>> +     sink_cmps = v4l2_subdev_state_get_compose(state, format->pad);
>> +
>> +     isp_fmt = rsz_find_format_by_code(format->format.code, format->pad);
>> +     if (!isp_fmt)
>> +             sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>> +     else
>> +             sink_fmt->code = format->format.code;
>> +
>> +     sink_fmt->width = clamp_t(u32, format->format.width,
>> +                               C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>> +     sink_fmt->height = clamp_t(u32, format->format.height,
>> +                                C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>> +
>> +     sink_crop->width = sink_fmt->width;
>> +     sink_crop->height = sink_fmt->height;
>> +     sink_crop->left = 0;
>> +     sink_crop->top = 0;
>> +
>> +     sink_cmps->width = sink_crop->width;
>> +     sink_cmps->height = sink_crop->height;
>> +     sink_cmps->left = 0;
>> +     sink_cmps->top = 0;
>> +
>> +     format->format = *sink_fmt;
>> +}
>> +
>> +static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state,
>> +                                   struct v4l2_subdev_format *format)
>> +{
>> +     const struct c3_isp_mbus_format_info *rsz_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
>> +     struct v4l2_rect *sink_crop;
>> +     struct v4l2_rect *sink_cmps;
>> +
>> +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +     sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>> +     sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>> +
>> +     rsz_fmt = rsz_find_format_by_code(format->format.code, format->pad);
>> +     if (!rsz_fmt)
>> +             src_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>> +     else
>> +             src_fmt->code = format->format.code;
>> +
>> +     src_fmt->width = clamp_t(u32, format->format.width,
>> +                              C3_ISP_MIN_WIDTH, sink_crop->width);
>> +     src_fmt->height = clamp_t(u32, format->format.height,
>> +                               C3_ISP_MIN_HEIGHT, sink_crop->height);
>> +
>> +     /* The sink compose size must be same with the source size. */
>> +     sink_cmps->width = src_fmt->width;
>> +     sink_cmps->height = src_fmt->height;
> Shouldn't it be the other way around ? The source sizes should always
> match the sink compose rectangle sizes ?


OK, will check and test this issue.

>> +
>> +     format->format = *src_fmt;
>> +}
>> +
>> +static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd,
>> +                           struct v4l2_subdev_state *state,
>> +                           struct v4l2_subdev_format *format)
>> +{
>> +     if (format->pad == C3_ISP_RESIZER_PAD_SINK) {
>> +             c3_isp_rsz_set_sink_fmt(state, format);
>> +     } else if (format->pad == C3_ISP_RESIZER_PAD_SOURCE) {
>> +             c3_isp_rsz_set_source_fmt(state, format);
>> +     } else {
> This can't happen


Will remove this branch.

>> +             dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
>> +             return -ENOTTY;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd,
>> +                                 struct v4l2_subdev_state *state,
>> +                                 struct v4l2_subdev_selection *sel)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +     struct v4l2_rect *crop;
>> +     struct v4l2_rect *cmps;
>> +
>> +     if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
>> +             return -EINVAL;
>> +
>> +     switch (sel->target) {
>> +     case V4L2_SEL_TGT_CROP_BOUNDS:
>> +             fmt = v4l2_subdev_state_get_format(state, sel->pad);
>> +             sel->r.width = fmt->width;
>> +             sel->r.height = fmt->height;
>> +             sel->r.left = 0;
>> +             sel->r.top = 0;
>> +             break;
>> +     case V4L2_SEL_TGT_COMPOSE_BOUNDS:
>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>> +             sel->r.width = crop->width;
>> +             sel->r.height = crop->height;
>> +             sel->r.left = 0;
>> +             sel->r.top = 0;
>> +             break;
>> +     case V4L2_SEL_TGT_CROP:
>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>> +             sel->r = *crop;
>> +             break;
>> +     case V4L2_SEL_TGT_COMPOSE:
>> +             cmps = v4l2_subdev_state_get_compose(state, sel->pad);
>> +             sel->r = *cmps;
>> +             break;
>> +     default:
>> +             return -EINVAL;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd,
>> +                                 struct v4l2_subdev_state *state,
>> +                                 struct v4l2_subdev_selection *sel)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +     struct v4l2_rect *crop;
>> +     struct v4l2_rect *cmps;
>> +
>> +     if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
>> +             return -EINVAL;
>> +
>> +     switch (sel->target) {
>> +     case V4L2_SEL_TGT_CROP:
>> +             fmt = v4l2_subdev_state_get_format(state, sel->pad);
>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>> +
>> +             sel->r.left = clamp_t(s32, sel->r.left, 0, fmt->width - 1);
>> +             sel->r.top = clamp_t(s32, sel->r.top, 0, fmt->height - 1);
>> +             sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH,
>> +                                  fmt->width - sel->r.left);
>> +             sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT,
>> +                                   fmt->height - sel->r.top);
>> +
>> +             crop->width = ALIGN(sel->r.width, 2);
>> +             crop->height = ALIGN(sel->r.height, 2);
>> +             crop->left = sel->r.left;
>> +             crop->top = sel->r.top;
>> +
>> +             sel->r = *crop;
>> +             break;
>> +     case V4L2_SEL_TGT_COMPOSE:
>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>> +             cmps = v4l2_subdev_state_get_compose(state, sel->pad);
>> +
>> +             sel->r.left = 0;
>> +             sel->r.top = 0;
>> +             sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH, crop->width);
>> +             sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT, crop->height);
>> +
>> +             cmps->width = ALIGN(sel->r.width, 2);
>> +             cmps->height = ALIGN(sel->r.height, 2);
>> +             cmps->left = sel->r.left;
>> +             cmps->top = sel->r.top;
>> +
>> +             sel->r = *cmps;
>> +
>> +             fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
>> +             fmt->width = cmps->width;
>> +             fmt->height = cmps->height;
>> +             break;
>> +     default:
>> +             return -EINVAL;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_rsz_init_state(struct v4l2_subdev *sd,
>> +                              struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
>> +     struct v4l2_rect *crop;
>> +     struct v4l2_rect *cmps;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>> +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>> +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>> +     sink_fmt->field = V4L2_FIELD_NONE;
>> +     sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>> +     sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>> +     sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>> +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>> +     sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>> +
>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>> +     crop->width = C3_ISP_DEFAULT_WIDTH;
>> +     crop->height = C3_ISP_DEFAULT_HEIGHT;
>> +     crop->left = 0;
>> +     crop->top = 0;
>> +
>> +     cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>> +     cmps->width = C3_ISP_DEFAULT_WIDTH;
>> +     cmps->height = C3_ISP_DEFAULT_HEIGHT;
>> +     cmps->left = 0;
>> +     cmps->top = 0;
>> +
>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
>> +     *src_fmt = *sink_fmt;
>> +
>> +     return c3_isp_rsz_init_routing(sd, state);
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops c3_isp_rsz_pad_ops = {
>> +     .enum_mbus_code = c3_isp_rsz_enum_mbus_code,
>> +     .get_fmt = v4l2_subdev_get_fmt,
>> +     .set_fmt = c3_isp_rsz_set_fmt,
>> +     .get_selection = c3_isp_rsz_get_selection,
>> +     .set_selection = c3_isp_rsz_set_selection,
>> +     .set_routing = c3_isp_rsz_set_routing,
>> +};
>> +
>> +static const struct v4l2_subdev_ops c3_isp_rsz_subdev_ops = {
>> +     .pad = &c3_isp_rsz_pad_ops,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops c3_isp_rsz_internal_ops = {
>> +     .init_state = c3_isp_rsz_init_state,
>> +};
>> +
>> +/* Media entity operations */
>> +static const struct media_entity_operations c3_isp_rsz_entity_ops = {
>> +     .link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +static int c3_isp_rsz_register(struct c3_isp_resizer *rsz)
>> +{
>> +     struct v4l2_subdev *sd = &rsz->sd;
>> +     int ret;
>> +
>> +     v4l2_subdev_init(sd, &c3_isp_rsz_subdev_ops);
>> +     sd->owner = THIS_MODULE;
>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +     sd->internal_ops = &c3_isp_rsz_internal_ops;
>> +     snprintf(sd->name, sizeof(sd->name), "isp-resizer%u", rsz->id);
> maybe "c3-isp_resizer%u"
>

OK, will use "c3-isp_resizer%u".

>> +
>> +     sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
>> +     sd->entity.ops = &c3_isp_rsz_entity_ops;
>> +
>> +     sd->dev = rsz->isp->dev;
>> +     v4l2_set_subdevdata(sd, rsz);
>> +
>> +     rsz->pads[C3_ISP_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>> +     rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>> +     ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_PAD_MAX, rsz->pads);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_init_finalize(sd);
>> +     if (ret)
>> +             goto err_entity_cleanup;
>> +
>> +     ret = v4l2_device_register_subdev(&rsz->isp->v4l2_dev, sd);
>> +     if (ret)
>> +             goto err_subdev_cleanup;
>> +
>> +     return 0;
>> +
>> +err_subdev_cleanup:
>> +     v4l2_subdev_cleanup(sd);
>> +err_entity_cleanup:
>> +     media_entity_cleanup(&sd->entity);
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_rsz_unregister(struct c3_isp_resizer *rsz)
>> +{
>> +     struct v4l2_subdev *sd = &rsz->sd;
>> +
>> +     v4l2_device_unregister_subdev(sd);
>> +     v4l2_subdev_cleanup(sd);
>> +     media_entity_cleanup(&sd->entity);
>> +}
>> +
>> +int c3_isp_resizers_register(struct c3_isp_device *isp)
>> +{
>> +     u32 i;
>> +     int ret;
>> +
>> +     for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
>> +             struct c3_isp_resizer *rsz = &isp->resizers[i];
>> +
>> +             rsz->id = i;
>> +             rsz->isp = isp;
>> +
>> +             if (rsz->id == C3_ISP_RSZ_0)
>> +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_0];
>> +             else if (rsz->id == C3_ISP_RSZ_1)
>> +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_1];
>> +             else
>> +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_2];
>> +
>> +             ret = c3_isp_rsz_register(rsz);
>> +             if (ret) {
>> +                     rsz->isp = NULL;
>> +                     c3_isp_resizers_unregister(isp);
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +void c3_isp_resizers_unregister(struct c3_isp_device *isp)
>> +{
>> +     u32 i;
>> +
>> +     for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
>> +             struct c3_isp_resizer *rsz = &isp->resizers[i];
>> +
>> +             if (rsz->isp)
>> +                     c3_isp_rsz_unregister(rsz);
>> +     };
>> +}
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
>> new file mode 100644
>> index 000000000000..72024442d48f
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
>> @@ -0,0 +1,488 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/cleanup.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-event.h>
>> +#include <media/v4l2-ioctl.h>
>> +#include <media/v4l2-mc.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +#include "include/uapi/c3-isp-config.h"
>> +
>> +/* Hardware configuration */
>> +
>> +static void c3_isp_stats_af_init(struct c3_isp_stats *stats)
>> +{
>> +     c3_isp_write(stats->isp, ISP_AF_ROI0_WIN01,
>> +                  AF_ROI_XYWIN_00_INIT | AF_ROI_XYWIN_01_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AF_ROI0_WIN23,
>> +                  AF_ROI_XYWIN_02_INIT | AF_ROI_XYWIN_03_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AF_ROI1_WIN01,
>> +                  AF_ROI_XYWIN_10_INIT | AF_ROI_XYWIN_11_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AF_ROI1_WIN23,
>> +                  AF_ROI_XYWIN_12_INIT | AF_ROI_XYWIN_13_INIT);
>> +
>> +     /* 0: old statistics output, 1: new statistics output. */
>> +     c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL,
>> +                        AF_STAT_SELECT, 0x1 << AF_STAT_SELECT_SHIFT);
>> +}
>> +
>> +static void c3_isp_stats_ae_init(struct c3_isp_stats *stats)
>> +{
>> +     c3_isp_write(stats->isp, ISP_AE_ROI0_WIN01,
>> +                  AE_ROI_XYWIN_00_INIT | AE_ROI_XYWIN_01_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AE_ROI0_WIN23,
>> +                  AE_ROI_XYWIN_02_INIT | AE_ROI_XYWIN_03_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AE_ROI1_WIN01,
>> +                  AE_ROI_XYWIN_10_INIT | AE_ROI_XYWIN_11_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AE_ROI1_WIN23,
>> +                  AE_ROI_XYWIN_12_INIT | AE_ROI_XYWIN_13_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AE_STAT_THD01,
>> +                  AE_STAT_THRD0_INIT | AE_STAT_THRD1_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AE_STAT_THD23,
>> +                  AE_STAT_THRD2_INIT | AE_STAT_THRD3_INIT);
>> +
>> +     /* Set 0 when ae_stat_switch is not 0 */
>> +     c3_isp_update_bits(stats->isp, ISP_AE_CTRL,
>> +                        AE_CTRL_INPUT_2LINE_TOGETHER, 0);
>> +
>> +     /* Configure ae luma mode */
>> +     c3_isp_update_bits(stats->isp, ISP_AE_CTRL, AE_CTRL_LUMA_MODE_MASK,
>> +                        AE_CTRL_LUMA_MODE_FILTER << AE_CTRL_LUMA_MODE_SHIFT);
>> +}
>> +
>> +static void c3_isp_stats_awb_init(struct c3_isp_stats *stats)
>> +{
>> +     /* Initialize the awb statistics rectangle of image */
>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN01,
>> +                  AWB_ROI_XYWIN_00_INIT | AWB_ROI_XYWIN_01_INIT);
>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN23,
>> +                  AWB_ROI_XYWIN_02_INIT | AWB_ROI_XYWIN_03_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN01,
>> +                  AWB_ROI_XYWIN_10_INIT | AWB_ROI_XYWIN_11_INIT);
>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN23,
>> +                  AWB_ROI_XYWIN_12_INIT | AWB_ROI_XYWIN_13_INIT);
>> +}
>> +
>> +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
>> +{
>> +     struct c3_isp_device *isp = stats->isp;
>> +     struct c3_isp_stats_info *stats_info = stats->buff->vaddr;
>> +     u32 awb_dma_size = sizeof(stats_info->awb_stats);
>> +     u32 ae_dma_size = sizeof(stats_info->ae_stats);
>> +     u32 awb_dma_addr = stats->buff->paddr;
>> +     u32 af_dma_addr;
>> +     u32 ae_dma_addr;
>> +
>> +     ae_dma_addr = awb_dma_addr + awb_dma_size;
>> +     af_dma_addr = ae_dma_addr + ae_dma_size;
>> +
>> +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR0, VIU_DMAWR_AF_BADDR_MASK,
>> +                        VIU_DMAWR_AF_BADDR(af_dma_addr));
>> +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR1, VIU_DMAWR_AWB_BADDR_MASK,
>> +                        VIU_DMAWR_AWB_BADDR(awb_dma_addr));
>> +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR2, VIU_DMAWR_AE_BADDR_MASK,
>> +                        VIU_DMAWR_AE_BADDR(ae_dma_addr));
>> +}
>> +
>> +static void c3_isp_stats_enable(struct c3_isp_stats *stats)
>> +{
>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>> +                        TOP_3A_AE_STAT_EN, TOP_3A_AE_STAT_EN);
>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>> +                        TOP_3A_AWB_STAT_EN, TOP_3A_AWB_STAT_EN);
>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>> +                        TOP_3A_AF_STAT_EN, TOP_3A_AF_STAT_EN);
>> +}
>> +
>> +static void c3_isp_stats_disable(struct c3_isp_stats *stats)
>> +{
>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>> +                        TOP_3A_AE_STAT_EN, 0);
>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>> +                        TOP_3A_AWB_STAT_EN, 0);
>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>> +                        TOP_3A_AF_STAT_EN, 0);
>> +}
>> +
>> +/* The unit of dma_size is 16 bytes */
>> +static void c3_isp_stats_cfg_dmawr_size(struct c3_isp_stats *stats)
>> +{
>> +     u32 dma_size;
>> +
>> +     dma_size = sizeof(struct af_stats_info) / 16;
>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0,
>> +                        VIU_DMAWR_SIZE_AF_MASK, dma_size);
>> +
>> +     dma_size = sizeof(struct awb_stats_info) / 16;
>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE_AWB_MASK,
>> +                        dma_size << VIU_DMAWR_SIZE_AWB_SHIFT);
>> +
>> +     dma_size = sizeof(struct ae_stats_info) / 16;
>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1,
>> +                        VIU_DMAWR_SIZE_AE_MASK, dma_size);
>> +}
>> +
>> +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
>> +{
>> +     stats->buff = list_first_entry_or_null(&stats->pending,
>> +                                            struct c3_isp_vb2_buffer, list);
>> +     if (stats->buff) {
>> +             c3_isp_stats_cfg_dmawr_addr(stats);
>> +             list_del(&stats->buff->list);
>> +     }
>> +}
>> +
>> +static void c3_isp_stats_start(struct c3_isp_stats *stats)
>> +{
>> +     c3_isp_stats_af_init(stats);
>> +     c3_isp_stats_ae_init(stats);
>> +     c3_isp_stats_awb_init(stats);
>> +
>> +     c3_isp_stats_cfg_dmawr_size(stats);
>> +     c3_isp_stats_cfg_buff(stats);
>> +     c3_isp_stats_enable(stats);
>> +
>> +     stats->is_streaming = true;
>> +}
>> +
>> +static void c3_isp_stats_stop(struct c3_isp_stats *stats)
>> +{
>> +     stats->is_streaming = false;
>> +
>> +     c3_isp_stats_disable(stats);
>> +}
>> +
>> +static void c3_isp_stats_return_buffers(struct c3_isp_stats *stats,
>> +                                     enum vb2_buffer_state state)
>> +{
>> +     unsigned long flags;
>> +     struct c3_isp_vb2_buffer *buff;
>> +
>> +     spin_lock_irqsave(&stats->buff_lock, flags);
>> +
>> +     if (stats->buff) {
>> +             vb2_buffer_done(&stats->buff->vb.vb2_buf, state);
>> +             stats->buff = NULL;
>> +     }
>> +
>> +     while (!list_empty(&stats->pending)) {
>> +             buff = list_first_entry(&stats->pending,
>> +                                     struct c3_isp_vb2_buffer, list);
>> +             list_del(&buff->list);
>> +             vb2_buffer_done(&buff->vb.vb2_buf, state);
>> +     }
>> +
>> +     spin_unlock_irqrestore(&stats->buff_lock, flags);
>> +}
>> +
>> +static int c3_isp_stats_querycap(struct file *file, void *fh,
>> +                              struct v4l2_capability *cap)
>> +{
>> +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>> +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_stats_enum_fmt(struct file *file, void *fh,
>> +                              struct v4l2_fmtdesc *f)
>> +{
>> +     struct c3_isp_stats *stats = video_drvdata(file);
>> +
>> +     if (f->index > 0 || f->type != stats->vb2_q.type)
>> +             return -EINVAL;
>> +
>> +     f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_stats_g_fmt(struct file *file, void *fh,
>> +                           struct v4l2_format *f)
>> +{
>> +     struct c3_isp_stats *stats = video_drvdata(file);
>> +
>> +     f->fmt.meta = stats->vfmt.fmt.meta;
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
>> +     .vidioc_querycap                = c3_isp_stats_querycap,
>> +     .vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
>> +     .vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
>> +     .vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
>> +     .vidioc_try_fmt_meta_cap        = c3_isp_stats_g_fmt,
>> +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>> +     .vidioc_querybuf                = vb2_ioctl_querybuf,
>> +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
>> +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
>> +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>> +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>> +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>> +     .vidioc_streamon                = vb2_ioctl_streamon,
>> +     .vidioc_streamoff               = vb2_ioctl_streamoff,
>> +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>> +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>> +};
>> +
>> +static const struct v4l2_file_operations isp_stats_v4l2_fops = {
>> +     .open = v4l2_fh_open,
>> +     .release = vb2_fop_release,
>> +     .poll = vb2_fop_poll,
>> +     .unlocked_ioctl = video_ioctl2,
>> +     .mmap = vb2_fop_mmap,
>> +};
>> +
>> +static int c3_isp_stats_vb2_queue_setup(struct vb2_queue *q,
>> +                                     unsigned int *num_buffers,
>> +                                     unsigned int *num_planes,
>> +                                     unsigned int sizes[],
>> +                                     struct device *alloc_devs[])
>> +{
>> +     if (*num_planes) {
>> +             if (*num_planes != 1)
>> +                     return -EINVAL;
>> +
>> +             if (sizes[0] < sizeof(struct c3_isp_stats_info))
>> +                     return -EINVAL;
>> +     } else {
>> +             *num_planes = 1;
>> +             sizes[0] = sizeof(struct c3_isp_stats_info);
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_vb2_buffer *buf =
>> +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned long flags;
>> +
>> +     spin_lock_irqsave(&stats->buff_lock, flags);
>> +
>> +     list_add_tail(&buf->list, &stats->pending);
>> +
>> +     spin_unlock_irqrestore(&stats->buff_lock, flags);
>> +}
>> +
>> +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
>> +{
>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned int size = stats->vfmt.fmt.meta.buffersize;
>> +
>> +     if (vb2_plane_size(vb, 0) < size) {
>> +             dev_err(stats->isp->dev,
>> +                     "User buffer too small (%ld < %u)\n",
>> +                     vb2_plane_size(vb, 0), size);
>> +             return -EINVAL;
>> +     }
>> +
>> +     vb2_set_plane_payload(vb, 0, size);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_vb2_buffer *buf =
>> +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>> +
>> +     buf->vaddr = vb2_plane_vaddr(vb, 0);
>> +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>> +
>> +     memset(buf->vaddr, 0, stats->vfmt.fmt.meta.buffersize);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_stats_vb2_start_streaming(struct vb2_queue *q,
>> +                                         unsigned int count)
>> +{
>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(q);
>> +     int ret;
>> +
>> +     guard(mutex)(&stats->isp->lock);
>> +
>> +     ret = pm_runtime_resume_and_get(stats->isp->dev);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = video_device_pipeline_start(&stats->vdev, &stats->isp->pipe);
>> +     if (ret) {
>> +             dev_err(stats->isp->dev,
>> +                     "Failed to start stats pipeline: %d\n", ret);
>> +             goto err_pm_put;
>> +     }
>> +
>> +     if (c3_isp_pipeline_ready(stats->isp)) {
>> +             ret = v4l2_subdev_enable_streams(&stats->isp->core.sd,
>> +                                              C3_ISP_CORE_PAD_SOURCE_STATS,
>> +                                              BIT(0));
>> +             if (ret)
>> +                     goto err_pipeline_stop;
>> +     }
>> +
>> +     c3_isp_stats_start(stats);
>> +
>> +     return 0;
>> +
>> +err_pipeline_stop:
>> +     video_device_pipeline_stop(&stats->vdev);
>> +err_pm_put:
>> +     pm_runtime_put(stats->isp->dev);
>> +     c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_QUEUED);
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
>> +{
>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(q);
>> +
>> +     guard(mutex)(&stats->isp->lock);
>> +
>> +     c3_isp_stats_stop(stats);
>> +     c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_ERROR);
>> +
>> +     if (stats->isp->pipe.start_count == 1)
>> +             v4l2_subdev_disable_streams(&stats->isp->core.sd,
>> +                                         C3_ISP_CORE_PAD_SOURCE_STATS,
>> +                                         BIT(0));
>> +
>> +     video_device_pipeline_stop(&stats->vdev);
>> +     pm_runtime_put(stats->isp->dev);
>> +}
>> +
>> +static const struct vb2_ops isp_stats_vb2_ops = {
>> +     .queue_setup = c3_isp_stats_vb2_queue_setup,
>> +     .buf_queue = c3_isp_stats_vb2_buf_queue,
>> +     .buf_prepare = c3_isp_stats_vb2_buf_prepare,
>> +     .buf_init = c3_isp_stats_vb2_buf_init,
>> +     .wait_prepare = vb2_ops_wait_prepare,
>> +     .wait_finish = vb2_ops_wait_finish,
>> +     .start_streaming = c3_isp_stats_vb2_start_streaming,
>> +     .stop_streaming = c3_isp_stats_vb2_stop_streaming,
>> +};
>> +
>> +int c3_isp_stats_register(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_stats *stats = &isp->stats;
>> +     struct video_device *vdev = &stats->vdev;
>> +     struct vb2_queue *vb2_q = &stats->vb2_q;
>> +     int ret;
>> +
>> +     memset(stats, 0, sizeof(*stats));
>> +     stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
>> +     stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_info);
>> +     stats->isp = isp;
>> +     INIT_LIST_HEAD(&stats->pending);
>> +     spin_lock_init(&stats->buff_lock);
>> +
>> +     mutex_init(&stats->lock);
>> +
>> +     snprintf(vdev->name, sizeof(vdev->name), "isp-stats");
>> +     vdev->fops = &isp_stats_v4l2_fops;
>> +     vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
>> +     vdev->v4l2_dev = &isp->v4l2_dev;
>> +     vdev->lock = &stats->lock;
>> +     vdev->minor = -1;
>> +     vdev->queue = vb2_q;
>> +     vdev->release = video_device_release_empty;
>> +     vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
>> +     vdev->vfl_dir = VFL_DIR_RX;
>> +     video_set_drvdata(vdev, stats);
>> +
>> +     vb2_q->drv_priv = stats;
>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>> +     vb2_q->ops = &isp_stats_vb2_ops;
>> +     vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
>> +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>> +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>> +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>> +     vb2_q->dev = isp->dev;
>> +     vb2_q->lock = &stats->lock;
>> +     vb2_q->min_queued_buffers = 2;
>> +
>> +     ret = vb2_queue_init(vb2_q);
>> +     if (ret)
>> +             goto err_destroy;
>> +
>> +     stats->pad.flags = MEDIA_PAD_FL_SINK;
>> +     ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
>> +     if (ret)
>> +             goto err_queue_release;
>> +
>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>> +     if (ret) {
>> +             dev_err(isp->dev,
>> +                     "Failed to register %s: %d\n", vdev->name, ret);
>> +             goto err_entity_cleanup;
>> +     }
>> +
>> +     return 0;
>> +
>> +err_entity_cleanup:
>> +     media_entity_cleanup(&vdev->entity);
>> +err_queue_release:
>> +     vb2_queue_release(vb2_q);
>> +err_destroy:
>> +     mutex_destroy(&stats->lock);
>> +     return ret;
>> +}
>> +
>> +void c3_isp_stats_unregister(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_stats *stats = &isp->stats;
>> +
>> +     vb2_queue_release(&stats->vb2_q);
>> +     media_entity_cleanup(&stats->vdev.entity);
>> +     video_unregister_device(&stats->vdev);
>> +     mutex_destroy(&stats->lock);
>> +}
>> +
>> +int c3_isp_stats_done(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_stats *stats = &isp->stats;
>> +     struct c3_isp_vb2_buffer *buff = stats->buff;
>> +     unsigned long flags;
>> +
>> +     if (!stats->is_streaming)
>> +             return -EINVAL;
>> +
>> +     spin_lock_irqsave(&stats->buff_lock, flags);
>> +
>> +     if (buff) {
>> +             buff->vb.sequence = stats->isp->frm_sequence;
>> +             buff->vb.vb2_buf.timestamp = ktime_get();
>> +             buff->vb.field = V4L2_FIELD_NONE;
>> +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
>> +     }
>> +
>> +     c3_isp_stats_cfg_buff(stats);
>> +
>> +     spin_unlock_irqrestore(&stats->buff_lock, flags);
>> +
>> +     return 0;
>> +}
>> diff --git a/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
>> new file mode 100644
>> index 000000000000..84ff5741357a
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
>> @@ -0,0 +1,537 @@
>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#ifndef __C3_ISP_CONFIG_H__
>> +#define __C3_ISP_CONFIG_H__
> When moving to include/uapi/linux/media/amlogic prefix this with
> _UAPI_
>

OK, will add "_UAPI_"

>> +
>> +#define AF_STAT_BLKH_NUM             17
>> +#define AF_STAT_BLKV_NUM             15
>> +#define AF_STAT_BLK_NUM              (AF_STAT_BLKH_NUM * AF_STAT_BLKV_NUM)
>> +/* AF stats block size need to be aligned with 2 */
>> +#define AF_STAT_BLK_SIZE             ALIGN(AF_STAT_BLK_NUM, 2)
>> +#define AE_HISTOGRAM_SIZE         1024
>> +#define AE_STAT_BLKH_NUM             17
>> +#define AE_STAT_BLKV_NUM             15
>> +#define AE_STAT_BLK_NUM              (AE_STAT_BLKH_NUM * AE_STAT_BLKV_NUM)
>> +/* AE stats block size need to be aligned with 2 */
>> +#define AE_STAT_BLK_SIZE             ALIGN(AE_STAT_BLK_NUM, 2)
>> +#define AE_BLOCK_WT_NUM              255
>> +#define AE_BLK_WT_DATA_NUM_OF_GROUP  8
>> +#define AWB_STAT_BLKH_NUM            32
>> +#define AWB_STAT_BLKV_NUM            24
>> +#define AWB_STAT_BLK_NUM             (AWB_STAT_BLKH_NUM * AWB_STAT_BLKV_NUM)
>> +/* AWB stats block size need to be aligned with 2 */
>> +#define AWB_STAT_BLK_SIZE            ALIGN(AWB_STAT_BLK_NUM, 2)
>> +#define AWB_BLOCK_WT_NUM             768
>> +#define AWB_BLK_WT_DATA_NUM_OF_GROUP 8
>> +#define AWB_STAT_BLC20_NUM           4
>> +#define AWB_STAT_GAIN10_NUM          4
>> +#define BLC_OFFSET_NUM               5
>> +#define GAMMA_LUT_GROUP_NUM          4
>> +#define GAMMA_LUT_POINT_NUM          129
>> +#define GAMMA_LUT_DATA_NUM_OF_GROUP  2
>> +
>> +/**
>> + * struct awb_zone_stats - AWB statistics of a block
>> + *
>> + * AWB zone stats is aligned with 8 bytes
>> + *
>> + * @rg: the ratio of R / G in a zone
>> + * @bg: the ratio of B / G in a zone
>> + * @pixel_sum: the total number of pixels used in a zone
>> + */
>> +struct awb_zone_stats {
>> +     u16 rg;
>> +     u16 bg;
>> +     u32 pixel_sum;
>> +};
>> +
>> +/**
>> + * struct awb_stats_info - Auto white balance statistics information.
>> + *
>> + * AWB statistical information of all blocks.
>> + *
>> + * @awb_stats: array of auto white balance statistics
>> + */
>> +struct awb_stats_info {
>> +     struct awb_zone_stats awb_stats[AWB_STAT_BLK_SIZE];
>> +};
>> +
>> +/**
>> + * struct ae_zone_stats - AE statistics of a block
>> + *
>> + * AE zone stats is aligned with 8 bytes.
>> + * This is a 5-bin histogram and the total sum is
>> + * normalized to 0xffff.
>> + * So hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4)
>> + *
>> + * @hist0: the global normalized pixel count for bin 0
>> + * @hist1: the global normalized pixel count for bin 1
>> + * @hist3: the global normalized pixel count for bin 3
>> + * @hist4: the global normalized pixel count for bin 4
>> + */
>> +struct ae_zone_stats {
>> +     u16 hist0;
> Should you include  <linux/types.h> and use types prefixed with __
> (__u16) ?
>

OK, will include <linux/types.h>  and use prefix "__".

>> +     u16 hist1;
>> +     u16 hist3;
>> +     u16 hist4;
>> +};
>> +
>> +/**
>> + * struct ae_stats_info - Exposure statistics information
>> + *
>> + * AE statistical information consists of
>> + * all blocks information and a 1024-bin histogram.
>> + *
>> + * @ae_stats: array of auto exposure block statistics
>> + * @hist: a 1024-bin histogram for the entire image
>> + */
>> +struct ae_stats_info {
>> +     struct ae_zone_stats ae_stats[AE_STAT_BLK_SIZE];
>> +     u32 hist[AE_HISTOGRAM_SIZE];
>> +};
>> +
>> +/**
>> + * struct af_zone_stats - AF statistics of a block
>> + *
>> + * AF block stats is aligned with 8 bytes.
>> + * The zonal accumulated contrast metrics are stored
>> + * in floating point format with 16 bits mantissa and
>> + * 5 or 6 bits exponent.
>> + * Apart from contrast metrics we accumulate squared image and
>> + * quartic image data over the zone.
>> + *
>> + * @i2_mat: the mantissa of zonal squared image pixel sum
>> + * @i4_mat: the mantissa of zonal quartic image pixel sum
>> + * @e4_mat: the mantissa of zonal multi-directional quartic edge sum
>> + * @i2_exp: the exponent of zonal squared image pixel sum
>> + * @i4_exp: the exponent of zonal quartic image pixel sum
>> + * @e4_exp: the exponent of zonal multi-directional quartic edge sum
>> + */
>> +struct af_zone_stats {
>> +     u16 i2_mat;
>> +     u16 i4_mat;
>> +     u16 e4_mat;
>> +     u16 i2_exp: 5;
>> +     u16 i4_exp: 6;
>> +     u16 e4_exp: 5;
>> +};
>> +
>> +/**
>> + * struct af_stats_info - Auto Focus statistics information
>> + *
>> + * AF statistical information of each block
>> + *
>> + * @af_stats: array of auto focus block statistics
>> + */
>> +struct af_stats_info {
>> +     struct af_zone_stats af_stats[AF_STAT_BLK_SIZE];
>> +};
>> +
>> +/**
>> + * struct c3_isp_stats_info - V4L2_META_FMT_C3ISP_STATS
>> + *
>> + * Contains ISP statistics
>> + *
>> + * @awb_stats: auto white balance stats
>> + * @ae_stats: auto exposure stats
>> + * @af_stats: auto focus stats
>> + */
>> +struct c3_isp_stats_info {
>> +     struct awb_stats_info awb_stats;
>> +     struct ae_stats_info ae_stats;
>> +     struct af_stats_info af_stats;
>> +};
>> +
>> +/**
>> + * enum c3_isp_param_buffer_version -  C3 ISP parameters block versioning
>> + *
>> + * @C3_ISP_PARAM_BUFFER_V0: First version of C3 ISP parameters block
>> + */
>> +enum c3_isp_param_buffer_version {
>> +     C3_ISP_PARAM_BUFFER_V0,
>> +};
>> +
>> +/**
>> + * enum c3_isp_param_block_type - Enumeration of C3 ISP parameter blocks
>> + *
>> + * Each block configures a specific processing block of the C3 ISP.
>> + * The block type allows the driver to correctly interpret
>> + * the parameters block data.
>> + *
>> + * @C3_ISP_PARAM_BLOCK_WB_CHANGE: White balance change parameters
>> + * @C3_ISP_PARAM_BLOCK_WB_LUMA: White balance Luma-based parameters
>> + * @C3_ISP_PARAM_BLOCK_WB_TRIANGLE: White balance triangle parameters
>> + * @C3_ISP_PARAM_BLOCK_AWB_STATS: AWB statistics parameters
>> + * @C3_ISP_PARAM_BLOCK_AE_STATS: AE statistics parameters
>> + * @C3_ISP_PARAM_BLOCK_AF_STATS: AF statistics parameters
>> + * @C3_ISP_PARAM_BLOCK_PST_GAMMA: post gamma parameters
>> + * @C3_ISP_PARAM_BLOCK_DMSC: demosaic parameters
>> + * @C3_ISP_PARAM_BLOCK_CCM: Color correction matrix parameters
>> + * @C3_ISP_PARAM_BLOCK_CSC: Color space conversion parameters
>> + * @C3_ISP_PARAM_BLOCK_BLC: Black level correction parameters
>> + * @C3_ISP_PARAM_BLOCK_SENTINEL: First non-valid block index
>> + */
>> +enum c3_isp_param_block_type {
>> +     C3_ISP_PARAM_BLOCK_WB_CHANGE,
>> +     C3_ISP_PARAM_BLOCK_WB_LUMA,
>> +     C3_ISP_PARAM_BLOCK_WB_TRIANGLE,
>> +     C3_ISP_PARAM_BLOCK_AWB_STATS,
>> +     C3_ISP_PARAM_BLOCK_AE_STATS,
>> +     C3_ISP_PARAM_BLOCK_AF_STATS,
>> +     C3_ISP_PARAM_BLOCK_PST_GAMMA,
>> +     C3_ISP_PARAM_BLOCK_DMSC,
>> +     C3_ISP_PARAM_BLOCK_CCM,
>> +     C3_ISP_PARAM_BLOCK_CSC,
>> +     C3_ISP_PARAM_BLOCK_BLC,
>> +     C3_ISP_PARAM_BLOCK_SENTINEL
>> +};
>> +
>> +/**
>> + * struct c3_isp_param_block_header - C3 ISP parameter block header
>> + *
>> + * This structure represents the common part of all the ISP configuration
>> + * blocks. Each parameters block shall embed an instance of this structure type
>> + * as its first member, followed by the block-specific configuration data. The
>> + * driver inspects this common header to discern the block type and its size and
>> + * properly handle the block content by casting it to the correct block-specific
>> + * type.
>> + *
>> + * @type: The parameters block type (enum c3_isp_param_block_type)
>> + * @enabled: Block enabled/disabled flag
>> + * @size: Size (in bytes) of parameters block
>> + */
>> +
>> +struct c3_isp_param_block_header {
>> +     enum c3_isp_param_block_type type;
>> +     bool enabled;
>> +     size_t size;
>> +};
> What is the size of this structure ? Is it aligned or does the
> compiler inserts padding bytes ? In general, I would try to align
> everything to 8 bytes to avoid the compiler inserting padding bytes.
>
> A tool that can help you identifies holes is pahole. Just write a
> small userspace program that includes types from this header and
> declare a variable of each type of defined in this header. Pass the
> executable to pahole and it will show the memory layout of each
> member.
>

Will check this issue.

>> +
>> +/**
>> + * struct wb_change_cfg - White Balance configuration
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @wb_gain: white balance gain of each color
>> + *   wb_gain[0]: Gr gain, range 0~0xfff
>> + *   wb_gain[1]: R gain, range 0~0xfff
>> + *   wb_gain[2]: B gain, range 0~0xfff
>> + *   wb_gain[3]: Gb gain, range 0~0xfff
>> + *   wb_gain[4]: Ir gain, range 0~0xfff
>> + * @wb_limit: white balance limit of each color
>> + *   wb_limit[0]: Gr limit, 16 bits float
>> + *   wb_limit[1]: R limit, 16 bits float
>> + *   wb_limit[2]: B limit, 16 bits float
>> + *   wb_limit[3]: Gb limit, 16 bits float
>> + *   wb_limit[4]: Ir limit, 16 bits float
>> + * @ae_gain_grbgi: Gain of each color before blending to luma
>> + *   ae_gain_grbgi[0]: Gr gain, range 0~255
>> + *   ae_gain_grbgi[1]: R gain, range 0~255
>> + *   ae_gain_grbgi[2]: B gain, range 0~255
>> + *   ae_gain_grbgi[3]: Gb gain, range 0~255
>> + *   ae_gain_grbgi[4]: Ir gain, range 0~255
>> + * @ae_bl12_grbgi: Offset of each color before ae_gain_grbgi
>> + *   ae_bl12_grbgi[0]: Gr offset, range 0~4095
>> + *   ae_bl12_grbgi[1]: R offset, range 0~4095
>> + *   ae_bl12_grbgi[2]: B offset, range 0~4095
>> + *   ae_bl12_grbgi[3]: Gb offset, range 0~4095
>> + *   ae_bl12_grbgi[4]: Ir offset, range 0~4095
>> + */
>> +struct wb_change_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u32 wb_gain[5];
>> +     u32 wb_limit[5];
>> +     u32 ae_gain_grbgi[5];
>> +     u32 ae_bl12_grbgi[5];
>> +};
>> +
>> +/**
>> + * struct wb_luma_cfg - White Balance Luma-based configuration
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @awb_stat_blc20: BLC in AWB statistic
>> + *   awb_stat_blc20[0]: Gr blc, range 0~0xfffff
>> + *   awb_stat_blc20[1]: R blc, range 0~0xfffff
>> + *   awb_stat_blc20[2]: B blc, range 0~0xfffff
>> + *   awb_stat_blc20[3]: Gb blc, range 0~0xfffff
>> + * @awb_stat_gain10: Gain in AWB statistic
>> + *   awb_stat_gain10[0]: Gr gain, range 0~1023
>> + *   awb_stat_gain10[1]: R gain, range 0~1023
>> + *   awb_stat_gain10[2]: B gain, range 0~1023
>> + *   awb_stat_gain10[3]: Gb gain, range 0~1023
>> + * @awb_stat_satur_low: AWB statistic under-saturation threshold
>> + *   value: range 0~65535
>> + * @awb_stat_satur_high: AWB statistic over-saturation threshold
>> + *   value: range 0~65535
>> + */
>> +struct wb_luma_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u32 awb_stat_blc20[AWB_STAT_BLC20_NUM];
>> +     u32 awb_stat_gain10[AWB_STAT_GAIN10_NUM];
>> +     u32 awb_stat_satur_low;
>> +     u32 awb_stat_satur_high;
>> +};
>> +
>> +/**
>> + * struct wb_triangle_cfg - White Balance Triangle
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @awb_stat_satur_vald: AWB statistic over saturation control
>> + *   value: 0: disable, 1: enable
>> + * @awb_stat_rg_min: min value of r/g
>> + *   value: 0~4095
>> + * @awb_stat_rg_max: max value of r/g
>> + *   value: 0~4095
>> + * @awb_stat_bg_min: min value of b/g
>> + *   value: 0~4095
>> + * @awb_stat_bg_max: max value of b/g
>> + *   value: 0~4095
>> + * @awb_stat_rg_low: low value of r/g
>> + *   value: 0~4095
>> + * @awb_stat_rg_high: high value of r/g
>> + *   value: 0~4095
>> + * @awb_stat_bg_low: low value of b/g
>> + *   value: 0~4095
>> + * @awb_stat_bg_high: high value of b/g
>> + *   value: 0~4095
>> + */
>> +struct wb_triangle_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u32 awb_stat_satur_vald;
>> +     u32 awb_stat_rg_min;
>> +     u32 awb_stat_rg_max;
>> +     u32 awb_stat_bg_min;
>> +     u32 awb_stat_bg_max;
>> +     u32 awb_stat_rg_low;
>> +     u32 awb_stat_rg_high;
>> +     u32 awb_stat_bg_low;
>> +     u32 awb_stat_bg_high;
>> +};
>> +
>> +/**
>> + * struct awb_stats_cfg - AWB statistics configuration
>> + *
>> + * This structure contains AWB statistics control information.
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @awb_stat_switch: the switch of AWB statistics
>> + *   value: 0~7
>> + * @awb_stat_blk_weight: Array of weights for AWB statistics blocks
>> + *   value: 0~15
>> + */
>> +struct awb_stats_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u8 awb_stat_switch;
>> +     u32 awb_stat_blk_weight[AWB_BLOCK_WT_NUM];
>> +};
>> +
>> +/**
>> + * struct ae_stats_cfg - AE statistics configuration
>> + *
>> + * This structure contains AE statistics control information.
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @ae_stat_switch: the switch of AE statistics
>> + *   value: 0~3
>> + * @ae_stat_blk_weight: Array of weights for AE statistics blocks
>> + *   value: 0~15
>> + */
>> +struct ae_stats_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u8 ae_stat_switch;
>> +     u32 ae_stat_blk_weight[AE_BLOCK_WT_NUM];
>> +};
>> +
>> +/**
>> + * struct af_stats_cfg - AF statistics configuration
>> + *
>> + * This structure contains AF statistics control information.
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @af_stat_switch: the switch of AF statistics
>> + *   value: 0~3
>> + */
>> +struct af_stats_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u8 af_stat_switch;
>> +};
>> +
>> +/**
>> + * struct pst_gamma_cfg - Post gamma configuration
>> + *
>> + * This structure contains post gamma parameters
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @pst_gamma_lut: LUT for P-Stitch gamma
>> + *   value: 0~65535
>> + */
>> +struct pst_gamma_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u32 pst_gamma_lut[GAMMA_LUT_GROUP_NUM][GAMMA_LUT_POINT_NUM];
>> +};
>> +
>> +/**
>> + * struct dmsc_cfg - Demosaic configuration
>> + *
>> + * This structure contains demosaic parameters
>> + *
>> + * @header: The C3 ISP parameters block header
>> + */
>> +struct dmsc_cfg {
>> +     struct c3_isp_param_block_header header;
>> +};
>> +
>> +/**
>> + * struct ccm_cfg - ISP CCM configuration
>> + *
>> + * This structure holds the parameters for configuring the CCM,
>> + * which is used for color correction.
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @ccm_4x3matrix: A 3x4 matrix used for color correction
>> + *   value: 0~8191
>> + */
>> +struct ccm_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u32 ccm_4x3matrix[3][4];
>> +};
>> +
>> +/**
>> + * struct csc_cfg - ISP Color Space Conversion configuration
>> + *
>> + * This structure contains settings for color space conversion.
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @cm0_offset_inp: Input offset values for the 0-order color matrix
>> + *   value: 0~8191
>> + * @cm0_offset_oup: Output offset values for the 0-order color matrix
>> + *   value: 0~8191
>> + * @cm0_3x3mtrx_rs: matrix right shift for cm0
>> + *   value: 0~3
>> + * @cm0_3x3matrix: A 3x3 matrix used for the color matrix
>> + *   value: 0~8191
>> + */
>> +struct csc_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u32 cm0_offset_inp[3];
>> +     u32 cm0_offset_oup[3];
>> +     u32 cm0_3x3mtrx_rs;
>> +     u32 cm0_3x3matrix[3][3];
>> +};
>> +
>> +/**
>> + * struct blc_cfg - ISP Black Level Correction (BLC) configuration
>> + *
>> + * This structure holds the parameters for BLC in image processing.
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @fe_bl_ofst: Array of front-end BLC offsets for each color each channels
>> + *   fe_bl_ofst[0]: Gr blc offset, range 0~0x1fffff
>> + *   fe_bl_ofst[1]: R blc offset, range 0~0x1fffff
>> + *   fe_bl_ofst[2]: B blc offset, range 0~0x1fffff
>> + *   fe_bl_ofst[3]: Gb blc offset, range 0~0x1fffff
>> + *   fe_bl_ofst[4]: Ir blc offset, range 0~0x1fffff
>> + * @blc_ofst: Array of LSWB BLC offsets
>> + *   blc_ofst[0]: Gr blc offset, 16 bits float
>> + *   blc_ofst[1]: R blc offset, 16 bits float
>> + *   blc_ofst[2]: B blc offset, 16 bits float
>> + *   blc_ofst[3]: Gb blc offset, 16 bits float
>> + *   blc_ofst[4]: Ir blc offset, 16 bits float
>> + */
>> +struct blc_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u32 fe_bl_ofst[BLC_OFFSET_NUM];
>> +     u32 blc_ofst[BLC_OFFSET_NUM];
>> +};
>> +
>> +/**
>> + * define C3_ISP_PARAMS_MAX_SIZE - Maximum size of all C3 ISP Parameters
>> + *
>> + * Though the parameters for the C3 ISP are passed as optional blocks, the
>> + * driver still needs to know the absolute maximum size so that it can allocate
>> + * a buffer sized appropriately to accommodate userspace attempting to set all
>> + * possible parameters in a single frame.
>> + */
>> +#define C3_ISP_PARAMS_MAX_SIZE                 \
>> +     (sizeof(struct wb_change_cfg) +        \
>> +     sizeof(struct wb_luma_cfg)   +         \
>> +     sizeof(struct wb_triangle_cfg) +       \
>> +     sizeof(struct awb_stats_cfg) +          \
>> +     sizeof(struct ae_stats_cfg) +           \
>> +     sizeof(struct af_stats_cfg) +           \
>> +     sizeof(struct pst_gamma_cfg) +         \
>> +     sizeof(struct dmsc_cfg) +              \
>> +     sizeof(struct ccm_cfg) +               \
>> +     sizeof(struct csc_cfg) +               \
>> +     sizeof(struct blc_cfg))
>> +
>> +/**
>> + * struct c3_isp_params_buffer - C3 ISP configuration parameters
>> + *
>> + * This struct contains the configuration parameters of the C3 ISP
>> + * algorithms, serialized by userspace into an opaque data buffer. Each
>> + * configuration parameter block is represented by a block-specific structure
>> + * which contains a :c:type:`c3_isp_param_block_header` entry as first
>> + * member. Userspace populates the @data buffer with configuration parameters
>> + * for the blocks that it intends to configure. As a consequence, the data
>> + * buffer effective size changes according to the number of ISP blocks that
>> + * userspace intends to configure.
>> + *
>> + * The parameters buffer is versioned by the @version field to allow modifying
>> + * and extending its definition. Userspace should populate the @version field to
>> + * inform the driver about the version it intends to use. The driver will parse
>> + * and handle the @data buffer according to the data layout specific to the
>> + * indicated revision and return an error if the desired revision is not
>> + * supported.
>> + *
>> + * For each ISP block that userspace wants to configure, a block-specific
>> + * structure is appended to the @data buffer, one after the other without gaps
>> + * in between nor overlaps. Userspace shall populate the @total_size field with
>> + * the effective size, in bytes, of the @data buffer.
>> + *
>> + * The expected memory layout of the parameters buffer is::
>> + *
>> + *   +-------------------- struct c3_isp_params_buffer ------------------+
>> + *   | version = C3_ISP_PARAM_BUFFER_V0;                                   |
>> + *   | total_size = sizeof(sizeof(struct wb_change_cfg))                   |
>> + *   |              sizeof(sizeof(struct wb_luma_cfg));                    |
>> + *   | +------------------------- data  ---------------------------------+ |
>> + *   | | +------------------ struct wb_change_cfg) --------------------+ | |
>> + *   | | | +---------  struct c3_isp_param_block_header header  -----+ | | |
>> + *   | | | | type = C3_ISP_PARAM_BLOCK_WB_CHANGE;                    | | | |
>> + *   | | | | enabled = true;                                            | | | |
>> + *   | | | | size =                                                  | | | |
>> + *   | | | |    sizeof(struct c3_isp_param_block_header header);     | | | |
>> + *   | | | +---------------------------------------------------------+ | | |
>> + *   | | | wb_gain[5] = ...;                                           | | |
>> + *   | | | wb_limit[5] = ...;                                          | | |
>> + *   | | | ae_gain_grbgi[5] = ...;                                     | | |
>> + *   | | | ae_bl12_grbgi[5] = ...;                                     | | |
>> + *   | | +------------------ struct wb_luma_cfg -----------------------+ | |
>> + *   | | | +---------- struct c3_isp_param_block_header header ------+ | | |
>> + *   | | | | type = C3_ISP_PARAM_BLOCK_WB_LUMA;                      | | | |
>> + *   | | | | enabled = true;                                            | | | |
>> + *   | | | | size = sizeof(struct wb_luma_cfg);                      | | | |
>> + *   | | | +---------------------------------------------------------+ | | |
>> + *   | | | awb_stat_blc20[4] = ...;                                    | | |
>> + *   | | | awb_stat_gain10[4] = ...;                                   | | |
>> + *   | | | awb_stat_satur_low = ...;                                   | | |
>> + *   | | | awb_stat_satur_high = ...;                                  | | |
>> + *   | | +-------------------------------------------------------------+ | |
>> + *   | +-----------------------------------------------------------------+ |
>> + *   +---------------------------------------------------------------------+
>> + *
>> + * @version: The C3 ISP parameters buffer version
>> + * @total_size: The C3 ISP configuration data effective size,
>> + *           excluding this header
>> + * @data: The C3 ISP configuration blocks data
>> + */
>> +struct c3_isp_params_buffer {
>> +     enum c3_isp_param_buffer_version version;
>> +     size_t total_size;
>> +     u8 data[C3_ISP_PARAMS_MAX_SIZE];
>> +};
>> +
>> +#endif
>>
>> --
>> 2.46.1
>>
>>
>>

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

* Re: [PATCH v3 7/9] media: platform: Add c3 ISP driver
  2024-11-07 16:15     ` Jacopo Mondi
@ 2024-11-08 12:38       ` Keke Li
  0 siblings, 0 replies; 37+ messages in thread
From: Keke Li @ 2024-11-08 12:38 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi Jacopo

Thanks very much for your reply.

On 2024/11/8 00:15, Jacopo Mondi wrote:
> [ EXTERNAL EMAIL ]
>
> Hi again Keke
>
> On Thu, Nov 07, 2024 at 05:03:45PM +0100, Jacopo Mondi wrote:
>> Hi Keke
>>
>>     a first pass of review without going into details about the
>> ISP parameters and stats but mostly on architecture.
>>
>> On Wed, Sep 18, 2024 at 02:07:18PM +0800, Keke Li via B4 Relay wrote:
>>> From: Keke Li <keke.li@amlogic.com>
>>>
>>> The C3 ISP supports multi-camera and muti-exposure
>>> high dynamic range (HDR). It brings together some
>>> advanced imaging technologies to provide good image quality.
>>> This driver mainly responsible for driving ISP pipeline
>>> to process raw image.
>>>
>>> Signed-off-by: Keke Li <keke.li@amlogic.com>
>>> ---
>>>   drivers/media/platform/amlogic/Kconfig             |   1 +
>>>   drivers/media/platform/amlogic/Makefile            |   1 +
>>>   drivers/media/platform/amlogic/c3-isp/Kconfig      |  17 +
>>>   drivers/media/platform/amlogic/c3-isp/Makefile     |  10 +
>>>   .../media/platform/amlogic/c3-isp/c3-isp-capture.c | 759 ++++++++++++++++++
>>>   .../media/platform/amlogic/c3-isp/c3-isp-common.h  | 327 ++++++++
>>>   .../media/platform/amlogic/c3-isp/c3-isp-core.c    | 675 ++++++++++++++++
>>>   drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c | 486 ++++++++++++
>>>   .../media/platform/amlogic/c3-isp/c3-isp-params.c  | 857 +++++++++++++++++++++
>>>   .../media/platform/amlogic/c3-isp/c3-isp-regs.h    | 683 ++++++++++++++++
>>>   .../media/platform/amlogic/c3-isp/c3-isp-resizer.c | 768 ++++++++++++++++++
>>>   .../media/platform/amlogic/c3-isp/c3-isp-stats.c   | 488 ++++++++++++
>>>   .../amlogic/c3-isp/include/uapi/c3-isp-config.h    | 537 +++++++++++++
> Let me add a link to the conversation I had when I introduced
> extensible format in RkISP1 about the alignment and padding of uAPI
> types:
> https://lore.kernel.org/all/20240724085004.82694-1-jacopo.mondi@ideasonboard.com/T/#m5333e7b6ab4230bb32efa063ab6fd73a529fdc29
>
> I think the takeaway is:
> - Make sure there are no holes, and if any replace them with
>    reserved[] bytes
> - Preferably align to 8-bytes


OK, will add reserved[] to align.

>> This really should be in include/uapi/linux/media/amlogic
>> so that it is made available to userspace.
>>
>>>   13 files changed, 5609 insertions(+)
>>>
>>> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
>>> index df09717b28d0..ebda6b7edc2d 100644
>>> --- a/drivers/media/platform/amlogic/Kconfig
>>> +++ b/drivers/media/platform/amlogic/Kconfig
>>> @@ -2,6 +2,7 @@
>>>
>>>   comment "Amlogic media platform drivers"
>>>
>>> +source "drivers/media/platform/amlogic/c3-isp/Kconfig"
>>>   source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
>>>   source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>> I start wondering if this shouldn't rather be organized in an
>> drivers/media/platform/amlogic/c3/ folder with an
>> drivers/media/platform/amlogic/c3/isp/ subfolder
>>
>> Do you know if any of the csi-2-rx, adapter or ISP can
>> be used in other SoCs ?
>>
>>>   source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
>>> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
>>> index b370154b090c..d0d9363d4d8d 100644
>>> --- a/drivers/media/platform/amlogic/Makefile
>>> +++ b/drivers/media/platform/amlogic/Makefile
>>> @@ -1,5 +1,6 @@
>>>   # SPDX-License-Identifier: GPL-2.0-only
>>>
>>> +obj-y += c3-isp/
>>>   obj-y += c3-mipi-adapter/
>>>   obj-y += c3-mipi-csi2/
>>>   obj-y += meson-ge2d/
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/Kconfig b/drivers/media/platform/amlogic/c3-isp/Kconfig
>>> new file mode 100644
>>> index 000000000000..e317c1e81750
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/Kconfig
>>> @@ -0,0 +1,17 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +
>>> +config VIDEO_C3_ISP
>>> +   tristate "Amlogic C3 Image Signal Processor (ISP) driver"
>>> +   depends on ARCH_MESON || COMPILE_TEST
>>> +   depends on VIDEO_DEV
>>> +   depends on OF
>>> +   select MEDIA_CONTROLLER
>>> +   select V4L2_FWNODE
>>> +   select VIDEO_V4L2_SUBDEV_API
>>> +   select VIDEOBUF2_DMA_CONTIG
>>> +   help
>>> +     Video4Linux2 driver for Amlogic C3 ISP pipeline.
>>> +     C3 ISP pipeline mainly for processing raw image
>>            The C3 ISP is used for processing raw images
>>> +     and output result to memory.
>> Or something similar, but "C3 ISP pipeline mainly for processing"
>> doesn't sound right to be
>>
>>> +
>>> +     To compile this driver as a module choose m here.
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/Makefile b/drivers/media/platform/amlogic/c3-isp/Makefile
>>> new file mode 100644
>>> index 000000000000..b1b064170b57
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/Makefile
>>> @@ -0,0 +1,10 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +
>>> +c3-isp-objs := c3-isp-dev.o \
>>> +          c3-isp-params.o \
>>> +          c3-isp-stats.o \
>>> +          c3-isp-capture.o \
>>> +          c3-isp-core.o \
>>> +          c3-isp-resizer.o
>>> +
>>> +obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
>>> new file mode 100644
>>> index 000000000000..ee9a7a17a203
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
>>> @@ -0,0 +1,759 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/cleanup.h>
>>> +#include <linux/pm_runtime.h>
>>> +
>>> +#include <media/v4l2-ctrls.h>
>>> +#include <media/v4l2-event.h>
>>> +#include <media/v4l2-ioctl.h>
>>> +#include <media/v4l2-mc.h>
>>> +#include <media/videobuf2-dma-contig.h>
>>> +
>>> +#include "c3-isp-common.h"
>>> +#include "c3-isp-regs.h"
>>> +
>>> +static const struct c3_isp_capture_format cap_formats[] = {
>>> +   {
>>> +           .mbus_code = MEDIA_BUS_FMT_Y8_1X8,
>>> +           .fourcc = V4L2_PIX_FMT_GREY,
>>> +           .depth = 8,
>>> +   },
>>> +   {
>>> +           .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
>> Does the 5X8 version represents the format on the internal bus between
>> the resizers and the capture device ?
>>
>> How does format propagation work from the ISP to the resizers and the
>> capture devices ? I mean, is there an internal bus where the number of
>> samples (5X8 vs 2X8) changes depending on the output format ?
>>
>>> +           .fourcc = V4L2_PIX_FMT_NV12,
>>> +           .depth = 12,
>>> +   }, {
>>> +           .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
>>> +           .fourcc = V4L2_PIX_FMT_NV21,
>>> +           .depth = 12,
>>> +   }, {
>>> +           .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
>>> +           .fourcc = V4L2_PIX_FMT_NV16,
>>> +           .depth = 16,
>>> +   }, {
>>> +           .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
>>> +           .fourcc = V4L2_PIX_FMT_NV61,
>>> +           .depth = 16,
>>> +   },
>>> +};
>>> +
>>> +/* Hardware configuration */
>>> +
>>> +/* Set the address of wrmifx3(write memory interface) */
>>> +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
>>> +{
>>> +   struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>>> +   struct c3_isp_vb2_buffer *buff = cap->buff;
>>> +   u32 offset;
>>> +
>>> +   c3_isp_write(cap->isp,
>>> +                C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
>>> +                WRMIFX3_CH0_BADDR(buff->paddr));
>>> +
>>> +   if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
>>> +       pix->pixelformat == V4L2_PIX_FMT_NV21 ||
>>> +       pix->pixelformat == V4L2_PIX_FMT_NV16 ||
>>> +       pix->pixelformat == V4L2_PIX_FMT_NV61) {
>>> +           offset = pix->width * pix->height;
>>> +           c3_isp_write(cap->isp,
>>> +                        C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
>>> +                        WRMIFX3_CH1_BADDR(buff->paddr + offset));
>>> +   }
>>> +}
>>> +
>>> +static void c3_isp_cap_output_size(struct c3_isp_capture *cap)
>>> +{
>>> +   struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>>> +
>>> +   c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
>>> +                      DISP_OUT_VSIZE_MASK, pix->height);
>>> +   c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
>>> +                      DISP_OUT_HSIZE_MASK, pix->width << DISP_OUT_HSIZE_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
>>> +{
>>> +   struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>>> +   u32 stride;
>>> +
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>> +                      WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
>>> +
>>> +   /* Grey has 1 plane*/
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>> +                      WRMIFX3_FMT_MTX_PLANE_MASK,
>>> +                      WRMIFX3_FMT_MTX_PLANE_X1 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
>>> +
>>> +   /* Set Y only as output format */
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>> +                      WRMIFX3_FMT_MODE_OUT_MASK,
>>> +                      WRMIFX3_FMT_MODE_OUT_Y_ONLY << WRMIFX3_FMT_MODE_OUT_SHIFT);
>>> +
>>> +   /* The unit of stride is 128 bits */
>>> +   stride = DIV_ROUND_UP(fmt->width * 8, 128);
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>>> +                      WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
>>> +
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>>> +                      WRMIFX3_CH0_PIX_BITS_MODE_MASK,
>>> +                      WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, u32 swap_uv)
>>> +{
>>> +   struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>>> +   u32 stride;
>>> +
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>> +                      WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>> +                      WRMIFX3_FMT_MTX_UV_SWAP_MASK,
>>> +                      swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
>>> +
>>> +   /* NV12 or NV21 has 2 planes*/
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>> +                      WRMIFX3_FMT_MTX_PLANE_MASK,
>>> +                      WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
>>> +
>>> +   /* Set YUV420 as output format */
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>> +                      WRMIFX3_FMT_MODE_OUT_MASK,
>>> +                      WRMIFX3_FMT_MODE_OUT_YUV420 << WRMIFX3_FMT_MODE_OUT_SHIFT);
>>> +
>>> +   /* The unit of stride is 128 bits */
>>> +   stride = DIV_ROUND_UP(fmt->width * 8, 128);
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>>> +                      WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
>>> +                      WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
>>> +
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>>> +                      WRMIFX3_CH0_PIX_BITS_MODE_MASK,
>>> +                      WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
>>> +                      WRMIFX3_CH1_PIX_BITS_MODE_MASK,
>>> +                      WRMIFX3_CH1_PIX_BITS_16BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, u32 swap_uv)
>>> +{
>>> +   struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>>> +   u32 stride;
>>> +
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>> +                      WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_16BIT);
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>> +                      WRMIFX3_FMT_MTX_UV_SWAP_MASK,
>>> +                      swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
>>> +
>>> +   /* NV16 or NV61 has 2 planes*/
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>> +                      WRMIFX3_FMT_MTX_PLANE_MASK,
>>> +                      WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
>>> +
>>> +   /* Set YUV422 as output format */
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>> +                      WRMIFX3_FMT_MODE_OUT_MASK,
>>> +                      WRMIFX3_FMT_MODE_OUT_YUV422 << WRMIFX3_FMT_MODE_OUT_SHIFT);
>>> +
>>> +   /* The unit of stride is 128 bits */
>>> +   stride = DIV_ROUND_UP(fmt->width * 16, 128);
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>>> +                      WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
>>> +                      WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
>>> +
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>>> +                      WRMIFX3_CH0_PIX_BITS_MODE_MASK,
>>> +                      WRMIFX3_CH0_PIX_BITS_16BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
>>> +                      WRMIFX3_CH1_PIX_BITS_MODE_MASK,
>>> +                      WRMIFX3_CH1_PIX_BITS_32BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
>>> +{
>>> +   struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>>> +
>>> +   if (fmt->pixelformat == V4L2_PIX_FMT_GREY) {
>>> +           c3_isp_cap_wrmifx3_grey(cap);
>>> +   } else if (fmt->pixelformat == V4L2_PIX_FMT_NV12) {
>>> +           c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
>>> +   } else if (fmt->pixelformat == V4L2_PIX_FMT_NV21) {
>>> +           c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
>>> +   } else if (fmt->pixelformat == V4L2_PIX_FMT_NV16) {
>>> +           c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
>>> +   } else if (fmt->pixelformat == V4L2_PIX_FMT_NV61) {
>>> +           c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
>>> +   } else {
>>> +           dev_err(cap->isp->dev, "Invalid pixel format: 0x%x\n", fmt->pixelformat);
>>> +           return;
>>> +   }
>>> +
>>> +   c3_isp_write(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id),
>>> +                WRMIFX3_FMT_HSIZE(fmt->width) | WRMIFX3_FMT_VSIZE(fmt->height));
>>> +
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
>>> +                      WRMIFX3_WIN_LUMA_HEND_MASK,
>>> +                      WRMIFX3_WIN_LUMA_HEND(fmt->width) << WRMIFX3_WIN_LUMA_HEND_SHIFT);
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
>>> +                      WRMIFX3_WIN_LUMA_VEND_MASK,
>>> +                      WRMIFX3_WIN_LUMA_VEND(fmt->height) << WRMIFX3_WIN_LUMA_VEND_SHIFT);
>>> +
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_HSIZE, cap->id),
>>> +                      WRMIFX3_CROP_HEND_MASK,
>>> +                      WRMIFX3_CROP_HEND(fmt->width) << WRMIFX3_CROP_HEND_SHIFT);
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_VSIZE, cap->id),
>>> +                      WRMIFX3_CROP_VEND_MASK,
>>> +                      WRMIFX3_CROP_VEND(fmt->height) << WRMIFX3_CROP_VEND_SHIFT);
>>> +
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
>>> +                      WRMIFX3_WIN_CHROM_HEND_MASK,
>>> +                      WRMIFX3_WIN_CHROM_HEND(fmt->width) << WRMIFX3_WIN_CHROM_HEND_SHIFT);
>>> +   c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
>>> +                      WRMIFX3_WIN_CHROM_VEND_MASK,
>>> +                      WRMIFX3_WIN_CHROM_VEND(fmt->height) << WRMIFX3_WIN_CHROM_VEND_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)
>> This function is called from two locations, one with the buff_lock
>> held, the other one without, if I'm not mistaken.
>>
>>> +{
>>> +   cap->buff = list_first_entry_or_null(&cap->pending,
>>> +                                        struct c3_isp_vb2_buffer, list);
>>> +   if (cap->buff) {
>>> +           c3_isp_cap_wrmifx3_buff(cap);
>>> +           list_del(&cap->buff->list);
>>> +   }
>>> +}
>>> +
>>> +static void c3_isp_cap_start(struct c3_isp_capture *cap)
>>> +{
>>> +   c3_isp_cap_cfg_buff(cap);
>>> +
>>> +   c3_isp_cap_output_size(cap);
>>> +   c3_isp_cap_wrmifx3_size(cap);
>>> +
>>> +   c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN,
>>> +                      TOP_WRMIF_EN(cap->id), TOP_WRMIF_EN(cap->id));
>>> +
>>> +   cap->is_streaming = true;
>>> +}
>>> +
>>> +static void c3_isp_cap_stop(struct c3_isp_capture *cap)
>>> +{
>>> +   cap->is_streaming = false;
>>> +
>>> +   c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, TOP_WRMIF_EN(cap->id), 0);
>>> +}
>>> +
>>> +static int c3_isp_cap_done(struct c3_isp_capture *cap)
>>> +{
>>> +   struct c3_isp_vb2_buffer *buff = cap->buff;
>>> +   unsigned long flags;
>>> +
>>> +   if (!cap->is_streaming)
>>> +           return -EINVAL;
>> Is this an error condition or 0 should be returnes ?
>>
>>> +
>>> +   spin_lock_irqsave(&cap->buff_lock, flags);
>>> +
>>> +   if (buff) {
>>> +           buff->vb.sequence = cap->isp->frm_sequence;
>>> +           buff->vb.vb2_buf.timestamp = ktime_get();
>>> +           buff->vb.field = V4L2_FIELD_NONE;
>>> +           vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
>>> +   }
>>> +
>>> +   c3_isp_cap_cfg_buff(cap);
>>> +
>>> +   spin_unlock_irqrestore(&cap->buff_lock, flags);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +/* V4L2 video operations */
>>> +
>>> +static const struct c3_isp_capture_format
>>> +*c3_cap_find_fmt(u32 fourcc)
>>> +{
>>> +   unsigned int i;
>>> +
>>> +   for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
>>> +           if (cap_formats[i].fourcc == fourcc)
>>> +                   return &cap_formats[i];
>>> +   }
>>> +
>>> +   return NULL;
>>> +}
>>> +
>>> +static void c3_cap_try_fmt(struct c3_isp_capture *cap,
>>> +                      struct v4l2_pix_format *pix)
>>> +{
>>> +   const struct c3_isp_capture_format *fmt;
>>> +
>>> +   fmt = c3_cap_find_fmt(pix->pixelformat);
>>> +   if (!fmt)
>>> +           fmt = &cap_formats[0];
>>> +
>>> +   pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>>> +   pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>>> +   pix->pixelformat = fmt->fourcc;
>>> +   pix->field = V4L2_FIELD_NONE;
>>> +   pix->colorspace = V4L2_COLORSPACE_SRGB;
>>> +   pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>> +   pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>>> +
>>> +   /* ISP hardware requires 16 bytes alignment */
>>> +   pix->bytesperline = ALIGN(pix->width, 16);
>>> +   pix->sizeimage = pix->bytesperline * pix->height * fmt->depth / 8;
>>> +}
>>> +
>>> +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
>>> +                                 enum vb2_buffer_state state)
>>> +{
>>> +   unsigned long flags;
>>> +   struct c3_isp_vb2_buffer *buff;
>>> +
>>> +   spin_lock_irqsave(&cap->buff_lock, flags);
>>> +
>>> +   if (cap->buff) {
>>> +           vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
>>> +           cap->buff = NULL;
>>> +   }
>>> +
>>> +   while (!list_empty(&cap->pending)) {
>>> +           buff = list_first_entry(&cap->pending,
>>> +                                   struct c3_isp_vb2_buffer, list);
>>> +           list_del(&buff->list);
>>> +           vb2_buffer_done(&buff->vb.vb2_buf, state);
>>> +   }
>>> +
>>> +   spin_unlock_irqrestore(&cap->buff_lock, flags);
>>> +}
>>> +
>>> +static int c3_isp_cap_querycap(struct file *file, void *fh,
>>> +                          struct v4l2_capability *cap)
>>> +{
>>> +   strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>>> +   strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_cap_enum_fmt(struct file *file, void *fh,
>>> +                          struct v4l2_fmtdesc *f)
>>> +{
>>> +   const struct c3_isp_capture_format *fmt;
>>> +   unsigned int index = 0;
>>> +   unsigned int i;
>>> +
>>> +   if (!f->mbus_code) {
>>> +           if (f->index >= ARRAY_SIZE(cap_formats))
>>> +                   return -EINVAL;
>>> +
>>> +           fmt = &cap_formats[f->index];
>>> +           f->pixelformat = fmt->fourcc;
>>> +           return 0;
>>> +   }
>>> +
>>> +   for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
>>> +           fmt = &cap_formats[i];
>>> +           if (f->mbus_code != fmt->mbus_code)
>>> +                   continue;
>>> +
>>> +           if (index++ == f->index) {
>>> +                   f->pixelformat = cap_formats[i].fourcc;
>>> +                   return 0;
>>> +           }
>>> +   }
>>> +
>>> +   return -EINVAL;
>>> +}
>>> +
>>> +static int c3_isp_cap_g_fmt(struct file *file, void *fh,
>>> +                       struct v4l2_format *f)
>>> +{
>>> +   struct c3_isp_capture *cap = video_drvdata(file);
>>> +
>>> +   f->fmt.pix = cap->vfmt.fmt.pix;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_cap_s_fmt(struct file *file, void *fh,
>>> +                       struct v4l2_format *f)
>>> +{
>>> +   struct c3_isp_capture *cap = video_drvdata(file);
>>> +
>>> +   c3_cap_try_fmt(cap, &f->fmt.pix);
>>> +   cap->vfmt = *f;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_cap_try_fmt(struct file *file, void *fh,
>>> +                         struct v4l2_format *f)
>>> +{
>>> +   struct c3_isp_capture *cap = video_drvdata(file);
>>> +
>>> +   c3_cap_try_fmt(cap, &f->fmt.pix);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
>>> +                              struct v4l2_frmsizeenum *fsize)
>>> +{
>>> +   const struct c3_isp_capture_format *fmt;
>>> +
>>> +   if (fsize->index)
>>> +           return -EINVAL;
>>> +
>>> +   fmt = c3_cap_find_fmt(fsize->pixel_format);
>>> +   if (!fmt)
>>> +           return -EINVAL;
>>> +
>>> +   fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
>>> +   fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
>>> +   fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
>>> +   fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
>>> +   fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
>>> +   fsize->stepwise.step_width = 2;
>>> +   fsize->stepwise.step_height = 2;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
>>> +   .vidioc_querycap                = c3_isp_cap_querycap,
>>> +   .vidioc_enum_fmt_vid_cap        = c3_isp_cap_enum_fmt,
>>> +   .vidioc_g_fmt_vid_cap           = c3_isp_cap_g_fmt,
>>> +   .vidioc_s_fmt_vid_cap           = c3_isp_cap_s_fmt,
>>> +   .vidioc_try_fmt_vid_cap         = c3_isp_cap_try_fmt,
>>> +   .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>>> +   .vidioc_querybuf                = vb2_ioctl_querybuf,
>>> +   .vidioc_qbuf                    = vb2_ioctl_qbuf,
>>> +   .vidioc_expbuf                  = vb2_ioctl_expbuf,
>>> +   .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>>> +   .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>>> +   .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>>> +   .vidioc_streamon                = vb2_ioctl_streamon,
>>> +   .vidioc_streamoff               = vb2_ioctl_streamoff,
>>> +   .vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
>>> +   .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>>> +   .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>>> +};
>>> +
>>> +static const struct v4l2_file_operations isp_cap_v4l2_fops = {
>>> +   .open = v4l2_fh_open,
>>> +   .release = vb2_fop_release,
>>> +   .poll = vb2_fop_poll,
>>> +   .unlocked_ioctl = video_ioctl2,
>>> +   .mmap = vb2_fop_mmap,
>>> +};
>>> +
>>> +static int c3_isp_cap_link_validate(struct media_link *link)
>>> +{
>>> +   struct video_device *vdev =
>>> +           media_entity_to_video_device(link->sink->entity);
>>> +   struct v4l2_subdev *sd =
>>> +           media_entity_to_v4l2_subdev(link->source->entity);
>>> +   struct c3_isp_capture *cap = video_get_drvdata(vdev);
>>> +   struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
>>> +   struct v4l2_subdev_format src_fmt = {
>>> +           .which = V4L2_SUBDEV_FORMAT_ACTIVE,
>>> +           .pad = link->source->index,
>>> +   };
>>> +   const struct c3_isp_capture_format *cap_fmt =
>>> +                           c3_cap_find_fmt(pix_fmt->pixelformat);
>>> +   int ret;
>>> +
>>> +   ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   if (src_fmt.format.width != pix_fmt->width ||
>>> +       src_fmt.format.height != pix_fmt->height ||
>>> +       src_fmt.format.code != cap_fmt->mbus_code) {
>>> +           dev_err(cap->isp->dev,
>>> +                   "link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n",
>>> +                   link->source->entity->name, link->source->index,
>>> +                   link->sink->entity->name, link->sink->index,
>>> +                   src_fmt.format.code, src_fmt.format.width,
>>> +                   src_fmt.format.height, cap_fmt->mbus_code,
>>> +                   pix_fmt->width, pix_fmt->height);
>>> +
>>> +           return -EPIPE;
>>> +   };
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static const struct media_entity_operations isp_cap_entity_ops = {
>>> +   .link_validate = c3_isp_cap_link_validate,
>>> +};
>>> +
>>> +static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
>>> +                             unsigned int *num_buffers,
>>> +                             unsigned int *num_planes,
>>> +                             unsigned int sizes[],
>>> +                             struct device *alloc_devs[])
>>> +{
>>> +   struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>>> +   struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>>> +
>>> +   if (*num_planes) {
>>> +           if (*num_planes != 1)
>>> +                   return -EINVAL;
>>> +
>>> +           if (sizes[0] < pix->sizeimage)
>>> +                   return -EINVAL;
>>> +   } else {
>>> +           *num_planes = 1;
>>> +           sizes[0] = pix->sizeimage;
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
>>> +{
>>> +   struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>> +   struct c3_isp_vb2_buffer *buf =
>>> +                   container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>> +   struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>>> +   unsigned long flags;
>>> +
>>> +   spin_lock_irqsave(&cap->buff_lock, flags);
>>> +
>>> +   list_add_tail(&buf->list, &cap->pending);
>>> +
>>> +   spin_unlock_irqrestore(&cap->buff_lock, flags);
>>> +}
>>> +
>>> +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
>>> +{
>>> +   struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>>> +   unsigned int size = cap->vfmt.fmt.pix.sizeimage;
>>> +
>>> +   if (vb2_plane_size(vb, 0) < size) {
>>> +           dev_err(cap->isp->dev,
>>> +                   "User buffer too small (%ld < %u)\n",
>>> +                   vb2_plane_size(vb, 0), size);
>>> +           return -EINVAL;
>>> +   }
>>> +
>>> +   vb2_set_plane_payload(vb, 0, size);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
>>> +{
>>> +   struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>> +   struct c3_isp_vb2_buffer *buf =
>>> +           container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>> +   struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>>> +
>>> +   buf->vaddr = vb2_plane_vaddr(vb, 0);
>>> +   buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>>> +
>>> +   memset(buf->vaddr, 0, cap->vfmt.fmt.pix.sizeimage);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
>>> +                                 unsigned int count)
>>> +{
>>> +   struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>>> +   int ret;
>>> +
>>> +   guard(mutex)(&cap->isp->lock);
>>> +
>>> +   ret = pm_runtime_resume_and_get(cap->isp->dev);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
>>> +   if (ret) {
>>> +           dev_err(cap->isp->dev,
>>> +                   "Failed to start cap%u pipeline: %d\n", cap->id, ret);
>>> +           goto err_pm_put;
>>> +   }
>>> +
>>> +   if (c3_isp_pipeline_ready(cap->isp)) {
>>> +           ret = v4l2_subdev_enable_streams(&cap->isp->core.sd,
>>> +                                            C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>> +                                            BIT(0));
>>> +           if (ret)
>>> +                   goto err_pipeline_stop;
>>> +   }
>>> +
>>> +   c3_isp_rsz_start(cap->rsz);
>>> +   c3_isp_cap_start(cap);
>>> +
>>> +   return 0;
>>> +
>>> +err_pipeline_stop:
>>> +   video_device_pipeline_stop(&cap->vdev);
>>> +err_pm_put:
>>> +   pm_runtime_put(cap->isp->dev);
>>> +   c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
>>> +   return ret;
>>> +}
>>> +
>>> +static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
>>> +{
>>> +   struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>>> +
>>> +   guard(mutex)(&cap->isp->lock);
>>> +
>>> +   c3_isp_cap_stop(cap);
>>> +   c3_isp_rsz_stop(cap->rsz);
>>> +   c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
>>> +
>>> +   if (cap->isp->pipe.start_count == 1)
>>> +           v4l2_subdev_disable_streams(&cap->isp->core.sd,
>>> +                                       C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>> +                                       BIT(0));
>>> +
>>> +   video_device_pipeline_stop(&cap->vdev);
>>> +   pm_runtime_put(cap->isp->dev);
>>> +}
>>> +
>>> +static const struct vb2_ops isp_video_vb2_ops = {
>>> +   .queue_setup = c3_isp_vb2_queue_setup,
>>> +   .buf_queue = c3_isp_vb2_buf_queue,
>>> +   .buf_prepare = c3_isp_vb2_buf_prepare,
>>> +   .buf_init = c3_isp_vb2_buf_init,
>>> +   .wait_prepare = vb2_ops_wait_prepare,
>>> +   .wait_finish = vb2_ops_wait_finish,
>>> +   .start_streaming = c3_isp_vb2_start_streaming,
>>> +   .stop_streaming = c3_isp_vb2_stop_streaming,
>>> +};
>>> +
>>> +static int c3_isp_register_capture(struct c3_isp_capture *cap)
>>> +{
>>> +   struct video_device *vdev = &cap->vdev;
>>> +   struct vb2_queue *vb2_q = &cap->vb2_q;
>>> +   int ret;
>>> +
>>> +   snprintf(vdev->name, sizeof(vdev->name), "isp-cap%u", cap->id);
>>> +   vdev->fops = &isp_cap_v4l2_fops;
>>> +   vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
>>> +   vdev->v4l2_dev = &cap->isp->v4l2_dev;
>>> +   vdev->entity.ops = &isp_cap_entity_ops;
>>> +   vdev->lock = &cap->lock;
>>> +   vdev->minor = -1;
>>> +   vdev->queue = vb2_q;
>>> +   vdev->release = video_device_release_empty;
>>> +   vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
>>> +   vdev->vfl_dir = VFL_DIR_RX;
>>> +   video_set_drvdata(vdev, cap);
>>> +
>>> +   vb2_q->drv_priv = cap;
>>> +   vb2_q->mem_ops = &vb2_dma_contig_memops;
>>> +   vb2_q->ops = &isp_video_vb2_ops;
>>> +   vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
>>> +   vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>>> +   vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>>> +   vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>>> +   vb2_q->dev = cap->isp->dev;
>>> +   vb2_q->lock = &cap->lock;
>>> +   vb2_q->min_queued_buffers = 2;
>> I'm not sure what your plans regarding libcamera are, but be aware
>> we're going to get stricter about this value. Ideally, we aim to have
>> ISP driver set this value to 0 so that the ISP operates (produce
>> statistics) even if no buffer is queued to the capture devices. The
>> reason is that we want algorithms to run even if there are no capture
>> buffers provided.
>>
>> In order to allow the driver to operate with no buffers, you could
>> probably allocate a scratch buffer in the driver and use it whenever
>> you receive a frame completed IRQ and have no buffers available in the
>> cap->provided queue.
>>
>> See rkisp1_dummy_buf_create() in the RkISP1 driver and how dummy_buf
>> is used there.
>>
>>> +
>>> +   ret = vb2_queue_init(vb2_q);
>>> +   if (ret < 0)
>>> +           goto err_destroy;
>>> +
>>> +   cap->pad.flags = MEDIA_PAD_FL_SINK;
>>> +   ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
>>> +   if (ret < 0)
>>> +           goto err_queue_release;
>>> +
>>> +   ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>>> +   if (ret < 0) {
>>> +           dev_err(cap->isp->dev,
>>> +                   "Failed to register %s: %d\n", vdev->name, ret);
>>> +           goto err_entity_cleanup;
>>> +   }
>>> +
>>> +   return 0;
>>> +
>>> +err_entity_cleanup:
>>> +   media_entity_cleanup(&vdev->entity);
>>> +err_queue_release:
>>> +   vb2_queue_release(vb2_q);
>>> +err_destroy:
>>> +   mutex_destroy(&cap->lock);
>>> +   return ret;
>>> +}
>>> +
>>> +int c3_isp_captures_register(struct c3_isp_device *isp)
>>> +{
>>> +   int ret;
>>> +   unsigned int i;
>>> +   struct c3_isp_capture *cap;
>>> +
>>> +   for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>>> +           cap = &isp->caps[i];
>>> +           memset(cap, 0, sizeof(*cap));
>>> +
>>> +           cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
>>> +           cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
>>> +           cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
>>> +           cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
>>> +           cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
>>> +
>>> +           c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
>>> +
>>> +           cap->id = i;
>>> +           if (cap->id == C3_ISP_CAP_DEV_0)
>>> +                   cap->rsz = &isp->resizers[C3_ISP_RSZ_0];
>>> +           else if (cap->id == C3_ISP_CAP_DEV_1)
>>> +                   cap->rsz = &isp->resizers[C3_ISP_RSZ_1];
>>> +           else
>>> +                   cap->rsz = &isp->resizers[C3_ISP_RSZ_2];
>>> +
>>> +           cap->isp = isp;
>>> +           INIT_LIST_HEAD(&cap->pending);
>>> +           spin_lock_init(&cap->buff_lock);
>>> +           mutex_init(&cap->lock);
>>> +
>>> +           ret = c3_isp_register_capture(cap);
>>> +           if (ret) {
>>> +                   cap->isp = NULL;
>>> +                   mutex_destroy(&cap->lock);
>>> +                   c3_isp_captures_unregister(isp);
>>> +                   return ret;
>>> +           }
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +void c3_isp_captures_unregister(struct c3_isp_device *isp)
>>> +{
>>> +   unsigned int i;
>>> +   struct c3_isp_capture *cap;
>>> +
>>> +   for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>>> +           cap = &isp->caps[i];
>>> +
>>> +           if (!cap->isp)
>>> +                   continue;
>>> +           vb2_queue_release(&cap->vb2_q);
>>> +           media_entity_cleanup(&cap->vdev.entity);
>>> +           video_unregister_device(&cap->vdev);
>>> +           mutex_destroy(&cap->lock);
>>> +   }
>>> +}
>>> +
>>> +void c3_isp_captures_done(struct c3_isp_device *isp)
>>> +{
>>> +   c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
>>> +   c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
>>> +   c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
>>> +}
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
>>> new file mode 100644
>>> index 000000000000..19f2a3bc29c9
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
>>> @@ -0,0 +1,327 @@
>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#ifndef __C3_ISP_COMMON_H__
>>> +#define __C3_ISP_COMMON_H__
>>> +
>>> +#include <linux/clk.h>
>>> +
>>> +#include <media/media-device.h>
>>> +#include <media/videobuf2-core.h>
>>> +#include <media/v4l2-device.h>
>>> +#include <media/v4l2-subdev.h>
>>> +#include <media/videobuf2-v4l2.h>
>>> +
>>> +#define C3_ISP_DRIVER_NAME            "c3-isp"
>>> +#define C3_ISP_CLOCK_NUM_MAX          3
>>> +
>>> +#define C3_ISP_CORE_DEF_SINK_PAD_FMT  MEDIA_BUS_FMT_SRGGB10_1X10
>>> +#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUYV8_1_5X8
>>> +#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUYV8_1_5X8
>>> +#define C3_ISP_DEFAULT_WIDTH          1920
>>> +#define C3_ISP_DEFAULT_HEIGHT         1080
>>> +#define C3_ISP_MAX_WIDTH              2888
>>> +#define C3_ISP_MAX_HEIGHT             2240
>>> +#define C3_ISP_MIN_WIDTH              160
>>> +#define C3_ISP_MIN_HEIGHT             120
>>> +
>>> +#define C3_DISP_INTER                 0x400
>>> +#define C3_DISP_REG(base, id)         ((base) + (id) * C3_DISP_INTER)
>>> +#define C3_WRMIFX3_INTER              0x100
>>> +#define C3_WRMIFX3_REG(base, id)      ((base) + (id) * C3_WRMIFX3_INTER)
>>> +#define C3_PPS_TAP4_S11_H_NUM         33
>>> +#define C3_PPS_LUT_CTYPE_0            0
>>> +#define C3_PPS_LUT_CTYPE_2            2
>>> +#define C3_SCALE_EN                   1
>>> +#define C3_SCALE_DIS                  0
>>> +
>>> +#define C3_ISP_PHASE_OFFSET_0         0
>>> +#define C3_ISP_PHASE_OFFSET_1         1
>>> +#define C3_ISP_PHASE_OFFSET_NONE      0xff
>>> +
>>> +enum c3_isp_core_pads {
>>> +   C3_ISP_CORE_PAD_SINK_VIDEO,
>>> +   C3_ISP_CORE_PAD_SINK_PARAMS,
>>> +   C3_ISP_CORE_PAD_SOURCE_STATS,
>>> +   C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>> +   C3_ISP_CORE_PAD_MAX
>>> +};
>>> +
>>> +enum c3_isp_resizer_ids {
>>> +   C3_ISP_RSZ_0,
>>> +   C3_ISP_RSZ_1,
>>> +   C3_ISP_RSZ_2,
>>> +   C3_ISP_NUM_RSZ
>>> +};
>>> +
>>> +enum c3_isp_resizer_pads {
>>> +   C3_ISP_RESIZER_PAD_SINK,
>>> +   C3_ISP_RESIZER_PAD_SOURCE,
>>> +   C3_ISP_RESIZER_PAD_MAX
>>> +};
>>> +
>>> +enum c3_isp_cap_devs {
>>> +   C3_ISP_CAP_DEV_0,
>>> +   C3_ISP_CAP_DEV_1,
>>> +   C3_ISP_CAP_DEV_2,
>>> +   C3_ISP_NUM_CAP_DEVS
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_pps_io_size - isp scaler input and output size
>>> + *
>>> + * @thsize: input horizontal size of after preprocessing
>>> + * @tvsize: input vertical size of after preprocessing
>>> + * @ohsize: output horizontal size
>>> + * @ovsize: output vertical size
>>> + * @ihsize: input horizontal size
>>> + * @max_hsize: maximum horizontal size
>>> + */
>>> +struct c3_isp_pps_io_size {
>>> +   u32 thsize;
>>> +   u32 tvsize;
>>> +   u32 ohsize;
>>> +   u32 ovsize;
>>> +   u32 ihsize;
>>> +   u32 max_hsize;
>>> +};
>>> +
>>> +/**
>>> + * @mbus_code: the mbus code
>>> + * @pads: save the pad flag of this mbus_code
>>> + * @xofst: horizontal phase offset of hardware
>>> + * @yofst: vertical phase offset of hardware
>>> + */
>>> +struct c3_isp_mbus_format_info {
>>> +   u32 mbus_code;
>>> +   u32 pads;
>>> +   u8 xofst;
>>> +   u8 yofst;
>>> +};
>>> +
>>> +/**
>>> + * @mbus_code: the mbus code
>>> + * @fourcc: pixel format
>>> + * @depth: pixel width
>>> + */
>>> +struct c3_isp_capture_format {
>>> +   u32 mbus_code;
>>> +   u32 fourcc;
>>> +   u8 depth;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_vb2_buffer - A container of vb2 buffer
>>> + *
>>> + * @vb: vb2 buffer
>>> + * @vaddr: buffer virtual address
>>> + * @paddr: buffer physical address
>>> + * @list: entry of the buffer in the queue
>>> + */
>>> +struct c3_isp_vb2_buffer {
>>> +   struct vb2_v4l2_buffer vb;
>>> +   void *vaddr;
>>> +   dma_addr_t paddr;
>>> +   struct list_head list;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_core - ISP core subdev
>>> + *
>>> + * @sd: ISP sub-device
>>> + * @pads: ISP sub-device pads
>>> + * @src_sd: source sub-device
>>> + * @isp: pointer to c3_isp_device
>>> + * @src_sd_pad: source sub-device pad
>>> + */
>>> +struct c3_isp_core {
>>> +   struct v4l2_subdev sd;
>>> +   struct media_pad pads[C3_ISP_CORE_PAD_MAX];
>>> +   struct v4l2_subdev *src_sd;
>>> +   u16 src_sd_pad;
>>> +   struct c3_isp_device *isp;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_resizer - ISP resizer subdev
>>> + *
>>> + * @id: resizer id
>>> + * @sd: resizer sub-device
>>> + * @pads: resizer sub-device pads
>>> + * @isp: pointer to c3_isp_device
>>> + * @cap: pointer to c3_isp_capture
>>> + */
>>> +struct c3_isp_resizer {
>>> +   enum c3_isp_resizer_ids id;
>>> +   struct v4l2_subdev sd;
>>> +   struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
>>> +   struct c3_isp_device *isp;
>>> +   struct c3_isp_capture *cap;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_stats - ISP statistics device
>>> + *
>>> + * @vb2_q: vb2 buffer queue
>>> + * @vdev: video node
>>> + * @vfmt: v4l2_format of the metadata format
>>> + * @pad: media pad
>>> + * @lock: protects vb2_q, vdev
>>> + * @is_streaming: stats status
>>> + * @isp: pointer to c3_isp_device
>>> + * @buff: in use buffer
>>> + * @buff_lock: protects stats buffer
>>> + * @pending: stats buffer list head
>>> + */
>>> +struct c3_isp_stats {
>>> +   struct vb2_queue vb2_q;
>>> +   struct video_device vdev;
>>> +   struct v4l2_format vfmt;
>>> +   struct media_pad pad;
>>> +
>>> +   struct mutex lock; /* Protects vb2_q, vdev */
>>> +   bool is_streaming;
>>> +   struct c3_isp_device *isp;
>>> +
>>> +   struct c3_isp_vb2_buffer *buff;
>>> +   spinlock_t buff_lock; /* Protects stream buffer */
>>> +   struct list_head pending;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_params - ISP parameters device
>>> + *
>>> + * @vb2_q: vb2 buffer queue
>>> + * @vdev: video node
>>> + * @vfmt: v4l2_format of the metadata format
>>> + * @pad: media pad
>>> + * @lock: protects vb2_q, vdev
>>> + * @isp: pointer to c3_isp_device
>>> + * @buff: in use buffer
>>> + * @buff_lock: protects stats buffer
>>> + * @pending: stats buffer list head
>>> + */
>>> +struct c3_isp_params {
>>> +   struct vb2_queue vb2_q;
>>> +   struct video_device vdev;
>>> +   struct v4l2_format vfmt;
>>> +   struct media_pad pad;
>>> +
>>> +   struct mutex lock; /* Protects vb2_q, vdev */
>>> +   struct c3_isp_device *isp;
>>> +
>>> +   struct c3_isp_vb2_buffer *buff;
>>> +   spinlock_t buff_lock; /* Protects stream buffer */
>>> +   struct list_head pending;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_capture - ISP capture device
>>> + *
>>> + * @id: capture device ID
>>> + * @vb2_q: vb2 buffer queue
>>> + * @vdev: video node
>>> + * @vfmt: v4l2_format of the capture format
>>> + * @pad: media pad
>>> + * @lock: protects vb2_q, vdev
>>> + * @is_streaming: capture device status
>>> + * @isp: pointer to c3_isp_device
>>> + * @rsz: pointer to c3_isp_resizer
>>> + * @buff: in use buffer
>>> + * @buff_lock: protects capture buffer
>>> + * @pending: capture buffer list head
>>> + */
>>> +struct c3_isp_capture {
>>> +   enum c3_isp_cap_devs id;
>>> +   struct vb2_queue vb2_q;
>>> +   struct video_device vdev;
>>> +   struct v4l2_format vfmt;
>>> +   struct media_pad pad;
>>> +
>>> +   struct mutex lock; /* Protects vb2_q, vdev */
>>> +   bool is_streaming;
>>> +   struct c3_isp_device *isp;
>>> +   struct c3_isp_resizer *rsz;
>>> +
>>> +   struct c3_isp_vb2_buffer *buff;
>>> +   spinlock_t buff_lock; /* Protects stream buffer */
>>> +   struct list_head pending;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_info - ISP information
>>> + *
>>> + * @clocks: array of ISP clock names
>>> + * @clock_rates: array of ISP clock rate
>>> + * @clock_num: actual clock number
>>> + */
>>> +struct c3_isp_info {
>>> +   char *clocks[C3_ISP_CLOCK_NUM_MAX];
>>> +   u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
>>> +   u32 clock_num;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_device - ISP platform device
>>> + *
>>> + * @dev: pointer to the struct device
>>> + * @base: base register address
>>> + * @clks: array of clocks
>>> + * @notifier: notifier to register on the v4l2-async API
>>> + * @v4l2_dev: v4l2_device variable
>>> + * @media_dev: media device variable
>>> + * @pipe: media pipeline
>>> + * @core: ISP core subdev
>>> + * @resizer: ISP resizer subdev
>>> + * @stats: ISP stats device
>>> + * @params: ISP params device
>>> + * @caps: array of ISP capture device
>>> + * @frm_sequence: used to record frame id
>>> + * @lock: protect ISP device
>>> + * @info: version-specific ISP information
>>> + */
>>> +struct c3_isp_device {
>>> +   struct device *dev;
>>> +   void __iomem *base;
>>> +   struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
>>> +
>>> +   struct v4l2_async_notifier notifier;
>>> +   struct v4l2_device v4l2_dev;
>>> +   struct media_device media_dev;
>>> +   struct media_pipeline pipe;
>>> +
>>> +   struct c3_isp_core core;
>>> +   struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
>>> +   struct c3_isp_stats stats;
>>> +   struct c3_isp_params params;
>>> +   struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
>>> +
>>> +   u32 frm_sequence;
>>> +   struct mutex lock; /* Protect ISP device */
>>> +   const struct c3_isp_info *info;
>>> +};
>>> +
>>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
>>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
>>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
>>> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp);
>>> +
>>> +int c3_isp_core_register(struct c3_isp_device *isp);
>>> +void c3_isp_core_unregister(struct c3_isp_device *isp);
>>> +int c3_isp_resizers_register(struct c3_isp_device *isp);
>>> +void c3_isp_resizers_unregister(struct c3_isp_device *isp);
>>> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz);
>>> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz);
>>> +int c3_isp_captures_register(struct c3_isp_device *isp);
>>> +void c3_isp_captures_unregister(struct c3_isp_device *isp);
>>> +void c3_isp_captures_done(struct c3_isp_device *isp);
>>> +int c3_isp_stats_register(struct c3_isp_device *isp);
>>> +void c3_isp_stats_unregister(struct c3_isp_device *isp);
>>> +int c3_isp_stats_done(struct c3_isp_device *isp);
>>> +int c3_isp_params_register(struct c3_isp_device *isp);
>>> +void c3_isp_params_unregister(struct c3_isp_device *isp);
>>> +int c3_isp_params_done(struct c3_isp_device *isp);
>>> +
>>> +#endif
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
>>> new file mode 100644
>>> index 000000000000..d3672aff9fd2
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
>>> @@ -0,0 +1,675 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/pm_runtime.h>
>>> +
>>> +#include "c3-isp-common.h"
>>> +#include "c3-isp-regs.h"
>>> +#include "include/uapi/c3-isp-config.h"
>>> +
>>> +#define C3_ISP_CORE_SUBDEV_NAME        "isp-core"
>>> +
>>> +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
>>> +   /* RAW formats */
>>> +   {
>>> +           .mbus_code      = MEDIA_BUS_FMT_SBGGR10_1X10,
>>> +           .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_0,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_1,
>>> +   }, {
>>> +           .mbus_code      = MEDIA_BUS_FMT_SGBRG10_1X10,
>>> +           .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_1,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_1,
>>> +   }, {
>>> +           .mbus_code      = MEDIA_BUS_FMT_SGRBG10_1X10,
>>> +           .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_0,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_0,
>>> +   }, {
>>> +           .mbus_code      = MEDIA_BUS_FMT_SRGGB10_1X10,
>>> +           .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_1,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_0,
>>> +   }, {
>>> +           .mbus_code      = MEDIA_BUS_FMT_SBGGR12_1X12,
>>> +           .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_0,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_1,
>>> +   }, {
>>> +           .mbus_code      = MEDIA_BUS_FMT_SGBRG12_1X12,
>>> +           .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_1,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_1,
>>> +   }, {
>>> +           .mbus_code      = MEDIA_BUS_FMT_SGRBG12_1X12,
>>> +           .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_0,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_0,
>>> +   }, {
>>> +           .mbus_code      = MEDIA_BUS_FMT_SRGGB12_1X12,
>>> +           .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_1,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_0,
>>> +   },
>>> +   /* YUV formats */
>>> +   {
>>> +           .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
>>> +           .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +   }, {
>>> +           .mbus_code      = MEDIA_BUS_FMT_YUYV8_1_5X8,
>>> +           .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +   }, {
>>> +           .mbus_code      = MEDIA_BUS_FMT_YUYV8_2X8,
>>> +           .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +   },
>>> +};
>>> +
>>> +static const struct c3_isp_mbus_format_info
>>> +*core_find_format_by_code(u32 code, u32 pad)
>>> +{
>>> +   int i;
>> unsigned
>>
>>> +
>>> +   for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>>> +           const struct c3_isp_mbus_format_info *info =
>>> +                   &c3_isp_core_mbus_formats[i];
>>> +
>>> +           if (info->mbus_code == code && info->pads & BIT(pad))
>>> +                   return info;
>>> +   }
>>> +
>>> +   return NULL;
>>> +}
>>> +
>>> +static const struct c3_isp_mbus_format_info
>>> +*core_find_format_by_index(u32 index, u32 pad)
>>> +{
>>> +   int i;
>> unsigned
>>
>>> +
>>> +   for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>>> +           const struct c3_isp_mbus_format_info *info =
>>> +                   &c3_isp_core_mbus_formats[i];
>>> +
>>> +           if (!(info->pads & BIT(pad)))
>>> +                   continue;
>>> +
>>> +           if (!index)
>>> +                   return info;
>>> +
>>> +           index--;
>>> +   }
>>> +
>>> +   return NULL;
>>> +}
>>> +
>>> +static void c3_isp_core_enable(struct c3_isp_device *isp)
>>> +{
>>> +   /* Select the line sync signal */
>>> +   c3_isp_update_bits(isp, ISP_TOP_IRQ_LINE_THRD,
>>> +                      TOP_IRQ_DIN_HSYNC, TOP_IRQ_DIN_HSYNC);
>>> +
>>> +   /* Enable frame done and stats error irq */
>>> +   c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
>>> +                      TOP_IRQ_FRAME_DONE, TOP_IRQ_FRAME_DONE);
>>> +   c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
>>> +                      TOP_IRQ_STATS_ERR, TOP_IRQ_STATS_ERR);
>>> +
>>> +   /* Enable image data to ISP core */
>>> +   c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
>>> +                      TOP_DATA_PATH_MASK, TOP_PATH_MIPI_TO_CORE);
>>> +}
>>> +
>>> +static void c3_isp_core_disable(struct c3_isp_device *isp)
>>> +{
>>> +   /* Disable image data to ISP core */
>>> +   c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
>>> +                      TOP_DATA_PATH_MASK, 0x0);
>>> +
>>> +   /* Disable all irq */
>>> +   c3_isp_write(isp, ISP_TOP_IRQ_EN, 0x0);
>>> +}
>>> +
>>> +/* Set the phase offset of blc, wb and lns */
>>> +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
>>> +                             u8 xofst, u8 yofst)
>>> +{
>>> +   c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
>>> +                      LSWB_BLC_XPHS_OFST_MASK,
>>> +                      xofst << LSWB_BLC_XPHS_OFST_SHIFT);
>>> +   c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
>>> +                      LSWB_BLC_YPHS_OFST_MASK, yofst);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
>>> +                      LSWB_WB_XPHS_OFST_MASK,
>>> +                      xofst << LSWB_WB_XPHS_OFST_SHIFT);
>>> +   c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
>>> +                      LSWB_WB_YPHS_OFST_MASK, yofst);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
>>> +                      LSWB_LNS_XPHS_OFST_MASK,
>>> +                      xofst << LSWB_LNS_XPHS_OFST_SHIFT);
>>> +   c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
>>> +                      LSWB_LNS_YPHS_OFST_MASK, yofst);
>>> +}
>>> +
>>> +/* Set the phase offset of af, ae and awb */
>>> +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
>>> +                           u8 xofst, u8 yofst)
>>> +{
>>> +   c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_XPHS_OFST_MASK,
>>> +                      xofst << AF_CTRL_XPHS_OFST_SHIFT);
>>> +   c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_YPHS_OFST_MASK,
>>> +                      yofst << AF_CTRL_YPHS_OFST_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_XPHS_OFST_MASK,
>>> +                      xofst << AE_CTRL_XPHS_OFST_SHIFT);
>>> +   c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_YPHS_OFST_MASK,
>>> +                      yofst << AE_CTRL_YPHS_OFST_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_XPHS_OFST_MASK,
>>> +                      xofst << AWB_CTRL_XPHS_OFST_SHIFT);
>>> +   c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_YPHS_OFST_MASK, yofst);
>>> +}
>>> +
>>> +/* Set the phase offset of demosaic */
>>> +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
>>> +                            u8 xofst, u8 yofst)
>>> +{
>>> +   c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_XPHS_OFST_MASK,
>>> +                      xofst << DMS_COMMON_XPHS_OFST_SHIFT);
>>> +   c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_YPHS_OFST_MASK, yofst);
>>> +}
>>> +
>>> +static void c3_isp_core_cfg_ofst(struct c3_isp_device *isp,
>>> +                            struct v4l2_mbus_framefmt *fmt)
>>> +{
>>> +   const struct c3_isp_mbus_format_info *isp_fmt =
>>> +                   core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
>>> +
>>> +   c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>>> +   c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>>> +   c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>>> +}
>>> +
>>> +/* Set format of the hardware control module */
>>> +static void c3_isp_core_top_fmt(struct c3_isp_device *isp,
>>> +                           struct v4l2_mbus_framefmt *fmt)
>>> +{
>>> +   c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
>>> +                TOP_INPUT_HSIZE(fmt->width) | TOP_INPUT_VSIZE(fmt->height));
>>> +
>>> +   c3_isp_write(isp, ISP_TOP_FRM_SIZE,
>>> +                TOP_FRM_CORE_IHSIZE(fmt->width) | TOP_FRM_CORE_IVSIZE(fmt->height));
>>> +
>>> +   c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE, TOP_HOLD_HSIZE_MASK,
>>> +                      fmt->width << TOP_HOLD_HSIZE_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_core_af_fmt(struct c3_isp_device *isp,
>>> +                          struct v4l2_mbus_framefmt *fmt)
>>> +{
>>> +   u32 hidx;
>>> +   u32 vidx;
>>> +   int i;
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, AF_HV_STAT_HBLK_NUM_MASK,
>>> +                      AF_STAT_BLKH_NUM << AF_HV_STAT_HBLK_NUM_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM,
>>> +                      AF_HV_STAT_VBLK_NUM_MASK, AF_STAT_BLKV_NUM);
>>> +
>>> +   c3_isp_write(isp, ISP_AF_HV_SIZE,
>>> +                AF_HV_STAT_HSIZE(fmt->width) | AF_HV_STAT_VSIZE(fmt->height));
>>> +
>>> +   /* Set the index address to 0 position */
>>> +   c3_isp_write(isp, ISP_AF_IDX_ADDR, 0);
>>> +   /*
>>> +    * Calculate and set the coordinates of points in the grid.
>>> +    * hidx and vidx need to be aligned with 2.
>>> +    */
>>> +   for (i = 0; i <= AF_STAT_BLKH_NUM; i++) {
>>> +           hidx = i * fmt->width / AF_STAT_BLKH_NUM;
>>> +           hidx = ALIGN_DOWN(hidx, 2);
>>> +
>>> +           vidx = i * fmt->height / AF_STAT_BLKV_NUM;
>>> +           vidx = min(vidx, fmt->height);
>>> +           vidx = ALIGN_DOWN(vidx, 2);
>>> +           c3_isp_write(isp, ISP_AF_IDX_DATA,
>>> +                        AF_IDX_HIDX_DATA(hidx) | AF_IDX_VIDX_DATA(vidx));
>>> +   }
>>> +}
>>> +
>>> +static void c3_isp_core_ae_fmt(struct c3_isp_device *isp,
>>> +                          struct v4l2_mbus_framefmt *fmt)
>>> +{
>>> +   u32 hidx;
>>> +   u32 vidx;
>>> +   int i;
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, AE_HV_STAT_HBLK_NUM_MASK,
>>> +                      AE_STAT_BLKH_NUM << AE_HV_STAT_HBLK_NUM_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM,
>>> +                      AE_HV_STAT_VBLK_NUM_MASK, AE_STAT_BLKV_NUM);
>>> +
>>> +   c3_isp_write(isp, ISP_AE_HV_SIZE,
>>> +                AE_HV_STAT_HSIZE(fmt->width) | AE_HV_STAT_VSIZE(fmt->height));
>>> +
>>> +   /* Set the index address to 0 position */
>>> +   c3_isp_write(isp, ISP_AE_IDX_ADDR, 0);
>>> +   /*
>>> +    * Calculate and set the coordinates of points in the grid.
>>> +    * hidx and vidx need to be aligned with 2.
>>> +    */
>>> +   for (i = 0; i <= AE_STAT_BLKH_NUM; i++) {
>>> +           hidx = i * fmt->width / AE_STAT_BLKH_NUM;
>>> +           hidx = ALIGN_DOWN(hidx, 2);
>>> +
>>> +           vidx = i * fmt->height / AE_STAT_BLKV_NUM;
>>> +           vidx = min(vidx, fmt->height);
>>> +           vidx = ALIGN_DOWN(vidx, 2);
>>> +
>>> +           c3_isp_write(isp, ISP_AE_IDX_DATA,
>>> +                        AE_IDX_HIDX_DATA(hidx) | AE_IDX_VIDX_DATA(vidx));
>>> +   }
>>> +}
>>> +
>>> +static void c3_isp_core_awb_fmt(struct c3_isp_device *isp,
>>> +                           struct v4l2_mbus_framefmt *fmt)
>>> +{
>>> +   u32 hidx;
>>> +   u32 vidx;
>>> +   int i;
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, AWB_HV_STAT_HBLK_NUM_MASK,
>>> +                      AWB_STAT_BLKH_NUM << AWB_HV_STAT_HBLK_NUM_SHIFT);
>>> +   c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM,
>>> +                      AWB_HV_STAT_VBLK_NUM_MASK, AWB_STAT_BLKV_NUM);
>>> +
>>> +   c3_isp_write(isp, ISP_AWB_HV_SIZE,
>>> +                AWB_HV_STAT_HSIZE(fmt->width) | AWB_HV_STAT_VSIZE(fmt->height));
>>> +
>>> +   /* Set the index address to 0 position */
>>> +   c3_isp_write(isp, ISP_AWB_IDX_ADDR, 0);
>>> +   /*
>>> +    * Calculate and set the coordinates of points in the grid.
>>> +    * hidx and vidx need to be aligned with 2.
>>> +    */
>>> +   for (i = 0; i <= AWB_STAT_BLKH_NUM; i++) {
>>> +           hidx = i * fmt->width / AWB_STAT_BLKH_NUM;
>>> +           hidx = ALIGN_DOWN(hidx, 2);
>>> +
>>> +           vidx = i * fmt->height / AWB_STAT_BLKV_NUM;
>>> +           vidx = min(vidx, fmt->height);
>>> +           vidx = ALIGN_DOWN(vidx, 2);
>>> +
>>> +           c3_isp_write(isp, ISP_AWB_IDX_DATA,
>>> +                        AWB_IDX_HIDX_DATA(hidx) | AWB_IDX_VIDX_DATA(vidx));
>>> +   }
>>> +}
>>> +
>>> +static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
>>> +                              struct v4l2_subdev_state *state)
>>> +{
>>> +   struct v4l2_mbus_framefmt *fmt;
>>> +
>>> +   fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>>> +
>>> +   c3_isp_core_cfg_ofst(isp, fmt);
>>> +   c3_isp_core_top_fmt(isp, fmt);
>>> +   c3_isp_core_af_fmt(isp, fmt);
>>> +   c3_isp_core_ae_fmt(isp, fmt);
>>> +   c3_isp_core_awb_fmt(isp, fmt);
>>> +}
>>> +
>>> +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
>>> +                                 struct v4l2_subdev_state *state,
>>> +                                 u32 pad, u64 streams_mask)
>>> +{
>>> +   struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>>> +   u64 sink_streams;
>>> +   int ret;
>>> +
>>> +   core->isp->frm_sequence = 0;
>>> +   c3_isp_core_cfg_format(core->isp, state);
>>> +   c3_isp_core_enable(core->isp);
>>> +
>>> +   sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>> +                                                  C3_ISP_CORE_PAD_SINK_VIDEO,
>>> +                                                  &streams_mask);
>>> +   ret = v4l2_subdev_enable_streams(core->src_sd,
>>> +                                    core->src_sd_pad, sink_streams);
>>> +   if (ret) {
>>> +           c3_isp_core_disable(core->isp);
>>> +           return ret;
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
>>> +                                  struct v4l2_subdev_state *state,
>>> +                                  u32 pad, u64 streams_mask)
>>> +{
>>> +   struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>>> +   u64 sink_streams;
>>> +   int ret;
>>> +
>>> +   sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>> +                                                  C3_ISP_CORE_PAD_SINK_VIDEO,
>>> +                                                  &streams_mask);
>>> +   ret = v4l2_subdev_disable_streams(core->src_sd,
>>> +                                     core->src_sd_pad, sink_streams);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   c3_isp_core_disable(core->isp);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
>>> +                              struct v4l2_subdev_state *state,
>>> +                              struct v4l2_subdev_krouting *routing)
>>> +{
>>> +   static const struct v4l2_mbus_framefmt format = {
>>> +           .width = C3_ISP_DEFAULT_WIDTH,
>>> +           .height = C3_ISP_DEFAULT_HEIGHT,
>>> +           .code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
>>> +           .field = V4L2_FIELD_NONE,
>>> +           .colorspace = V4L2_COLORSPACE_SRGB,
>>> +           .ycbcr_enc = V4L2_YCBCR_ENC_601,
>>> +           .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>>> +           .xfer_func = V4L2_XFER_FUNC_SRGB,
>>> +   };
>>> +   int ret;
>>> +
>>> +   ret = v4l2_subdev_routing_validate(sd, routing,
>>> +                                      V4L2_SUBDEV_ROUTING_NO_SOURCE_STREAM_MIX);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
>>> +                               struct v4l2_subdev_state *state)
>>> +{
>>> +   struct v4l2_subdev_route routes[2];
>>> +   struct v4l2_subdev_krouting routing;
>>> +
>>> +   routes[0].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
>>> +   routes[0].sink_stream = 0;
>>> +   routes[0].source_pad = C3_ISP_CORE_PAD_SOURCE_STATS;
>>> +   routes[0].source_stream = 0;
>>> +   routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>>> +
>>> +   routes[1].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
>>> +   routes[1].sink_stream = 0;
>>> +   routes[1].source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
>>> +   routes[1].source_stream = 0;
>>> +   routes[1].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>>> +
>>> +   routing.num_routes = ARRAY_SIZE(routes);
>>> +   routing.routes = routes;
>>> +
>>> +   return c3_isp_core_cfg_routing(sd, state, &routing);
>>> +}
>>> +
>>> +static int c3_isp_core_set_routing(struct v4l2_subdev *sd,
>>> +                              struct v4l2_subdev_state *state,
>>> +                              enum v4l2_subdev_format_whence which,
>>> +                              struct v4l2_subdev_krouting *routing)
>> I'm not sure I see a reason for the ISP subdev to implement routing.
>> In my understanding it will only receive a single image stream and
>> processes it to 3 DMA output nodes, and to control which output is
>> enabled you use media links.
>>
>>> +{
>>> +   bool is_streaming = v4l2_subdev_is_streaming(sd);
>>> +
>>> +   if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>>> +           return -EBUSY;
>>> +
>>> +   return c3_isp_core_cfg_routing(sd, state, routing);
>>> +}
>>> +
>>> +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
>>> +                                 struct v4l2_subdev_state *state,
>>> +                                 struct v4l2_subdev_mbus_code_enum *code)
>>> +{
>>> +   const struct c3_isp_mbus_format_info *info;
>>> +   int ret = 0;
>>> +
>>> +   switch (code->pad) {
>>> +   case C3_ISP_CORE_PAD_SINK_VIDEO:
>>> +   case C3_ISP_CORE_PAD_SOURCE_VIDEO:
>>> +           info = core_find_format_by_index(code->index, code->pad);
>>> +           if (!info)
>>> +                   ret = -EINVAL;
>>> +           else
>>> +                   code->code = info->mbus_code;
>>> +
>>> +           break;
>>> +   case C3_ISP_CORE_PAD_SINK_PARAMS:
>>> +   case C3_ISP_CORE_PAD_SOURCE_STATS:
>>> +           if (code->index)
>>> +                   ret = -EINVAL;
>>> +           else
>>> +                   code->code = MEDIA_BUS_FMT_METADATA_FIXED;
>>> +
>>> +           break;
>>> +   default:
>>> +           ret = -EINVAL;
>>> +           break;
>>> +   }
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static void c3_isp_core_set_sink_fmt(struct v4l2_subdev_state *state,
>>> +                                struct v4l2_subdev_format *format)
>>> +{
>>> +   struct v4l2_mbus_framefmt *sink_fmt;
>>> +   const struct c3_isp_mbus_format_info *isp_fmt;
>>> +
>>> +   sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>> +
>>> +   isp_fmt = core_find_format_by_code(format->format.code, format->pad);
>>> +   if (!isp_fmt)
>>> +           sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>>> +   else
>>> +           sink_fmt->code = format->format.code;
>>> +
>>> +   sink_fmt->width = clamp_t(u32, format->format.width,
>>> +                             C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>>> +   sink_fmt->height = clamp_t(u32, format->format.height,
>>> +                              C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>>> +
>>> +   format->format = *sink_fmt;
>>> +}
>>> +
>>> +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
>>> +                                  struct v4l2_subdev_format *format)
>>> +{
>>> +   const struct c3_isp_mbus_format_info *isp_fmt;
>>> +   struct v4l2_mbus_framefmt *sink_fmt;
>>> +   struct v4l2_mbus_framefmt *src_fmt;
>>> +
>>> +   sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>>> +   src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>> +
>>> +   isp_fmt = core_find_format_by_code(format->format.code, format->pad);
>>> +   if (!isp_fmt)
>>> +           src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>>> +   else
>>> +           src_fmt->code = format->format.code;
>>> +
>>> +   /* The source size must be same with the sink size. */
>>> +   src_fmt->width  = sink_fmt->width;
>>> +   src_fmt->height = sink_fmt->height;
>>> +
>>> +   format->format = *src_fmt;
>>> +}
>>> +
>>> +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
>>> +                          struct v4l2_subdev_state *state,
>>> +                          struct v4l2_subdev_format *format)
>>> +{
>>> +   struct v4l2_mbus_framefmt *fmt;
>>> +
>>> +   switch (format->pad) {
>>> +   case C3_ISP_CORE_PAD_SINK_VIDEO:
>>> +           c3_isp_core_set_sink_fmt(state, format);
>>> +           break;
>>> +   case C3_ISP_CORE_PAD_SINK_PARAMS:
>>> +   case C3_ISP_CORE_PAD_SOURCE_STATS:
>>> +           fmt = v4l2_subdev_state_get_format(state, format->pad);
>>> +           format->format = *fmt;
>>> +           break;
>>> +   case C3_ISP_CORE_PAD_SOURCE_VIDEO:
>>> +           c3_isp_core_set_source_fmt(state, format);
>>> +           break;
>>> +   default:
>> I don't think this can happen. The core validates that format->pad is
>> correct.
>>
>>> +           dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
>>> +           return -ENOTTY;
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_core_init_state(struct v4l2_subdev *sd,
>>> +                             struct v4l2_subdev_state *state)
>>> +{
>>> +   struct v4l2_mbus_framefmt *sink_fmt;
>>> +   struct v4l2_mbus_framefmt *src_fmt;
>>> +
>>> +   /* Video sink pad */
>>> +   sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>>> +   sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>>> +   sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>>> +   sink_fmt->field = V4L2_FIELD_NONE;
>>> +   sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>>> +   sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>>> +   sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
>>> +   sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>> +   sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
>>> +
>>> +   /* Video source pad */
>>> +   src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
>>> +   src_fmt->width = C3_ISP_DEFAULT_WIDTH;
>>> +   src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>>> +   src_fmt->field = V4L2_FIELD_NONE;
>>> +   src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>>> +   src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>> +   src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>>> +   src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>> +   src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>>> +
>>> +   /* Parameters pad */
>>> +   sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
>>> +   sink_fmt->width = 0;
>>> +   sink_fmt->height = 0;
>>> +   sink_fmt->field = V4L2_FIELD_NONE;
>>> +   sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>>> +
>>> +   /* Statistics pad */
>>> +   src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
>>> +   src_fmt->width = 0;
>>> +   src_fmt->height = 0;
>>> +   src_fmt->field = V4L2_FIELD_NONE;
>>> +   src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>>> +
>>> +   return c3_isp_core_init_routing(sd, state);
>>> +}
>>> +
>>> +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
>>> +   .enum_mbus_code = c3_isp_core_enum_mbus_code,
>>> +   .get_fmt = v4l2_subdev_get_fmt,
>>> +   .set_fmt = c3_isp_core_set_fmt,
>>> +   .enable_streams = c3_isp_core_enable_streams,
>>> +   .disable_streams = c3_isp_core_disable_streams,
>>> +   .set_routing = c3_isp_core_set_routing,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
>>> +   .pad = &c3_isp_core_pad_ops,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
>>> +   .init_state = c3_isp_core_init_state,
>>> +};
>>> +
>>> +static int c3_isp_core_link_validate(struct media_link *link)
>>> +{
>>> +   if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
>>> +           return 0;
>>> +
>>> +   return v4l2_subdev_link_validate(link);
>>> +}
>>> +
>>> +/* Media entity operations */
>>> +static const struct media_entity_operations c3_isp_core_entity_ops = {
>>> +   .link_validate = c3_isp_core_link_validate,
>>> +};
>>> +
>>> +int c3_isp_core_register(struct c3_isp_device *isp)
>>> +{
>>> +   struct c3_isp_core *core = &isp->core;
>>> +   struct v4l2_subdev *sd = &core->sd;
>>> +   int ret;
>>> +
>>> +   v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
>>> +   sd->owner = THIS_MODULE;
>>> +   sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>> +   sd->internal_ops = &c3_isp_core_internal_ops;
>>> +   snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
>>> +
>>> +   sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
>>> +   sd->entity.ops = &c3_isp_core_entity_ops;
>>> +
>>> +   core->isp = isp;
>>> +   sd->dev = isp->dev;
>>> +   v4l2_set_subdevdata(sd, core);
>>> +
>>> +   core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
>>> +   core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
>>> +   core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
>>> +   core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
>>> +   ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   ret = v4l2_subdev_init_finalize(sd);
>>> +   if (ret)
>>> +           goto err_entity_cleanup;
>>> +
>>> +   ret = v4l2_device_register_subdev(&isp->v4l2_dev, sd);
>>> +   if (ret)
>>> +           goto err_subdev_cleanup;
>>> +
>>> +   return 0;
>>> +
>>> +err_subdev_cleanup:
>>> +   v4l2_subdev_cleanup(sd);
>>> +err_entity_cleanup:
>>> +   media_entity_cleanup(&sd->entity);
>>> +   return ret;
>>> +}
>>> +
>>> +void c3_isp_core_unregister(struct c3_isp_device *isp)
>>> +{
>>> +   struct c3_isp_core *core = &isp->core;
>>> +   struct v4l2_subdev *sd = &core->sd;
>>> +
>>> +   v4l2_device_unregister_subdev(sd);
>>> +   v4l2_subdev_cleanup(sd);
>>> +   media_entity_cleanup(&sd->entity);
>>> +}
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
>>> new file mode 100644
>>> index 000000000000..a57b9f8dbc3c
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
>>> @@ -0,0 +1,486 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/clk.h>
>>> +#include <linux/device.h>
>>> +#include <linux/module.h>
>>> +#include <linux/mutex.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pm_runtime.h>
>>> +
>>> +#include <media/v4l2-common.h>
>>> +#include <media/v4l2-device.h>
>>> +#include <media/v4l2-fwnode.h>
>>> +#include <media/v4l2-mc.h>
>>> +
>>> +#include "c3-isp-common.h"
>>> +#include "c3-isp-regs.h"
>>> +
>>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
>>> +{
>>> +   return readl(isp->base + reg);
>>> +}
>>> +
>>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
>>> +{
>>> +   writel(val, isp->base + reg);
>>> +}
>>> +
>>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
>>> +{
>>> +   u32 orig, tmp;
>>> +
>>> +   orig = c3_isp_read(isp, reg);
>>> +
>>> +   tmp = orig & ~mask;
>>> +   tmp |= val & mask;
>>> +
>>> +   if (tmp != orig)
>>> +           c3_isp_write(isp, reg, tmp);
>>> +}
>>> +
>>> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp)
>>> +{
>>> +   struct media_pipeline_entity_iter iter;
>>> +   unsigned int n_video_devices = 0;
>>> +   struct media_entity *entity;
>>> +   int ret;
>>> +
>>> +   ret = media_pipeline_entity_iter_init(&isp->pipe, &iter);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   media_pipeline_for_each_entity(&isp->pipe, &iter, entity) {
>>> +           if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
>>> +                   n_video_devices++;
>>> +   }
>>> +
>>> +   media_pipeline_entity_iter_cleanup(&iter);
>>> +
>>> +   return n_video_devices == isp->pipe.start_count;
>>> +}
>> As suggested in the review of the -params.c module, I suggest to
>> implement start_streaming on the capture nodes only. From there you
>> can call enable_streams on the ISP subdevice. The ISP subdevice can
>> count how many links to resizers are enabled, and actually start its
>> operation when all the enabled ones have been started.
>>
>> The idea is to use enabled media links to identify how many capture video
>> devices are expected to be used, and only start the ISP (and the
>> downstream subdevices like the CSI-2 Adap and RX and the sensor) when
>> all of linked ones have been started.
>>
>>> +
>>> +/* PM runtime suspend */
>>> +static int __maybe_unused c3_isp_runtime_suspend(struct device *dev)
>>> +{
>>> +   struct c3_isp_device *isp = dev_get_drvdata(dev);
>>> +
>>> +   clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +/* PM runtime resume */
>>> +static int __maybe_unused c3_isp_runtime_resume(struct device *dev)
>>> +{
>>> +   struct c3_isp_device *isp = dev_get_drvdata(dev);
>>> +
>>> +   return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
>>> +}
>>> +
>>> +static const struct dev_pm_ops c3_isp_pm_ops = {
>>> +   SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>>> +                           pm_runtime_force_resume)
>>> +   SET_RUNTIME_PM_OPS(c3_isp_runtime_suspend,
>>> +                      c3_isp_runtime_resume, NULL)
>>> +};
>>> +
>>> +/* IRQ handling */
>>> +static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
>>> +{
>>> +   struct c3_isp_device *isp = dev;
>>> +   u32 status;
>>> +
>>> +   /* Get irq status and clear irq status */
>>> +   status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
>>> +   c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
>>> +
>>> +   if (status & TOP_IRQ_FRAME_DONE) {
>>> +           c3_isp_stats_done(isp);
>>> +           c3_isp_params_done(isp);
>>> +           c3_isp_captures_done(isp);
>>> +           isp->frm_sequence++;
>>> +   }
>>> +
>>> +   if (status & TOP_IRQ_STATS_ERR)
>>> +           dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
>>> +
>>> +   return IRQ_HANDLED;
>>> +}
>>> +
>>> +/* Subdev notifier register */
>>> +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
>>> +                          struct v4l2_subdev *sd,
>>> +                          struct v4l2_async_connection *asc)
>>> +{
>>> +   struct c3_isp_device *isp =
>>> +           container_of(notifier, struct c3_isp_device, notifier);
>>> +   struct c3_isp_core *core = &isp->core;
>>> +   struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
>>> +   int ret;
>>> +
>>> +   ret = media_entity_get_fwnode_pad(&sd->entity,
>>> +                                     sd->fwnode, MEDIA_PAD_FL_SOURCE);
>>> +   if (ret < 0) {
>>> +           dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
>>> +           return ret;
>>> +   }
>>> +
>>> +   core->src_sd = sd;
>>> +   core->src_sd_pad = ret;
>>> +
>>> +   return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
>>> +                                          MEDIA_LNK_FL_IMMUTABLE);
>>> +}
>>> +
>>> +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
>>> +{
>>> +   struct c3_isp_device *isp =
>>> +           container_of(notifier, struct c3_isp_device, notifier);
>>> +   int ret;
>>> +
>>> +   ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
>>> +   if (ret < 0) {
>>> +           dev_err(isp->dev,
>>> +                   "Failed to register subdev nodes: %d\n", ret);
>>> +           return ret;
>>> +   }
>>> +
>>> +   dev_info(isp->dev, "notify complete\n");
>>> +
>>> +   return media_device_register(&isp->media_dev);
>>> +}
>>> +
>>> +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
>>> +{
>>> +   struct c3_isp_device *isp =
>>> +           container_of(asc->notifier, struct c3_isp_device, notifier);
>>> +
>>> +   media_device_unregister(&isp->media_dev);
>>> +}
>>> +
>>> +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
>>> +   .bound = c3_isp_notify_bound,
>>> +   .complete = c3_isp_notify_complete,
>>> +   .destroy = c3_isp_notify_destroy,
>>> +};
>>> +
>>> +static int c3_isp_async_nf_register(struct c3_isp_device *isp)
>>> +{
>>> +   struct v4l2_async_connection *asc;
>>> +   struct fwnode_handle *ep;
>>> +   int ret;
>>> +
>>> +   v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
>>> +
>>> +   ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
>>> +                                        FWNODE_GRAPH_ENDPOINT_NEXT);
>>> +   if (!ep)
>>> +           return -ENOTCONN;
>>> +
>>> +   asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
>>> +                                         struct v4l2_async_connection);
>>> +   if (IS_ERR(asc)) {
>>> +           fwnode_handle_put(ep);
>>> +           return PTR_ERR(asc);
>>> +   }
>>> +
>>> +   fwnode_handle_put(ep);
>>> +
>>> +   isp->notifier.ops = &c3_isp_notify_ops;
>>> +   ret = v4l2_async_nf_register(&isp->notifier);
>>> +   if (ret)
>>> +           v4l2_async_nf_cleanup(&isp->notifier);
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
>>> +{
>>> +   v4l2_async_nf_unregister(&isp->notifier);
>>> +   v4l2_async_nf_cleanup(&isp->notifier);
>>> +}
>>> +
>>> +static int c3_isp_v4l2_register(struct c3_isp_device *isp)
>>> +{
>>> +   struct media_device *media_dev = &isp->media_dev;
>>> +   struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
>>> +   int ret;
>>> +
>>> +   /* Initialize media device */
>>> +   strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
>>> +           sizeof(media_dev->model));
>>> +   media_dev->dev = isp->dev;
>>> +
>>> +   media_device_init(media_dev);
>>> +
>>> +   /* Initialize v4l2 device */
>>> +   v4l2_dev->mdev = media_dev;
>>> +   strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
>>> +           sizeof(v4l2_dev->name));
>>> +
>>> +   ret = v4l2_device_register(isp->dev, v4l2_dev);
>>> +   if (ret) {
>>> +           media_device_cleanup(media_dev);
>>> +           dev_err(isp->dev,
>>> +                   "Failed to register V4L2 device: %d\n", ret);
>>> +   }
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
>>> +{
>>> +   v4l2_device_unregister(&isp->v4l2_dev);
>>> +   media_device_cleanup(&isp->media_dev);
>>> +}
>>> +
>>> +static void c3_isp_remove_links(struct c3_isp_device *isp)
>>> +{
>>> +   unsigned int i;
>>> +
>>> +   media_entity_remove_links(&isp->core.sd.entity);
>>> +
>>> +   for (i = 0; i < C3_ISP_NUM_RSZ; i++)
>>> +           media_entity_remove_links(&isp->resizers[i].sd.entity);
>>> +
>>> +   for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
>>> +           media_entity_remove_links(&isp->caps[i].vdev.entity);
>>> +}
>>> +
>>> +static int c3_isp_create_links(struct c3_isp_device *isp)
>>> +{
>>> +   unsigned int i;
>>> +   int ret;
>>> +
>>> +   for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
>>> +           ret = media_create_pad_link(&isp->resizers[i].sd.entity,
>>> +                                       C3_ISP_RESIZER_PAD_SOURCE,
>>> +                                       &isp->resizers[i].cap->vdev.entity,
>>> +                                       0, MEDIA_LNK_FL_ENABLED);
>> This could be made IMMUTABLE
>>
>>> +           if (ret) {
>>> +                   dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
>>> +                   goto err_remove_links;
>>> +           }
>>> +
>>> +           ret = media_create_pad_link(&isp->core.sd.entity,
>>> +                                       C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>> +                                       &isp->resizers[i].sd.entity,
>>> +                                       C3_ISP_RESIZER_PAD_SINK,
>>> +                                       MEDIA_LNK_FL_ENABLED);
>>> +           if (ret) {
>>> +                   dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
>>> +                   goto err_remove_links;
>>> +           }
>>> +   }
>>> +
>>> +   ret = media_create_pad_link(&isp->core.sd.entity,
>>> +                               C3_ISP_CORE_PAD_SOURCE_STATS,
>>> +                               &isp->stats.vdev.entity,
>>> +                               0, MEDIA_LNK_FL_ENABLED);
>>> +   if (ret) {
>>> +           dev_err(isp->dev, "Failed to link core and stats\n");
>>> +           goto err_remove_links;
>>> +   }
>>> +
>>> +   ret = media_create_pad_link(&isp->params.vdev.entity, 0,
>>> +                               &isp->core.sd.entity,
>>> +                               C3_ISP_CORE_PAD_SINK_PARAMS,
>>> +                               MEDIA_LNK_FL_ENABLED);
>>> +   if (ret) {
>>> +           dev_err(isp->dev, "Failed to link params and core\n");
>>> +           goto err_remove_links;
>>> +   }
>>> +
>>> +   return 0;
>>> +
>>> +err_remove_links:
>>> +   c3_isp_remove_links(isp);
>>> +   return ret;
>>> +}
>>> +
>>> +static int c3_isp_videos_register(struct c3_isp_device *isp)
>>> +{
>>> +   int ret;
>>> +
>>> +   ret = c3_isp_captures_register(isp);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   ret = c3_isp_stats_register(isp);
>>> +   if (ret)
>>> +           goto err_captures_unregister;
>>> +
>>> +   ret = c3_isp_params_register(isp);
>>> +   if (ret)
>>> +           goto err_stats_unregister;
>>> +
>>> +   ret = c3_isp_create_links(isp);
>>> +   if (ret)
>>> +           goto err_params_unregister;
>>> +
>>> +   return 0;
>>> +
>>> +err_params_unregister:
>>> +   c3_isp_params_unregister(isp);
>>> +err_stats_unregister:
>>> +   c3_isp_stats_unregister(isp);
>>> +err_captures_unregister:
>>> +   c3_isp_captures_unregister(isp);
>>> +   return ret;
>>> +}
>>> +
>>> +static void c3_isp_videos_unregister(struct c3_isp_device *isp)
>>> +{
>>> +   c3_isp_remove_links(isp);
>>> +   c3_isp_params_unregister(isp);
>>> +   c3_isp_stats_unregister(isp);
>>> +   c3_isp_captures_unregister(isp);
>>> +}
>>> +
>>> +static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
>>> +{
>>> +   const struct c3_isp_info *info = isp->info;
>>> +   int ret;
>>> +   u32 i;
>>> +
>>> +   for (i = 0; i < info->clock_num; i++)
>>> +           isp->clks[i].id = info->clocks[i];
>>> +
>>> +   ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   for (i = 0; i < info->clock_num; i++) {
>>> +           if (!info->clock_rates[i])
>>> +                   continue;
>>> +           ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);
>>> +           if (ret) {
>>> +                   dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
>>> +                           info->clock_rates[i]);
>>> +                   return ret;
>>> +           }
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_probe(struct platform_device *pdev)
>>> +{
>>> +   struct device *dev = &pdev->dev;
>>> +   struct c3_isp_device *isp;
>>> +   int irq;
>>> +   int ret;
>>> +
>>> +   isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
>>> +   if (!isp)
>>> +           return -ENOMEM;
>>> +
>>> +   isp->info = of_device_get_match_data(dev);
>>> +   isp->dev = dev;
>>> +
>>> +   isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
>>> +   if (IS_ERR(isp->base))
>>> +           return dev_err_probe(dev, PTR_ERR(isp->base),
>>> +                                "Failed to ioremap resource\n");
>>> +
>>> +   irq = platform_get_irq(pdev, 0);
>>> +   if (irq < 0)
>>> +           return irq;
>>> +
>>> +   ret = c3_isp_cfg_clocks(isp);
>>> +   if (ret)
>>> +           return dev_err_probe(dev, ret, "Failed to configure clocks\n");
>>> +
>>> +   platform_set_drvdata(pdev, isp);
>>> +
>>> +   pm_runtime_enable(dev);
>>> +
>>> +   ret = c3_isp_v4l2_register(isp);
>>> +   if (ret)
>>> +           goto err_runtime_disable;
>>> +
>>> +   ret = c3_isp_core_register(isp);
>>> +   if (ret)
>>> +           goto err_v4l2_unregister;
>>> +
>>> +   ret = c3_isp_resizers_register(isp);
>>> +   if (ret)
>>> +           goto err_core_unregister;
>>> +
>>> +   ret = c3_isp_async_nf_register(isp);
>>> +   if (ret)
>>> +           goto err_resizers_unregister;
>>> +
>>> +   ret = c3_isp_videos_register(isp);
>>> +   if (ret)
>>> +           goto err_nf_unregister;
>>> +
>>> +   ret = devm_request_irq(dev, irq,
>>> +                          c3_isp_irq_handler, IRQF_SHARED,
>>> +                          dev_driver_string(dev), isp);
>>> +   if (ret)
>>> +           goto err_streams_unregister;
>> I would request the IRQ before registering devices to userspace.
>>
>>> +
>>> +   mutex_init(&isp->lock);
>>> +
>>> +   return 0;
>>> +
>>> +err_streams_unregister:
>>> +   c3_isp_videos_unregister(isp);
>>> +err_nf_unregister:
>>> +   c3_isp_async_nf_unregister(isp);
>>> +err_resizers_unregister:
>>> +   c3_isp_resizers_unregister(isp);
>>> +err_core_unregister:
>>> +   c3_isp_core_unregister(isp);
>>> +err_v4l2_unregister:
>>> +   c3_isp_v4l2_unregister(isp);
>>> +err_runtime_disable:
>>> +   pm_runtime_disable(dev);
>>> +   return ret;
>>> +};
>>> +
>>> +static void c3_isp_remove(struct platform_device *pdev)
>>> +{
>>> +   struct c3_isp_device *isp = platform_get_drvdata(pdev);
>>> +
>>> +   mutex_destroy(&isp->lock);
>>> +   c3_isp_videos_unregister(isp);
>>> +   c3_isp_async_nf_unregister(isp);
>>> +   c3_isp_core_unregister(isp);
>>> +   c3_isp_resizers_unregister(isp);
>>> +   c3_isp_v4l2_unregister(isp);
>>> +   pm_runtime_disable(isp->dev);
>>> +};
>>> +
>>> +static const struct c3_isp_info isp_info = {
>>> +   .clocks = {"vapb", "isp0"},
>>> +   .clock_rates = {0, 400000000},
>>> +   .clock_num = 2
>>> +};
>>> +
>>> +static const struct of_device_id c3_isp_of_match[] = {
>>> +   { .compatible = "amlogic,c3-isp",
>>> +     .data = &isp_info },
>>> +   { },
>>> +};
>>> +MODULE_DEVICE_TABLE(of, c3_isp_of_match);
>>> +
>>> +static struct platform_driver c3_isp_driver = {
>>> +   .probe = c3_isp_probe,
>>> +   .remove = c3_isp_remove,
>>> +   .driver = {
>>> +           .name = "c3-isp",
>>> +           .of_match_table = c3_isp_of_match,
>>> +           .pm = &c3_isp_pm_ops,
>>> +   },
>>> +};
>>> +
>>> +module_platform_driver(c3_isp_driver);
>>> +
>>> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
>>> +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
>>> new file mode 100644
>>> index 000000000000..8a6b7ce86eaf
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
>>> @@ -0,0 +1,857 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/pm_runtime.h>
>>> +
>>> +#include <media/v4l2-ctrls.h>
>>> +#include <media/v4l2-event.h>
>> Do you need these ?
>>
>>> +#include <media/v4l2-ioctl.h>
>>> +#include <media/v4l2-mc.h>
>>> +#include <media/videobuf2-dma-contig.h>
>> See below about using the dma-contig vb2 ops
>>
>>> +
>>> +#include "c3-isp-common.h"
>>> +#include "c3-isp-regs.h"
>>> +#include "include/uapi/c3-isp-config.h"
>>> +
>>> +typedef void (*block_handler)(struct c3_isp_device *isp,
>>> +                         struct c3_isp_param_block_header *block);
>>> +
>>> +struct c3_isp_block_handler {
>>> +   size_t size;
>>> +   block_handler handler;
>>> +};
>>> +
>>> +/* Hardware configuration */
>>> +
>>> +static void c3_isp_params_cfg_wb_change(struct c3_isp_device *isp,
>>> +                                   struct c3_isp_param_block_header *block)
>>> +{
>>> +   struct wb_change_cfg *wb = (struct wb_change_cfg *)block;
>>> +
>>> +   if (!block->enabled) {
>>> +           c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
>>> +                              TOP_BEO_CTRL_WB_EN, false);
>>> +           return;
>>> +   }
>>> +
>>> +   c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
>>> +                      TOP_BEO_CTRL_WB_EN, TOP_BEO_CTRL_WB_EN);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, LSWB_WB_GAIN0_MASK,
>>> +                      wb->wb_gain[0] << LSWB_WB_GAIN0_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0,
>>> +                      LSWB_WB_GAIN1_MASK, wb->wb_gain[1]);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, LSWB_WB_GAIN2_MASK,
>>> +                      wb->wb_gain[2] << LSWB_WB_GAIN2_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1,
>>> +                      LSWB_WB_GAIN3_MASK, wb->wb_gain[3]);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN2,
>>> +                      LSWB_WB_GAIN4_MASK, wb->wb_gain[4]);
>>> +
>>> +   c3_isp_write(isp, ISP_LSWB_WB_LIMIT0,
>>> +                LSWB_WB_LIMIT0(wb->wb_limit[0]) | LSWB_WB_LIMIT1(wb->wb_limit[1]));
>>> +
>>> +   c3_isp_write(isp, ISP_LSWB_WB_LIMIT1,
>>> +                LSWB_WB_LIMIT2(wb->wb_limit[2]) | LSWB_WB_LIMIT3(wb->wb_limit[3]));
>>> +
>>> +   c3_isp_update_bits(isp, ISP_LSWB_WB_LIMIT2, LSWB_WB_LIMIT4_MASK,
>>> +                      wb->wb_limit[4]);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_BL12_GRBGI0_MASK,
>>> +                      wb->ae_bl12_grbgi[0]);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_GAIN_GRBGI0_MASK,
>>> +                      wb->ae_gain_grbgi[0] << AE_CRTL2_GAIN_GRBGI0_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_BL12_GRBGI1_MASK,
>>> +                      wb->ae_bl12_grbgi[1]);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_GAIN_GRBGI1_MASK,
>>> +                      wb->ae_gain_grbgi[1] << AE_CRTL2_GAIN_GRBGI1_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_BL12_GRBGI2_MASK,
>>> +                      wb->ae_bl12_grbgi[2]);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_GAIN_GRBGI2_MASK,
>>> +                      wb->ae_gain_grbgi[2] << AE_CRTL2_GAIN_GRBGI2_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_BL12_GRBGI3_MASK,
>>> +                      wb->ae_bl12_grbgi[3]);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_GAIN_GRBGI3_MASK,
>>> +                      wb->ae_gain_grbgi[3] << AE_CRTL2_GAIN_GRBGI3_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_BL12_GRBGI4_MASK,
>>> +                      wb->ae_bl12_grbgi[4]);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_GAIN_GRBGI4_MASK,
>>> +                      wb->ae_gain_grbgi[4] << AE_CRTL2_GAIN_GRBGI4_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_wb_luma(struct c3_isp_device *isp,
>>> +                                 struct c3_isp_param_block_header *block)
>>> +{
>>> +   struct wb_luma_cfg *wb_luma = (struct wb_luma_cfg *)block;
>>> +
>>> +   if (!block->enabled)
>>> +           return;
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_0,
>>> +                      AWB_STAT_BLC20_GR_MASK, wb_luma->awb_stat_blc20[0]);
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_1,
>>> +                      AWB_STAT_BLC20_R_MASK, wb_luma->awb_stat_blc20[1]);
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_2,
>>> +                      AWB_STAT_BLC20_B_MASK, wb_luma->awb_stat_blc20[2]);
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_3,
>>> +                      AWB_STAT_BLC20_GB_MASK, wb_luma->awb_stat_blc20[3]);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_0,
>>> +                      AWB_STAT_GAIN10_GR_MASK, wb_luma->awb_stat_gain10[0]);
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_1,
>>> +                      AWB_STAT_GAIN10_R_MASK, wb_luma->awb_stat_gain10[1]);
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_2,
>>> +                      AWB_STAT_GAIN10_B_MASK, wb_luma->awb_stat_gain10[2]);
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_3,
>>> +                      AWB_STAT_GAIN10_GB_MASK, wb_luma->awb_stat_gain10[3]);
>>> +
>>> +   c3_isp_write(isp, ISP_AWB_STAT_SATUR_CTRL,
>>> +                AWB_STAT_SATUR_LOW(wb_luma->awb_stat_satur_low) |
>>> +                AWB_STAT_SATUR_HIGH(wb_luma->awb_stat_satur_high));
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_wb_triangle(struct c3_isp_device *isp,
>>> +                                     struct c3_isp_param_block_header *block)
>>> +{
>>> +   struct wb_triangle_cfg *wb = (struct wb_triangle_cfg *)block;
>>> +
>>> +   if (!block->enabled)
>>> +           return;
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_CTRL2, AWB_STAT_SATUR_VALID_MASK,
>>> +                      wb->awb_stat_satur_vald);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MIN_MASK,
>>> +                      wb->awb_stat_rg_min);
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MAX_MASK,
>>> +                      wb->awb_stat_rg_max << AWB_STAT_RG_MAX_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MIN_MASK,
>>> +                      wb->awb_stat_bg_min);
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MAX_MASK,
>>> +                      wb->awb_stat_bg_max << AWB_STAT_BG_MAX_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_LOW_MASK,
>>> +                      wb->awb_stat_rg_low);
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_HIGH_MASK,
>>> +                      wb->awb_stat_rg_high << AWB_STAT_RG_HIGH_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_LOW_MASK,
>>> +                      wb->awb_stat_bg_low);
>>> +   c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_HIGH_MASK,
>>> +                      wb->awb_stat_bg_high << AWB_STAT_BG_HIGH_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_awb_stats(struct c3_isp_device *isp,
>>> +                                   struct c3_isp_param_block_header *block)
>>> +{
>>> +   struct awb_stats_cfg *awb_stats = (struct awb_stats_cfg *)block;
>>> +   u32 *weight = awb_stats->awb_stat_blk_weight;
>>> +   int idx_base;
>>> +   int group;
>>> +   int i;
>>> +
>>> +   if (!block->enabled)
>>> +           return;
>>> +
>>> +   c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AWB_STAT_SWITCH_MASK,
>>> +                      awb_stats->awb_stat_switch << TOP_3A_AWB_STAT_SWITCH_SHIFT);
>>> +
>>> +   /* Calculate the group number */
>>> +   group = AWB_BLOCK_WT_NUM / AWB_BLK_WT_DATA_NUM_OF_GROUP;
>>> +
>>> +   /* Set the weight address to 0 position */
>>> +   c3_isp_write(isp, ISP_AWB_BLK_WT_ADDR, 0);
>>> +   for (i = 0; i < group; i++) {
>> you can now use
>>          for (unsigned int i = 0; ...)
>>
>> if 'i' is not needed outside of the loop
>>
>>> +           idx_base = i * AWB_BLK_WT_DATA_NUM_OF_GROUP;
>>> +           c3_isp_write(isp, ISP_AWB_BLK_WT_DATA,
>>> +                        AWB_BLK_WT_DATA0(weight[idx_base + 0]) |
>>> +                        AWB_BLK_WT_DATA1(weight[idx_base + 1]) |
>>> +                        AWB_BLK_WT_DATA2(weight[idx_base + 2]) |
>>> +                        AWB_BLK_WT_DATA3(weight[idx_base + 3]) |
>>> +                        AWB_BLK_WT_DATA4(weight[idx_base + 4]) |
>>> +                        AWB_BLK_WT_DATA5(weight[idx_base + 5]) |
>>> +                        AWB_BLK_WT_DATA6(weight[idx_base + 6]) |
>>> +                        AWB_BLK_WT_DATA7(weight[idx_base + 7]));
>>> +   }
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_ae_stats(struct c3_isp_device *isp,
>>> +                                  struct c3_isp_param_block_header *block)
>>> +{
>>> +   struct ae_stats_cfg *ae_stats = (struct ae_stats_cfg *)block;
>>> +   u32 *weight = ae_stats->ae_stat_blk_weight;
>>> +   int idx_base;
>>> +   int group;
>>> +   int i;
>>> +
>>> +   if (!block->enabled)
>>> +           return;
>>> +
>>> +   c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AE_STAT_SWITCH_MASK,
>>> +                      ae_stats->ae_stat_switch << TOP_3A_AE_STAT_SWITCH_SHIFT);
>>> +
>>> +   /* Calculate the group number */
>>> +   group = AE_BLOCK_WT_NUM / AE_BLK_WT_DATA_NUM_OF_GROUP;
>>> +
>>> +   /* Set the weight address to 0 position */
>>> +   c3_isp_write(isp, ISP_AE_BLK_WT_ADDR, 0);
>>> +   for (i = 0; i < group; i++) {
>>> +           idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
>>> +           c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
>>> +                        AE_BLK_WT_DATA0(weight[idx_base + 0]) |
>>> +                        AE_BLK_WT_DATA1(weight[idx_base + 1]) |
>>> +                        AE_BLK_WT_DATA2(weight[idx_base + 2]) |
>>> +                        AE_BLK_WT_DATA3(weight[idx_base + 3]) |
>>> +                        AE_BLK_WT_DATA4(weight[idx_base + 4]) |
>>> +                        AE_BLK_WT_DATA5(weight[idx_base + 5]) |
>>> +                        AE_BLK_WT_DATA6(weight[idx_base + 6]) |
>>> +                        AE_BLK_WT_DATA7(weight[idx_base + 7]));
>>> +   }
>>> +
>>> +   /* Write the last weight data */
>>> +   idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
>>> +   c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
>>> +                AE_BLK_WT_DATA0(weight[idx_base + 0]) |
>>> +                AE_BLK_WT_DATA1(weight[idx_base + 1]) |
>>> +                AE_BLK_WT_DATA2(weight[idx_base + 2]) |
>>> +                AE_BLK_WT_DATA3(weight[idx_base + 3]) |
>>> +                AE_BLK_WT_DATA4(weight[idx_base + 4]) |
>>> +                AE_BLK_WT_DATA5(weight[idx_base + 5]) |
>>> +                AE_BLK_WT_DATA6(weight[idx_base + 6]));
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_af_stats(struct c3_isp_device *isp,
>>> +                                  struct c3_isp_param_block_header *block)
>>> +{
>>> +   struct af_stats_cfg *af_stats = (struct af_stats_cfg *)block;
>>> +
>>> +   if (!block->enabled)
>>> +           return;
>>> +
>>> +   c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AF_STAT_SWITCH_MASK,
>>> +                      af_stats->af_stat_switch << TOP_3A_AF_STAT_SWITCH_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_pst_gamma(struct c3_isp_device *isp,
>>> +                                   struct c3_isp_param_block_header *block)
>>> +{
>>> +   struct pst_gamma_cfg *gamma = (struct pst_gamma_cfg *)block;
>>> +   int idx_base;
>>> +   int i, j;
>>> +
>>> +   if (!block->enabled) {
>>> +           c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, false);
>>> +           return;
>>> +   }
>>> +
>>> +   c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, TOP_BED_GAMMA_EN);
>>> +
>>> +   for (j = 0; j < GAMMA_LUT_GROUP_NUM; j++) {
>>> +           c3_isp_write(isp, ISP_PST_GAMMA_LUT_ADDR, PST_GAMMA_LUT_ADDR(j));
>>> +
>>> +           /* Calculate the block number */
>>> +           for (i = 0; i < GAMMA_LUT_POINT_NUM / GAMMA_LUT_DATA_NUM_OF_GROUP; i++) {
>>> +                   idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
>>> +                   c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>>> +                                PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]) |
>>> +                                PST_GAMMA_LUT_DATA1(gamma->pst_gamma_lut[j][idx_base + 1]));
>>> +           }
>>> +
>>> +           /* Write the last one lut data of group j */
>>> +           idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
>>> +           c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>>> +                        PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]));
>>> +   }
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_dmsc(struct c3_isp_device *isp,
>>> +                              struct c3_isp_param_block_header *block)
>>> +{
>>> +   if (!block->enabled) {
>>> +           c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, false);
>>> +           return;
>>> +   }
>>> +
>>> +   c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, TOP_BED_DMSC_EN);
>>> +}
>>> +
>>> +/* Configure 4 x 3 ccm matrix */
>>> +static void c3_isp_params_cfg_ccm(struct c3_isp_device *isp,
>>> +                             struct c3_isp_param_block_header *block)
>>> +{
>>> +   struct ccm_cfg *ccm = (struct ccm_cfg *)block;
>>> +
>>> +   if (!block->enabled) {
>>> +           c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, false);
>>> +           return;
>>> +   }
>>> +
>>> +   c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, TOP_BED_CCM_EN);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_00_MASK,
>>> +                      ccm->ccm_4x3matrix[0][0]);
>>> +   c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_01_MASK,
>>> +                      ccm->ccm_4x3matrix[0][1] << CCM_MTX_01_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_02_MASK,
>>> +                      ccm->ccm_4x3matrix[0][2]);
>>> +   c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_03_MASK,
>>> +                      ccm->ccm_4x3matrix[0][3] << CCM_MTX_03_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_10_MASK,
>>> +                      ccm->ccm_4x3matrix[1][0]);
>>> +   c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_11_MASK,
>>> +                      ccm->ccm_4x3matrix[1][1] << CCM_MTX_11_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_12_MASK,
>>> +                      ccm->ccm_4x3matrix[1][2]);
>>> +   c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_13_MASK,
>>> +                      ccm->ccm_4x3matrix[1][3] << CCM_MTX_13_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_20_MASK,
>>> +                      ccm->ccm_4x3matrix[2][0]);
>>> +   c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_21_MASK,
>>> +                      ccm->ccm_4x3matrix[2][1] << CCM_MTX_21_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_22_MASK,
>>> +                      ccm->ccm_4x3matrix[2][2]);
>>> +   c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_23_MASK,
>>> +                      ccm->ccm_4x3matrix[2][3] << CCM_MTX_23_SHIFT);
>>> +}
>>> +
>>> +/* Configure color space conversion matrix parameters */
>>> +static void c3_isp_params_cfg_csc(struct c3_isp_device *isp,
>>> +                             struct c3_isp_param_block_header *block)
>>> +{
>>> +   struct csc_cfg *csc = (struct csc_cfg *)block;
>>> +
>>> +   if (!block->enabled) {
>>> +           c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, false);
>>> +           return;
>>> +   }
>>> +
>>> +   c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, TOP_BED_CM0_EN);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST0_MASK,
>>> +                      csc->cm0_offset_inp[0]);
>>> +   c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST1_MASK,
>>> +                      csc->cm0_offset_inp[1] << CM0_INP_OFST1_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CM0_INP_OFST2, CM0_INP_OFST2_MASK,
>>> +                      csc->cm0_offset_inp[2]);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_00_MASK,
>>> +                      csc->cm0_3x3matrix[0][0]);
>>> +   c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_01_MASK,
>>> +                      csc->cm0_3x3matrix[0][1] << CM0_MTX_01_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_02_MASK,
>>> +                      csc->cm0_3x3matrix[0][2]);
>>> +   c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_10_MASK,
>>> +                      csc->cm0_3x3matrix[1][0] << CM0_MTX_10_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_11_MASK,
>>> +                      csc->cm0_3x3matrix[1][1]);
>>> +   c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_12_MASK,
>>> +                      csc->cm0_3x3matrix[1][2] << CM0_MTX_12_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_20_MASK,
>>> +                      csc->cm0_3x3matrix[2][0]);
>>> +   c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_21_MASK,
>>> +                      csc->cm0_3x3matrix[2][1] << CM0_MTX_21_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_MTX_22_MASK,
>>> +                      csc->cm0_3x3matrix[2][2]);
>>> +   c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_OFST_OUP0_MASK,
>>> +                      csc->cm0_offset_oup[0] << CM0_OFST_OUP0_SHIFT);
>>> +
>>> +   c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP1_MASK,
>>> +                      csc->cm0_offset_oup[1]);
>>> +   c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP2_MASK,
>>> +                      csc->cm0_offset_oup[2] << CM0_OFST_OUP2_SHIFT);
>>> +   c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_MTX_RS_MASK,
>>> +                      csc->cm0_3x3mtrx_rs << CM0_MTX_RS_SHIFT);
>>> +}
>>> +
>>> +/* Set blc offset of each color channel */
>>> +static void c3_isp_params_cfg_blc(struct c3_isp_device *isp,
>>> +                             struct c3_isp_param_block_header *block)
>>> +{
>>> +   struct blc_cfg *blc = (struct blc_cfg *)block;
>>> +
>>> +   if (!block->enabled) {
>>> +           c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN, false);
>>> +           return;
>>> +   }
>>> +
>>> +   c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN,
>>> +                      TOP_BEO_CTRL_BLC_EN);
>>> +
>>> +   c3_isp_write(isp, ISP_FED_BL_OFST_GR, FED_BL_GR_OFST(blc->fe_bl_ofst[0]));
>>> +   c3_isp_write(isp, ISP_FED_BL_OFST_R, FED_BL_R_OFST(blc->fe_bl_ofst[1]));
>>> +   c3_isp_write(isp, ISP_FED_BL_OFST_B, FED_BL_B_OFST(blc->fe_bl_ofst[2]));
>>> +   c3_isp_write(isp, ISP_FED_BL_OFST_GB, FED_BL_GB_OFST(blc->fe_bl_ofst[3]));
>>> +   c3_isp_write(isp, ISP_FED_BL_OFST_IR, FED_BL_IR_OFST(blc->fe_bl_ofst[4]));
>>> +
>>> +   c3_isp_write(isp, ISP_LSWB_BLC_OFST0,
>>> +                LSWB_BLC_OFST0(blc->blc_ofst[0]) | LSWB_BLC_OFST1(blc->blc_ofst[1]));
>>> +   c3_isp_write(isp, ISP_LSWB_BLC_OFST1,
>>> +                LSWB_BLC_OFST2(blc->blc_ofst[2]) | LSWB_BLC_OFST3(blc->blc_ofst[3]));
>>> +   c3_isp_write(isp, ISP_LSWB_BLC_OFST2, LSWB_BLC_OFST4(blc->blc_ofst[4]));
>>> +}
>>> +
>>> +static const struct c3_isp_block_handler c3_isp_block_handlers[] = {
>>> +   [C3_ISP_PARAM_BLOCK_WB_CHANGE] = {
>>> +           .size = sizeof(struct wb_change_cfg),
>>> +           .handler = c3_isp_params_cfg_wb_change,
>>> +   },
>>> +   [C3_ISP_PARAM_BLOCK_WB_LUMA] = {
>>> +           .size = sizeof(struct wb_luma_cfg),
>>> +           .handler = c3_isp_params_cfg_wb_luma,
>>> +   },
>>> +   [C3_ISP_PARAM_BLOCK_WB_TRIANGLE] = {
>>> +           .size = sizeof(struct wb_triangle_cfg),
>>> +           .handler = c3_isp_params_cfg_wb_triangle,
>>> +   },
>>> +   [C3_ISP_PARAM_BLOCK_AWB_STATS] = {
>>> +           .size = sizeof(struct awb_stats_cfg),
>>> +           .handler = c3_isp_params_cfg_awb_stats,
>>> +   },
>>> +   [C3_ISP_PARAM_BLOCK_AE_STATS] = {
>>> +           .size = sizeof(struct ae_stats_cfg),
>>> +           .handler = c3_isp_params_cfg_ae_stats,
>>> +   },
>>> +   [C3_ISP_PARAM_BLOCK_AF_STATS] = {
>>> +           .size = sizeof(struct af_stats_cfg),
>>> +           .handler = c3_isp_params_cfg_af_stats,
>>> +   },
>>> +   [C3_ISP_PARAM_BLOCK_PST_GAMMA] = {
>>> +           .size = sizeof(struct pst_gamma_cfg),
>>> +           .handler = c3_isp_params_cfg_pst_gamma,
>>> +   },
>>> +   [C3_ISP_PARAM_BLOCK_DMSC] = {
>>> +           .size = sizeof(struct dmsc_cfg),
>>> +           .handler = c3_isp_params_cfg_dmsc,
>>> +   },
>>> +   [C3_ISP_PARAM_BLOCK_CCM] = {
>>> +           .size = sizeof(struct ccm_cfg),
>>> +           .handler = c3_isp_params_cfg_ccm,
>>> +   },
>>> +   [C3_ISP_PARAM_BLOCK_CSC] = {
>>> +           .size = sizeof(struct csc_cfg),
>>> +           .handler = c3_isp_params_cfg_csc,
>>> +   },
>>> +   [C3_ISP_PARAM_BLOCK_BLC] = {
>>> +           .size = sizeof(struct blc_cfg),
>>> +           .handler = c3_isp_params_cfg_blc,
>>> +   },
>>> +};
>>> +
>>> +static enum vb2_buffer_state
>>> +c3_isp_params_cfg_blocks(struct c3_isp_params *params)
>>> +{
>>> +   struct c3_isp_params_buffer *config = params->buff->vaddr;
>>> +   enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
>>> +   size_t block_offset = 0;
>>> +   size_t max_offset = 0;
>>> +
>>> +   if (config->total_size > C3_ISP_PARAMS_MAX_SIZE) {
>>> +           dev_dbg(params->isp->dev, "Invalid parameters buffer size %lu\n",
>>> +                   config->total_size);
>>> +           state = VB2_BUF_STATE_ERROR;
>>> +           goto err_return_state;
>>> +   }
>> I suggest to move validation of the parameters buffer to .buf_prepare
>> time.
>>
>> This function is called in irq context, and it's better to do all
>> validation check at buffer queuing time instead.
>>
>> You can have a look at the rkisp1-params.c module, where
>> rkisp1_params_prepare_ext_params does the validation in the
>> .buf_prepare call path
>>
>>> +
>>> +   /* Ensure config->data has a full struct c3_isp_param_block_header */
>>> +   max_offset = config->total_size - sizeof(struct c3_isp_param_block_header);
>>> +
>>> +   while (block_offset <= max_offset) {
>>> +           const struct c3_isp_block_handler *block_handler;
>>> +           struct c3_isp_param_block_header *block;
>>> +
>>> +           block = (struct c3_isp_param_block_header *)
>>> +                    &config->data[block_offset];
>>> +
>>> +           if (block->type >= C3_ISP_PARAM_BLOCK_SENTINEL) {
>>> +                   dev_dbg(params->isp->dev, "Invalid parameters block type\n");
>>> +                   state = VB2_BUF_STATE_ERROR;
>>> +                   goto err_return_state;
>>> +           }
>>> +
>>> +           block_handler = &c3_isp_block_handlers[block->type];
>>> +           if (block->size != block_handler->size) {
>>> +                   dev_dbg(params->isp->dev, "Invalid parameters block size\n");
>>> +                   state = VB2_BUF_STATE_ERROR;
>>> +                   goto err_return_state;
>>> +           }
>>> +
>>> +           block_handler->handler(params->isp, block);
>>> +
>>> +           block_offset += block->size;
>>> +   }
>>> +
>>> +err_return_state:
>>> +   return state;
>>> +}
>>> +
>>> +/* Initialize ISP pipeline */
>>> +static int c3_isp_params_start(struct c3_isp_params *params)
>>> +{
>>> +   enum vb2_buffer_state state;
>>> +   unsigned long flags;
>>> +
>>> +   /* Reset these controllers */
>>> +   c3_isp_write(params->isp, ISP_TOP_FEO_CTRL0, TOP_FEO_CTRL0_ALL_DIS);
>>> +   c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_0, TOP_FEO_CTRL1_0_ALL_DIS);
>>> +   c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_1, TOP_FEO_CTRL1_1_ALL_DIS);
>>> +   c3_isp_write(params->isp, ISP_TOP_FED_CTRL, TOP_FED_CTRL_ALL_DIS);
>>> +   c3_isp_write(params->isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_ALL_DIS);
>>> +   c3_isp_write(params->isp, ISP_TOP_BED_CTRL, TOP_BED_CTRL_ALL_DIS);
>>> +
>>> +   spin_lock_irqsave(&params->buff_lock, flags);
>>> +
>>> +   /* Only use the first buffer to initialize ISP */
>>> +   params->buff = list_first_entry_or_null(&params->pending,
>>> +                                           struct c3_isp_vb2_buffer, list);
>>> +   if (!params->buff) {
>>> +           spin_unlock_irqrestore(&params->buff_lock, flags);
>>> +           return -EINVAL;
>>> +   }
>>> +
>>> +   state = c3_isp_params_cfg_blocks(params);
>>> +
>>> +   spin_unlock_irqrestore(&params->buff_lock, flags);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +/* V4L2 video operations */
>>> +
>>> +static void c3_isp_params_return_buffers(struct c3_isp_params *params,
>>> +                                    enum vb2_buffer_state state)
>>> +{
>>> +   unsigned long flags;
>>> +   struct c3_isp_vb2_buffer *buff;
>>> +
>>> +   spin_lock_irqsave(&params->buff_lock, flags);
>>> +
>>> +   while (!list_empty(&params->pending)) {
>>> +           buff = list_first_entry(&params->pending,
>>> +                                   struct c3_isp_vb2_buffer, list);
>>> +           list_del(&buff->list);
>>> +           vb2_buffer_done(&buff->vb.vb2_buf, state);
>>> +   }
>>> +
>>> +   spin_unlock_irqrestore(&params->buff_lock, flags);
>>> +}
>>> +
>>> +static int c3_isp_params_querycap(struct file *file, void *fh,
>>> +                             struct v4l2_capability *cap)
>>> +{
>>> +   strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>>> +   strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_params_enum_fmt(struct file *file, void *fh,
>>> +                             struct v4l2_fmtdesc *f)
>>> +{
>>> +   if (f->index)
>>> +           return -EINVAL;
>>> +
>>> +   f->pixelformat = V4L2_META_FMT_C3ISP_PARAMS;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_params_g_fmt(struct file *file, void *fh,
>>> +                          struct v4l2_format *f)
>>> +{
>>> +   struct c3_isp_params *params = video_drvdata(file);
>>> +
>>> +   f->fmt.meta = params->vfmt.fmt.meta;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static const struct v4l2_ioctl_ops isp_params_v4l2_ioctl_ops = {
>>> +   .vidioc_querycap                = c3_isp_params_querycap,
>>> +   .vidioc_enum_fmt_meta_out       = c3_isp_params_enum_fmt,
>>> +   .vidioc_g_fmt_meta_out          = c3_isp_params_g_fmt,
>>> +   .vidioc_s_fmt_meta_out          = c3_isp_params_g_fmt,
>>> +   .vidioc_try_fmt_meta_out        = c3_isp_params_g_fmt,
>>> +   .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>>> +   .vidioc_querybuf                = vb2_ioctl_querybuf,
>>> +   .vidioc_qbuf                    = vb2_ioctl_qbuf,
>>> +   .vidioc_expbuf                  = vb2_ioctl_expbuf,
>>> +   .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>>> +   .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>>> +   .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>>> +   .vidioc_streamon                = vb2_ioctl_streamon,
>>> +   .vidioc_streamoff               = vb2_ioctl_streamoff,
>>> +   .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>>> +   .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>>> +};
>>> +
>>> +static const struct v4l2_file_operations isp_params_v4l2_fops = {
>>> +   .open = v4l2_fh_open,
>>> +   .release = vb2_fop_release,
>>> +   .poll = vb2_fop_poll,
>>> +   .unlocked_ioctl = video_ioctl2,
>>> +   .mmap = vb2_fop_mmap,
>>> +};
>>> +
>>> +static int c3_isp_params_vb2_queue_setup(struct vb2_queue *q,
>>> +                                    unsigned int *num_buffers,
>>> +                                    unsigned int *num_planes,
>>> +                                    unsigned int sizes[],
>>> +                                    struct device *alloc_devs[])
>>> +{
>>> +   if (*num_planes) {
>>> +           if (*num_planes != 1)
>>> +                   return -EINVAL;
>>> +
>>> +           if (sizes[0] < sizeof(struct c3_isp_params_buffer))
>>> +                   return -EINVAL;
>>> +   } else {
>>> +           *num_planes = 1;
>>> +           sizes[0] = sizeof(struct c3_isp_params_buffer);
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static void c3_isp_params_vb2_buf_queue(struct vb2_buffer *vb)
>>> +{
>>> +   struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>> +   struct c3_isp_vb2_buffer *buf =
>>> +                   container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>> +   struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>>> +   unsigned long flags;
>>> +
>>> +   spin_lock_irqsave(&params->buff_lock, flags);
>>> +
>>> +   list_add_tail(&buf->list, &params->pending);
>>> +
>>> +   spin_unlock_irqrestore(&params->buff_lock, flags);
>>> +}
>>> +
>>> +static int c3_isp_params_vb2_buf_prepare(struct vb2_buffer *vb)
>>> +{
>>> +   struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>>> +   unsigned int size = params->vfmt.fmt.meta.buffersize;
>>> +
>>> +   if (vb2_plane_size(vb, 0) < size) {
>> How does this work ?
>>
>> 'size' is
>>        params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
>>
>> if I got this right
>>
>> Now, as you're using an extensible parameters implementation,
>> userspace is allowed to submit buffers of a smaller size, with only
>> the "interesting" blocks there.
>>
>> This check instead makes sure that userspace always fill the
>> paramteres buffer with all blocks, am I wrong ?
>>
>> This defeates the purpose of extensible formats, where instead
>> userspace is allowed to only fill the buffers with a subset of the
>> configuration blocks.
>>
>> Have I missed something ?
>>
>>> +           dev_err(params->isp->dev,
>>> +                   "User buffer too small (%ld < %u)\n",
>>> +                   vb2_plane_size(vb, 0), size);
>>> +           return -EINVAL;
>>> +   }
>>> +
>>> +   vb2_set_plane_payload(vb, 0, size);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_params_vb2_buf_init(struct vb2_buffer *vb)
>>> +{
>>> +   struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>> +   struct c3_isp_vb2_buffer *buf =
>>> +           container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>> +   struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>>> +
>>> +   buf->vaddr = vb2_plane_vaddr(vb, 0);
>>> +   buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>> This is not used
>>
>>> +
>>> +   memset(buf->vaddr, 0, params->vfmt.fmt.meta.buffersize);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_params_vb2_start_streaming(struct vb2_queue *q,
>>> +                                        unsigned int count)
>>> +{
>>> +   struct c3_isp_params *params = vb2_get_drv_priv(q);
>>> +   int ret;
>>> +
>>> +   guard(mutex)(&params->isp->lock);
>>> +
>>> +   ret = pm_runtime_resume_and_get(params->isp->dev);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   ret = video_device_pipeline_start(&params->vdev, &params->isp->pipe);
>>> +   if (ret) {
>>> +           dev_err(params->isp->dev,
>>> +                   "Failed to start params pipeline: %d\n", ret);
>>> +           goto err_pm_put;
>>> +   }
>>> +
>>> +   if (c3_isp_pipeline_ready(params->isp)) {
>> I understand this counts how many video devices have been started...
>>
>>
>>> +           ret = v4l2_subdev_enable_streams(&params->isp->core.sd,
>>> +                                            C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>> +                                            BIT(0));
>> ... but does it need to be called by this module ? it's not like there is a
>> parameters stream to be enabled in the ISP (see also the suggestion to
>> drop routing support from there).
>>
>>
>>> +           if (ret)
>>> +                   goto err_pipeline_stop;
>>> +   }
>>> +
>>> +   c3_isp_params_start(params);
>> Does this perform initialization and apply the first parameters from a
>> queued buffer ?
>>
>> I'm wondering if it wouldn't be better to:
>>
>> - Implement a start_streaming operation on capture nodes only
>> - Call the ISP's enable_streams
>> - The ISP driver counts how many enabled links to resizers are there
>> - If the number of enable_streams calls matches the number of enabled
>>    links:
>>    - call c3_isp_params_start() and any function that does the stats or
>>      params setup
>>    - actually start the ISP
>>    - propagate the enable_streams call to the downstream subdevs (Adap,
>>      then csi2-rx then sensor)
>>
>>> +
>>> +   return 0;
>>> +
>>> +err_pipeline_stop:
>>> +   video_device_pipeline_stop(&params->vdev);
>>> +err_pm_put:
>>> +   pm_runtime_put(params->isp->dev);
>>> +   c3_isp_params_return_buffers(params, VB2_BUF_STATE_QUEUED);
>>> +   return ret;
>>> +}
>>> +
>>> +static void c3_isp_params_vb2_stop_streaming(struct vb2_queue *q)
>>> +{
>>> +   struct c3_isp_params *params = vb2_get_drv_priv(q);
>>> +
>>> +   guard(mutex)(&params->isp->lock);
>>> +
>>> +   c3_isp_params_return_buffers(params, VB2_BUF_STATE_ERROR);
>> Even if you don't implement start_streaming as I've suggested, you
>> will need this one to return buffers to userspace.
>>
>>> +
>>> +   if (params->isp->pipe.start_count == 1)
>>> +           v4l2_subdev_disable_streams(&params->isp->core.sd,
>>> +                                       C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>> +                                       BIT(0));
>>> +
>>> +   video_device_pipeline_stop(&params->vdev);
>>> +   pm_runtime_put(params->isp->dev);
>>> +}
>>> +
>>> +static const struct vb2_ops isp_params_vb2_ops = {
>>> +   .queue_setup = c3_isp_params_vb2_queue_setup,
>>> +   .buf_queue = c3_isp_params_vb2_buf_queue,
>>> +   .buf_prepare = c3_isp_params_vb2_buf_prepare,
>>> +   .buf_init = c3_isp_params_vb2_buf_init,
>>> +   .wait_prepare = vb2_ops_wait_prepare,
>>> +   .wait_finish = vb2_ops_wait_finish,
>>> +   .start_streaming = c3_isp_params_vb2_start_streaming,
>>> +   .stop_streaming = c3_isp_params_vb2_stop_streaming,
>>> +};
>>> +
>>> +int c3_isp_params_register(struct c3_isp_device *isp)
>>> +{
>>> +   struct c3_isp_params *params = &isp->params;
>>> +   struct video_device *vdev = &params->vdev;
>>> +   struct vb2_queue *vb2_q = &params->vb2_q;
>>> +   int ret;
>>> +
>>> +   memset(params, 0, sizeof(*params));
>>> +   params->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_PARAMS;
>>> +   params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
>>> +   params->isp = isp;
>>> +   INIT_LIST_HEAD(&params->pending);
>>> +   spin_lock_init(&params->buff_lock);
>>> +   mutex_init(&params->lock);
>>> +
>>> +   snprintf(vdev->name, sizeof(vdev->name), "isp-params");
>> Here and in all other names, I would prefix them with "c3-"
>>
>>> +   vdev->fops = &isp_params_v4l2_fops;
>>> +   vdev->ioctl_ops = &isp_params_v4l2_ioctl_ops;
>>> +   vdev->v4l2_dev = &isp->v4l2_dev;
>>> +   vdev->lock = &params->lock;
>>> +   vdev->minor = -1;
>>> +   vdev->queue = vb2_q;
>>> +   vdev->release = video_device_release_empty;
>>> +   vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING;
>>> +   vdev->vfl_dir = VFL_DIR_TX;
>>> +   video_set_drvdata(vdev, params);
>>> +
>>> +   vb2_q->drv_priv = params;
>>> +   vb2_q->mem_ops = &vb2_dma_contig_memops;
>> Do you need to use DMA ?
>>
>> This implementation seems to rather receive a buffer of parameters,
>> inspect its content and write registers according to values there.
>>
>> I don't see direct DMA transfers to a memory mapped register area, but
>> instead single writes to registers.
>>
>> Should you use vb2_vmalloc_memops instead ?
>> Make sure to change the headers inclusions and Kconfig dependencies
>> accordingly.
>>
>>> +   vb2_q->ops = &isp_params_vb2_ops;
>>> +   vb2_q->type = V4L2_BUF_TYPE_META_OUTPUT;
>>> +   vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>>> +   vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>>> +   vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>>> +   vb2_q->dev = isp->dev;
>>> +   vb2_q->lock = &params->lock;
>>> +   vb2_q->min_queued_buffers = 1;
>>> +
>>> +   ret = vb2_queue_init(vb2_q);
>>> +   if (ret)
>>> +           goto err_detroy;
>>> +
>>> +   params->pad.flags = MEDIA_PAD_FL_SOURCE;
>>> +   ret = media_entity_pads_init(&vdev->entity, 1, &params->pad);
>>> +   if (ret)
>>> +           goto err_queue_release;
>>> +
>>> +   ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>>> +   if (ret < 0) {
>>> +           dev_err(isp->dev,
>>> +                   "Failed to register %s: %d\n", vdev->name, ret);
>>> +           goto err_entity_cleanup;
>>> +   }
>>> +
>>> +   return 0;
>>> +
>>> +err_entity_cleanup:
>>> +   media_entity_cleanup(&vdev->entity);
>>> +err_queue_release:
>>> +   vb2_queue_release(vb2_q);
>>> +err_detroy:
>>> +   mutex_destroy(&params->lock);
>>> +   return ret;
>>> +}
>>> +
>>> +void c3_isp_params_unregister(struct c3_isp_device *isp)
>>> +{
>>> +   struct c3_isp_params *params = &isp->params;
>>> +
>>> +   vb2_queue_release(&params->vb2_q);
>>> +   media_entity_cleanup(&params->vdev.entity);
>>> +   video_unregister_device(&params->vdev);
>>> +   mutex_destroy(&params->lock);
>>> +}
>>> +
>>> +int c3_isp_params_done(struct c3_isp_device *isp)
>>> +{
>>> +   struct c3_isp_params *params = &isp->params;
>>> +   enum vb2_buffer_state state;
>>> +   unsigned long flags;
>>> +
>>> +   spin_lock_irqsave(&params->buff_lock, flags);
>>> +
>>> +   params->buff = list_first_entry_or_null(&params->pending,
>>> +                                           struct c3_isp_vb2_buffer, list);
>>> +   if (!params->buff) {
>>> +           spin_unlock_irqrestore(&params->buff_lock, flags);
>>> +           return -EINVAL;
>>> +   }
>>> +
>>> +   list_del(&params->buff->list);
>>> +
>>> +   state = c3_isp_params_cfg_blocks(params);
>>> +
>>> +   params->buff->vb.sequence = params->isp->frm_sequence;
>>> +   params->buff->vb.vb2_buf.timestamp = ktime_get();
>>> +   params->buff->vb.field = V4L2_FIELD_NONE;
>>> +   vb2_buffer_done(&params->buff->vb.vb2_buf, state);
>>> +
>>> +   spin_unlock_irqrestore(&params->buff_lock, flags);
>>> +
>>> +   return 0;
>>> +}
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
>>> new file mode 100644
>>> index 000000000000..de1938f7c354
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
>>> @@ -0,0 +1,683 @@
>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#ifndef __C3_ISP_REGS_H__
>>> +#define __C3_ISP_REGS_H__
>>> +
>>> +#define ISP_TOP_INPUT_SIZE                       0x0000
>>> +#define TOP_INPUT_HSIZE(x)                       (((x) & 0xffff) << 16)
>>> +#define TOP_INPUT_VSIZE(x)                       ((x) & 0xffff)
>>> +
>>> +#define ISP_TOP_FRM_SIZE                         0x0004
>>> +#define TOP_FRM_CORE_IHSIZE(x)                   (((x) & 0xffff) << 16)
>>> +#define TOP_FRM_CORE_IVSIZE(x)                   ((x) & 0xffff)
>>> +
>>> +#define ISP_TOP_HOLD_SIZE                        0x0008
>>> +#define TOP_HOLD_HSIZE_MASK                      GENMASK(31, 16)
>>> +#define TOP_HOLD_HSIZE_SHIFT                     16
>>> +
>>> +#define ISP_TOP_PATH_EN                          0x0010
>>> +#define TOP_DISP_EN(x)                           BIT((x))
>>> +#define TOP_WRMIF_EN(x)                          BIT((x) + 8)
>>> +
>>> +#define ISP_TOP_PATH_SEL                         0x0014
>>> +#define TOP_DATA_PATH_MASK                       GENMASK(18, 16)
>>> +#define TOP_PATH_MIPI_TO_CORE                    BIT(16)
>>> +
>>> +#define ISP_TOP_IRQ_EN                           0x0080
>>> +#define TOP_IRQ_FRAME_DONE                       BIT(0)
>>> +#define TOP_IRQ_STATS_ERR                        BIT(5)
>>> +
>>> +#define ISP_TOP_IRQ_CLR                          0x0084
>>> +#define ISP_TOP_IRQ_LINE_THRD                    0x0088
>>> +#define TOP_IRQ_DIN_HSYNC                        BIT(16)
>>> +
>>> +#define ISP_TOP_RO_IRQ_STAT                      0x01c4
>>> +#define ISP_TOP_MODE_CTRL                        0x0400
>>> +#define ISP_TOP_FEO_CTRL0                        0x040c
>>> +#define TOP_FEO_CTRL0_ALL_DIS                    0
>>> +#define TOP_FEO_INPUT_FMT_EN                     BIT(8)
>>> +
>>> +#define ISP_TOP_FEO_CTRL1_0                      0x0410
>>> +#define TOP_FEO_CTRL1_0_ALL_DIS                  0
>>> +
>>> +#define ISP_TOP_FEO_CTRL1_1                      0x0414
>>> +#define TOP_FEO_CTRL1_1_ALL_DIS                  0
>>> +
>>> +#define ISP_TOP_FED_CTRL                         0x0418
>>> +#define TOP_FED_CTRL_ALL_DIS                     0
>>> +
>>> +#define ISP_TOP_BEO_CTRL                         0x041c
>>> +#define TOP_BEO_CTRL_ALL_DIS                     0
>>> +#define TOP_BEO_CTRL_GTM_EN                      BIT(2)
>>> +#define TOP_BEO_CTRL_WB_EN                       BIT(6)
>>> +#define TOP_BEO_CTRL_BLC_EN                      BIT(7)
>>> +#define TOP_BEO_CTRL_IDGAIN_EN                   BIT(8)
>>> +#define TOP_BEO_CTRL_EOTF_EN                     BIT(9)
>>> +
>>> +#define ISP_TOP_BED_CTRL                         0x0420
>>> +#define TOP_BED_CTRL_ALL_DIS                     0
>>> +#define TOP_BED_CM0_EN                           BIT(14)
>>> +#define TOP_BED_GAMMA_EN                         BIT(16)
>>> +#define TOP_BED_CCM_EN                           BIT(18)
>>> +#define TOP_BED_DMSC_EN                          BIT(19)
>>> +
>>> +#define ISP_TOP_3A_STAT_CRTL                     0x0424
>>> +#define TOP_3A_AE_STAT_EN                        BIT(0)
>>> +#define TOP_3A_AWB_STAT_EN                       BIT(1)
>>> +#define TOP_3A_AF_STAT_EN                        BIT(2)
>>> +#define TOP_3A_AWB_STAT_SWITCH_MASK              GENMASK(6, 4)
>>> +#define TOP_3A_AWB_STAT_SWITCH_SHIFT             4
>>> +#define TOP_3A_AWB_STAT_SWITCH_BEFORE_WB         2
>>> +
>>> +#define TOP_3A_AE_STAT_SWITCH_MASK               GENMASK(9, 8)
>>> +#define TOP_3A_AE_STAT_SWITCH_SHIFT              8
>>> +#define TOP_3A_AE_STAT_SWITCH_FROM_MLS           1
>>> +#define TOP_3A_AF_STAT_SWITCH_MASK               GENMASK(13, 12)
>>> +#define TOP_3A_AF_STAT_SWITCH_SHIFT              12
>>> +#define TOP_3A_AF_STAT_SWITCH_FROM_SNR            0
>>> +
>>> +#define ISP_FED_BL_OFST_GR                       0x2018
>>> +#define FED_BL_GR_OFST(x)                        ((x) & 0x1fffff)
>>> +
>>> +#define ISP_FED_BL_OFST_R                        0x201c
>>> +#define FED_BL_R_OFST(x)                         ((x) & 0x1fffff)
>>> +
>>> +#define ISP_FED_BL_OFST_B                        0x2020
>>> +#define FED_BL_B_OFST(x)                         ((x) & 0x1fffff)
>>> +
>>> +#define ISP_FED_BL_OFST_GB                       0x2024
>>> +#define FED_BL_GB_OFST(x)                        ((x) & 0x1fffff)
>>> +
>>> +#define ISP_FED_BL_OFST_IR                       0x2028
>>> +#define FED_BL_IR_OFST(x)                        ((x) & 0x1fffff)
>>> +
>>> +#define ISP_LSWB_BLC_OFST0                       0x4028
>>> +#define LSWB_BLC_OFST1_MASK                      GENMASK(15, 0)
>>> +#define LSWB_BLC_OFST1(x)                        ((x) & 0xffff)
>>> +#define LSWB_BLC_OFST1_EXT(x)                    ((x) & 0xffff)
>>> +#define LSWB_BLC_OFST0_MASK                      GENMASK(31, 16)
>>> +#define LSWB_BLC_OFST0(x)                        (((x) & 0xffff) << 16)
>>> +#define LSWB_BLC_OFST0_EXT(x)                    (((x) >> 16) & 0xffff)
>>> +
>>> +#define ISP_LSWB_BLC_OFST1                       0x402c
>>> +#define LSWB_BLC_OFST3_MASK                      GENMASK(15, 0)
>>> +#define LSWB_BLC_OFST3(x)                        ((x) & 0xffff)
>>> +#define LSWB_BLC_OFST3_EXT(x)                    ((x) & 0xffff)
>>> +#define LSWB_BLC_OFST2_MASK                      GENMASK(31, 16)
>>> +#define LSWB_BLC_OFST2(x)                        (((x) & 0xffff) << 16)
>>> +#define LSWB_BLC_OFST2_EXT(x)                    (((x) >> 16) & 0xffff)
>>> +
>>> +#define ISP_LSWB_BLC_OFST2                       0x4030
>>> +#define LSWB_BLC_OFST4_MASK                      GENMASK(15, 0)
>>> +#define LSWB_BLC_OFST4(x)                        ((x) & 0xffff)
>>> +#define LSWB_BLC_OFST4_EXT(x)                    ((x) & 0xffff)
>>> +
>>> +#define ISP_LSWB_BLC_PHSOFST                     0x4034
>>> +#define LSWB_BLC_YPHS_OFST_MASK                  GENMASK(1, 0)
>>> +#define LSWB_BLC_XPHS_OFST_MASK                  GENMASK(3, 2)
>>> +#define LSWB_BLC_XPHS_OFST_SHIFT                 2
>>> +
>>> +#define ISP_LSWB_WB_GAIN0                        0x4038
>>> +#define LSWB_WB_GAIN1_MASK                       GENMASK(11, 0)
>>> +#define LSWB_WB_GAIN0_MASK                       GENMASK(27, 16)
>>> +#define LSWB_WB_GAIN0_SHIFT                      16
>>> +
>>> +#define ISP_LSWB_WB_GAIN1                        0x403c
>>> +#define LSWB_WB_GAIN3_MASK                       GENMASK(11, 0)
>>> +#define LSWB_WB_GAIN2_MASK                       GENMASK(27, 16)
>>> +#define LSWB_WB_GAIN2_SHIFT                      16
>>> +
>>> +#define ISP_LSWB_WB_GAIN2                        0x4040
>>> +#define LSWB_WB_GAIN4_MASK                       GENMASK(11, 0)
>>> +
>>> +#define ISP_LSWB_WB_LIMIT0                       0x4044
>>> +#define LSWB_WB_LIMIT1(x)                        ((x) & 0xffff)
>>> +#define LSWB_WB_LIMIT0(x)                        (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_LSWB_WB_LIMIT1                       0x4048
>>> +#define LSWB_WB_LIMIT3(x)                        ((x) & 0xffff)
>>> +#define LSWB_WB_LIMIT2(x)                        (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_LSWB_WB_LIMIT2                       0x404c
>>> +#define LSWB_WB_LIMIT4_MASK                      GENMASK(15, 0)
>>> +
>>> +#define ISP_LSWB_WB_PHSOFST                      0x4050
>>> +#define LSWB_WB_YPHS_OFST_MASK                   GENMASK(1, 0)
>>> +#define LSWB_WB_XPHS_OFST_MASK                   GENMASK(3, 2)
>>> +#define LSWB_WB_XPHS_OFST_SHIFT                  2
>>> +
>>> +#define ISP_LSWB_LNS_PHSOFST                     0x4054
>>> +#define LSWB_LNS_YPHS_OFST_MASK                  GENMASK(1, 0)
>>> +#define LSWB_LNS_XPHS_OFST_MASK                  GENMASK(3, 2)
>>> +#define LSWB_LNS_XPHS_OFST_SHIFT                 2
>>> +
>>> +#define ISP_DMS_COMMON_PARAM0                    0x5000
>>> +#define DMS_COMMON_YPHS_OFST_MASK                GENMASK(1, 0)
>>> +#define DMS_COMMON_XPHS_OFST_MASK                GENMASK(3, 2)
>>> +#define DMS_COMMON_XPHS_OFST_SHIFT               2
>>> +
>>> +#define ISP_CM0_INP_OFST01                       0x6040
>>> +#define CM0_INP_OFST0_MASK                       GENMASK(12, 0)
>>> +#define CM0_INP_OFST1_MASK                       GENMASK(28, 16)
>>> +#define CM0_INP_OFST1_SHIFT                      16
>>> +
>>> +#define ISP_CM0_INP_OFST2                        0x6044
>>> +#define CM0_INP_OFST2_MASK                       GENMASK(12, 0)
>>> +
>>> +#define ISP_CM0_COEF00_01                        0x6048
>>> +#define CM0_MTX_00_MASK                          GENMASK(12, 0)
>>> +#define CM0_MTX_01_MASK                          GENMASK(28, 16)
>>> +#define CM0_MTX_01_SHIFT                         16
>>> +
>>> +#define ISP_CM0_COEF02_10                        0x604c
>>> +#define CM0_MTX_02_MASK                          GENMASK(12, 0)
>>> +#define CM0_MTX_10_MASK                          GENMASK(28, 16)
>>> +#define CM0_MTX_10_SHIFT                         16
>>> +
>>> +#define ISP_CM0_COEF11_12                        0x6050
>>> +#define CM0_MTX_11_MASK                          GENMASK(12, 0)
>>> +#define CM0_MTX_12_MASK                          GENMASK(28, 16)
>>> +#define CM0_MTX_12_SHIFT                         16
>>> +
>>> +#define ISP_CM0_COEF20_21                        0x6054
>>> +#define CM0_MTX_20_MASK                          GENMASK(12, 0)
>>> +#define CM0_MTX_21_MASK                          GENMASK(28, 16)
>>> +#define CM0_MTX_21_SHIFT                         16
>>> +
>>> +#define ISP_CM0_COEF22_OUP_OFST0                 0x6058
>>> +#define CM0_MTX_22_MASK                          GENMASK(12, 0)
>>> +#define CM0_OFST_OUP0_MASK                       GENMASK(28, 16)
>>> +#define CM0_OFST_OUP0_SHIFT                      16
>>> +
>>> +#define ISP_CM0_OUP_OFST12_RS                    0x605c
>>> +#define CM0_OFST_OUP1_MASK                       GENMASK(12, 0)
>>> +#define CM0_OFST_OUP2_MASK                       GENMASK(28, 16)
>>> +#define CM0_OFST_OUP2_SHIFT                      16
>>> +#define CM0_MTX_RS_MASK                          GENMASK(31, 30)
>>> +#define CM0_MTX_RS_SHIFT                         30
>>> +
>>> +#define ISP_CCM_MTX_00_01                        0x6098
>>> +#define CCM_MTX_00_MASK                          GENMASK(12, 0)
>>> +#define CCM_MTX_01_MASK                          GENMASK(28, 16)
>>> +#define CCM_MTX_01_SHIFT                         16
>>> +
>>> +#define ISP_CCM_MTX_02_03                        0x609c
>>> +#define CCM_MTX_02_MASK                          GENMASK(12, 0)
>>> +#define CCM_MTX_03_MASK                          GENMASK(28, 16)
>>> +#define CCM_MTX_03_SHIFT                         16
>>> +
>>> +#define ISP_CCM_MTX_10_11                        0x60A0
>>> +#define CCM_MTX_10_MASK                          GENMASK(12, 0)
>>> +#define CCM_MTX_11_MASK                          GENMASK(28, 16)
>>> +#define CCM_MTX_11_SHIFT                         16
>>> +
>>> +#define ISP_CCM_MTX_12_13                        0x60A4
>>> +#define CCM_MTX_12_MASK                          GENMASK(12, 0)
>>> +#define CCM_MTX_13_MASK                          GENMASK(28, 16)
>>> +#define CCM_MTX_13_SHIFT                         16
>>> +
>>> +#define ISP_CCM_MTX_20_21                        0x60A8
>>> +#define CCM_MTX_20_MASK                          GENMASK(12, 0)
>>> +#define CCM_MTX_21_MASK                          GENMASK(28, 16)
>>> +#define CCM_MTX_21_SHIFT                         16
>>> +
>>> +#define ISP_CCM_MTX_22_23_RS                     0x60Ac
>>> +#define CCM_MTX_22_MASK                          GENMASK(12, 0)
>>> +#define CCM_MTX_23_MASK                          GENMASK(28, 16)
>>> +#define CCM_MTX_23_SHIFT                         16
>>> +
>>> +#define ISP_PST_GAMMA_MODE                       0x60C0
>>> +#define PST_GAMMA_MODE                           BIT(0)
>>> +#define PST_GAMMA_MODE_EXT(x)                    ((x) & 0x1)
>>> +
>>> +#define ISP_PST_GAMMA_LUT_ADDR                   0x60cc
>>> +#define PST_GAMMA_LUT_ADDR(x)                    ((x) << 7)
>>> +
>>> +#define ISP_PST_GAMMA_LUT_DATA                   0x60d0
>>> +#define PST_GAMMA_LUT_DATA0(x)                   ((x) & 0xffff)
>>> +#define PST_GAMMA_LUT_DATA1(x)                   (((x) & 0xffff) << 16)
>>> +
>>> +#define DISP0_TOP_TOP_CTRL                       0x8000
>>> +#define DISP_CRP2_EN                             BIT(5)
>>> +
>>> +#define DISP0_TOP_CRP2_START                     0x8004
>>> +#define DISP_CRP2_VSTART_MASK                    GENMASK(15, 0)
>>> +#define DISP_CRP2_VSTART(x)                      ((x) & 0xffff)
>>> +#define DISP_CRP2_HSTART_MASK                    GENMASK(31, 16)
>>> +#define DISP_CRP2_HSTART(x)                      (((x) & 0xffff) << 16)
>>> +
>>> +#define DISP0_TOP_CRP2_SIZE                      0x8008
>>> +#define DISP_CRP2_VSIZE_MASK                     GENMASK(15, 0)
>>> +#define DISP_CRP2_VSIZE(x)                       ((x) & 0xffff)
>>> +#define DISP_CRP2_HSIZE_MASK                     GENMASK(31, 16)
>>> +#define DISP_CRP2_HSIZE(x)                       (((x) & 0xffff) << 16)
>>> +
>>> +#define DISP0_TOP_OUT_SIZE                       0x800c
>>> +#define DISP_OUT_VSIZE_MASK                      GENMASK(12, 0)
>>> +#define DISP_OUT_HSIZE_MASK                      GENMASK(28, 16)
>>> +#define DISP_OUT_HSIZE_SHIFT                     16
>>> +
>>> +#define ISP_DISP0_TOP_IN_SIZE                    0x804c
>>> +#define DISP_TOP_IN_VSIZE(x)                     ((x) & 0x1fff)
>>> +#define DISP_TOP_IN_HSIZE(x)                     (((x) & 0x1fff) << 16)
>>> +
>>> +#define DISP0_PPS_SCALE_EN                       0x8200
>>> +#define PPS_VSC_TAP_NUM_MASK                     GENMASK(3, 0)
>>> +#define PPS_HSC_TAP_NUM_MASK                     GENMASK(7, 4)
>>> +#define PPS_HSC_TAP_NUM_SHIFT                    4
>>> +#define PPS_HSC_TAP_NUM_INIT                     4
>>> +#define PPS_PREVSC_FLT_NUM_MASK                  GENMASK(11, 8)
>>> +#define PPS_PREVSC_FLT_NUM_SHIFT                 8
>>> +#define PPS_PREHSC_FLT_NUM_MASK                  GENMASK(15, 12)
>>> +#define PPS_PREHSC_FLT_NUM_SHIFT                 12
>>> +#define PPS_PREHSC_FLT_NUM_INIT                  8
>>> +#define PPS_PREVSC_RATE_MASK                     GENMASK(17, 16)
>>> +#define PPS_PREVSC_RATE_SHIFT                    16
>>> +#define PPS_PREHSC_RATE_MASK                     GENMASK(19, 18)
>>> +#define PPS_PREHSC_RATE_SHIFT                    18
>>> +#define PPS_HSC_EN_MASK                          BIT(20)
>>> +#define PPS_HSC_EN_SHIFT                         20
>>> +#define PPS_VSC_EN_MASK                          BIT(21)
>>> +#define PPS_VSC_EN_SHIFT                         21
>>> +#define PPS_PREVSC_EN_MASK                       BIT(22)
>>> +#define PPS_PREVSC_EN_SHIFT                      22
>>> +#define PPS_PREHSC_EN_MASK                       BIT(23)
>>> +#define PPS_PREHSC_EN_SHIFT                      23
>>> +#define PPS_HSC_NOR_RS_BITS_MASK                 GENMASK(27, 24)
>>> +#define PPS_HSC_NOR_RS_BITS_SHIFT                24
>>> +#define PPS_HSC_NOR_RS_BITS_INIT                 9
>>> +#define PPS_VSC_NOR_RS_BITS_MASK                 GENMASK(31, 28)
>>> +#define PPS_VSC_NOR_RS_BITS_SHIFT                28
>>> +#define PPS_VSC_NOR_RS_BITS_INIT                 9
>>> +
>>> +#define DISP0_PPS_PRE_HSCALE_COEF_0              0x8204
>>> +#define PPS_PREHSC_LUMA_COEF0_MASK               GENMASK(9, 0)
>>> +#define PPS_PREHSC_LUMA_COEF0_INIT               128
>>> +#define PPS_PREHSC_LUMA_COEF1_MASK               GENMASK(25, 16)
>>> +#define PPS_PREHSC_LUMA_COEF1_SHIFT              16
>>> +#define PPS_PREHSC_LUMA_COEF1_INIT               128
>>> +
>>> +#define DISP0_PPS_PRE_HSCALE_COEF_1              0x8208
>>> +#define PPS_PREHSC_LUMA_COEF2_MASK               GENMASK(9, 0)
>>> +#define PPS_PREHSC_LUMA_COEF2_INIT               32
>>> +#define PPS_PREHSC_LUMA_COEF3_MASK               GENMASK(25, 16)
>>> +#define PPS_PREHSC_LUMA_COEF3_SHIFT              16
>>> +#define PPS_PREHSC_LUMA_COEF3_INIT               32
>>> +
>>> +#define DISP0_PPS_VSC_START_PHASE_STEP           0x8224
>>> +#define PPS_VSC_FRACTION_PART_MASK               GENMASK(23, 0)
>>> +#define PPS_VSC_INTEGER_PART_MASK                GENMASK(27, 24)
>>> +#define PPS_VSC_INTEGER_PART_SHIFT               24
>>> +
>>> +#define DISP0_PPS_HSC_START_PHASE_STEP           0x8230
>>> +#define PPS_HSC_FRACTION_PART_MASK               GENMASK(23, 0)
>>> +#define PPS_HSC_INTEGER_PART_MASK                GENMASK(27, 24)
>>> +#define PPS_HSC_INTEGER_PART_SHIFT               24
>>> +
>>> +#define DISP0_PPS_444TO422                       0x823c
>>> +#define PPS_444TO422_EN_MASK                     BIT(0)
>>> +
>>> +#define ISP_SCALE0_COEF_IDX_LUMA                 0x8240
>>> +#define SCALE_LUMA_COEF_S11_MODE                 BIT(9)
>>> +#define SCALE_LUMA_CTYPE(x)                      (((x) & 0x7) << 10)
>>> +
>>> +#define ISP_SCALE0_COEF_LUMA                     0x8244
>>> +#define SCALE_COEF_LUMA_DATA1(x)                 ((x) & 0x7ff)
>>> +#define SCALE_COEF_LUMA_DATA0(x)                 (((x) & 0x7ff) << 16)
>>> +
>>> +#define ISP_SCALE0_COEF_IDX_CHRO                 0x8248
>>> +#define SCALE_CHRO_COEF_S11_MODE                 BIT(9)
>>> +#define SCALE_CHRO_CTYPE(x)                      (((x) & 0x7) << 10)
>>> +
>>> +#define ISP_SCALE0_COEF_CHRO                     0x824c
>>> +#define SCALE_COEF_CHRO_DATA1(x)                 ((x) & 0x7ff)
>>> +#define SCALE_COEF_CHRO_DATA0(x)                 (((x) & 0x7ff) << 16)
>>> +
>>> +#define ISP_AF_ROI0_WIN01                        0xa00c
>>> +#define AF_ROI_XYWIN_00_INIT                     (50 & 0xffff)
>>> +#define AF_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
>>> +
>>> +#define ISP_AF_ROI1_WIN01                        0xa010
>>> +#define AF_ROI_XYWIN_10_INIT                     (100 & 0xffff)
>>> +#define AF_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
>>> +
>>> +#define ISP_AF_ROI0_WIN23                        0xa014
>>> +#define AF_ROI_XYWIN_02_INIT                     (80 & 0xffff)
>>> +#define AF_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
>>> +
>>> +#define ISP_AF_ROI1_WIN23                        0xa018
>>> +#define AF_ROI_XYWIN_12_INIT                     (80 & 0xffff)
>>> +#define AF_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
>>> +
>>> +#define ISP_AF_CTRL                              0xa044
>>> +#define AF_CTRL_YPHS_OFST_MASK                   GENMASK(15, 14)
>>> +#define AF_CTRL_YPHS_OFST_SHIFT                  14
>>> +#define AF_CTRL_XPHS_OFST_MASK                   GENMASK(17, 16)
>>> +#define AF_CTRL_XPHS_OFST_SHIFT                  16
>>> +
>>> +#define ISP_AF_HV_SIZE                           0xa04c
>>> +#define AF_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
>>> +#define AF_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AF_HV_BLKNUM                         0xa050
>>> +#define AF_HV_STAT_VBLK_NUM_MASK                 GENMASK(5, 0)
>>> +#define AF_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x3f)
>>> +#define AF_HV_STAT_HBLK_NUM_MASK                 GENMASK(21, 16)
>>> +#define AF_HV_STAT_HBLK_NUM_SHIFT                16
>>> +
>>> +#define ISP_AF_EN_CTRL                           0xa054
>>> +#define AF_STAT_SELECT                           BIT(21)
>>> +#define AF_STAT_SELECT_SHIFT                     21
>>> +
>>> +#define ISP_RO_AF_GLB_STAT_PCK0                  0xa0e0
>>> +#define ISP_RO_AF_GLB_STAT_PCK1                  0xa0e4
>>> +#define ISP_RO_AF_GLB_STAT_PCK2                  0xa0e8
>>> +#define ISP_RO_AF_GLB_STAT_PCK3                  0xa0eC
>>> +#define ISP_AF_IDX_ADDR                          0xa1c0
>>> +#define ISP_AF_IDX_DATA                          0xa1c4
>>> +#define AF_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
>>> +#define AF_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_ROI0_WIN01                        0xa40c
>>> +#define AE_ROI_XYWIN_00_INIT                     (50 & 0xffff)
>>> +#define AE_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_ROI1_WIN01                        0xa410
>>> +#define AE_ROI_XYWIN_10_INIT                     (100 & 0xffff)
>>> +#define AE_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_ROI0_WIN23                        0xa414
>>> +#define AE_ROI_XYWIN_02_INIT                     (80 & 0xffff)
>>> +#define AE_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_ROI1_WIN23                        0xa418
>>> +#define AE_ROI_XYWIN_12_INIT                     (80 & 0xffff)
>>> +#define AE_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
>>> +
>>> +#define ISP_RO_AE_ROI_STAT_PCK0_0                0xa424
>>> +#define ISP_RO_AE_ROI_STAT_PCK1_0                0xa428
>>> +#define ISP_RO_AE_ROI_STAT_PCK0_1                0xa42c
>>> +#define ISP_RO_AE_ROI_STAT_PCK1_1                0xa430
>>> +#define ISP_AE_CTRL                              0xa448
>>> +#define AE_CTRL_AE_STAT_LOCAL_MODE               GENMASK(3, 2)
>>> +#define AE_CTRL_AE_STAT_LOCAL_MODE_EXT(x)        (((x) >> 2) & 0x3)
>>> +#define AE_CTRL_INPUT_2LINE_TOGETHER             BIT(7)
>>> +#define AE_CTRL_LUMA_MODE_MASK                   GENMASK(9, 8)
>>> +#define AE_CTRL_LUMA_MODE_SHIFT                  8
>>> +#define AE_CTRL_LUMA_MODE_FILTER                 2
>>> +#define AE_CTRL_YPHS_OFST_MASK                   GENMASK(25, 24)
>>> +#define AE_CTRL_YPHS_OFST_SHIFT                  24
>>> +#define AE_CTRL_XPHS_OFST_MASK                   GENMASK(27, 26)
>>> +#define AE_CTRL_XPHS_OFST_SHIFT                  26
>>> +
>>> +#define ISP_AE_CRTL2_0                           0xa44c
>>> +#define AE_CRTL2_BL12_GRBGI0_MASK                GENMASK(11, 0)
>>> +#define AE_CRTL2_GAIN_GRBGI0_MASK                GENMASK(23, 16)
>>> +#define AE_CRTL2_GAIN_GRBGI0_SHIFT               16
>>> +
>>> +#define ISP_AE_CRTL2_1                           0xa450
>>> +#define AE_CRTL2_BL12_GRBGI1_MASK                GENMASK(11, 0)
>>> +#define AE_CRTL2_GAIN_GRBGI1_MASK                GENMASK(23, 16)
>>> +#define AE_CRTL2_GAIN_GRBGI1_SHIFT               16
>>> +
>>> +#define ISP_AE_CRTL2_2                           0xa454
>>> +#define AE_CRTL2_BL12_GRBGI2_MASK                GENMASK(11, 0)
>>> +#define AE_CRTL2_GAIN_GRBGI2_MASK                GENMASK(23, 16)
>>> +#define AE_CRTL2_GAIN_GRBGI2_SHIFT               16
>>> +
>>> +#define ISP_AE_CRTL2_3                           0xa458
>>> +#define AE_CRTL2_BL12_GRBGI3_MASK                GENMASK(11, 0)
>>> +#define AE_CRTL2_GAIN_GRBGI3_MASK                GENMASK(23, 16)
>>> +#define AE_CRTL2_GAIN_GRBGI3_SHIFT               16
>>> +
>>> +#define ISP_AE_CRTL2_4                           0xa45C
>>> +#define AE_CRTL2_BL12_GRBGI4_MASK                GENMASK(11, 0)
>>> +#define AE_CRTL2_GAIN_GRBGI4_MASK                GENMASK(23, 16)
>>> +#define AE_CRTL2_GAIN_GRBGI4_SHIFT               16
>>> +
>>> +#define ISP_AE_HV_SIZE                           0xa464
>>> +#define AE_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
>>> +#define AE_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_HV_BLKNUM                         0xa468
>>> +#define AE_HV_STAT_VBLK_NUM_MASK                 GENMASK(6, 0)
>>> +#define AE_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x7f)
>>> +#define AE_HV_STAT_HBLK_NUM_MASK                 GENMASK(22, 16)
>>> +#define AE_HV_STAT_HBLK_NUM_SHIFT                16
>>> +
>>> +#define ISP_AE_STAT_THD01                        0xa46c
>>> +#define AE_STAT_THRD0_INIT                       (13107 & 0xffff)
>>> +#define AE_STAT_THRD1_INIT                       ((26214 & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_STAT_THD23                        0xa470
>>> +#define AE_STAT_THRD2_INIT                       (39321 & 0xffff)
>>> +#define AE_STAT_THRD3_INIT                       ((52428 & 0xffff) << 16)
>>> +
>>> +#define ISP_RO_AE_STAT_GPNUM                     0xa49c
>>> +#define ISP_AE_IDX_ADDR                          0xa600
>>> +#define ISP_AE_IDX_DATA                          0xa604
>>> +#define AE_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
>>> +#define AE_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_BLK_WT_ADDR                       0xa608
>>> +#define ISP_AE_BLK_WT_DATA                       0xa60c
>>> +#define AE_BLK_WT_DATA0(x)                       ((x) & 0xf)
>>> +#define AE_BLK_WT_DATA1(x)                       (((x) & 0xf) << 4)
>>> +#define AE_BLK_WT_DATA2(x)                       (((x) & 0xf) << 8)
>>> +#define AE_BLK_WT_DATA3(x)                       (((x) & 0xf) << 12)
>>> +#define AE_BLK_WT_DATA4(x)                       (((x) & 0xf) << 16)
>>> +#define AE_BLK_WT_DATA5(x)                       (((x) & 0xf) << 20)
>>> +#define AE_BLK_WT_DATA6(x)                       (((x) & 0xf) << 24)
>>> +#define AE_BLK_WT_DATA7(x)                       (((x) & 0xf) << 28)
>>> +
>>> +#define ISP_AWB_CTRL                             0xa834
>>> +#define AWB_CTRL_YPHS_OFST_MASK                  GENMASK(1, 0)
>>> +#define AWB_CTRL_XPHS_OFST_MASK                  GENMASK(3, 2)
>>> +#define AWB_CTRL_XPHS_OFST_SHIFT                 2
>>> +#define AWB_CTRL_STAT_RATIO_MODE                 BIT(4)
>>> +
>>> +#define ISP_AWB_HV_SIZE                          0xa83c
>>> +#define AWB_HV_STAT_VSIZE(x)                     ((x) & 0xffff)
>>> +#define AWB_HV_STAT_HSIZE(x)                     (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_STAT_ROI0_WIN01                  0xa80c
>>> +#define AWB_ROI_XYWIN_00_INIT                    (32 & 0xffff)
>>> +#define AWB_ROI_XYWIN_01_INIT                    ((62 & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_STAT_ROI1_WIN01                  0xa810
>>> +#define AWB_ROI_XYWIN_10_INIT                    (32 & 0xffff)
>>> +#define AWB_ROI_XYWIN_11_INIT                    ((82 & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_STAT_ROI0_WIN23                  0xa814
>>> +#define AWB_ROI_XYWIN_02_INIT                    (32 & 0xffff)
>>> +#define AWB_ROI_XYWIN_03_INIT                    ((42 & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_STAT_ROI1_WIN23                  0xa818
>>> +#define AWB_ROI_XYWIN_12_INIT                    (64 & 0xffff)
>>> +#define AWB_ROI_XYWIN_13_INIT                    ((52 & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_HV_BLKNUM                        0xa840
>>> +#define AWB_HV_STAT_VBLK_NUM_MASK                GENMASK(5, 0)
>>> +#define AWB_HV_STAT_VBLK_NUM_EXT(x)              ((x) & 0x3f)
>>> +#define AWB_HV_STAT_HBLK_NUM_MASK                GENMASK(21, 16)
>>> +#define AWB_HV_STAT_HBLK_NUM_SHIFT               16
>>> +
>>> +#define ISP_AWB_STAT_RG                          0xa848
>>> +#define AWB_STAT_RG_MIN_MASK                     GENMASK(11, 0)
>>> +#define AWB_STAT_RG_MAX_MASK                     GENMASK(27, 16)
>>> +#define AWB_STAT_RG_MAX_SHIFT                    16
>>> +
>>> +#define ISP_AWB_STAT_BG                          0xa84c
>>> +#define AWB_STAT_BG_MIN_MASK                     GENMASK(11, 0)
>>> +#define AWB_STAT_BG_MAX_MASK                     GENMASK(27, 16)
>>> +#define AWB_STAT_BG_MAX_SHIFT                    16
>>> +
>>> +#define ISP_AWB_STAT_RG_HL                       0xa850
>>> +#define AWB_STAT_RG_LOW_MASK                     GENMASK(11, 0)
>>> +#define AWB_STAT_RG_HIGH_MASK                    GENMASK(27, 16)
>>> +#define AWB_STAT_RG_HIGH_SHIFT                   16
>>> +
>>> +#define ISP_AWB_STAT_BG_HL                       0xa854
>>> +#define AWB_STAT_BG_LOW_MASK                     GENMASK(11, 0)
>>> +#define AWB_STAT_BG_HIGH_MASK                    GENMASK(27, 16)
>>> +#define AWB_STAT_BG_HIGH_SHIFT                   16
>>> +
>>> +#define ISP_AWB_STAT_CTRL2                       0xa858
>>> +#define AWB_STAT_SATUR_VALID_MASK                BIT(0)
>>> +#define AWB_STAT_LOCAL_MODE                      BIT(2)
>>> +#define AWB_STAT_LOCAL_MODE_EXT(x)               (((x) >> 2) & 0x1)
>>> +#define AWB_STAT_LUMA_DIV_MODE                   GENMASK(4, 3)
>>> +#define AWB_STAT_LUMA_DIV_MODE_EXT(x)            (((x) >> 3) & 0x3)
>>> +
>>> +#define ISP_AWB_STAT_BLC20_0                     0xa85c
>>> +#define AWB_STAT_BLC20_GR_MASK                   GENMASK(19, 0)
>>> +
>>> +#define ISP_AWB_STAT_BLC20_1                     0xa860
>>> +#define AWB_STAT_BLC20_R_MASK                    GENMASK(19, 0)
>>> +
>>> +#define ISP_AWB_STAT_BLC20_2                     0xa864
>>> +#define AWB_STAT_BLC20_B_MASK                    GENMASK(19, 0)
>>> +
>>> +#define ISP_AWB_STAT_BLC20_3                     0xa868
>>> +#define AWB_STAT_BLC20_GB_MASK                   GENMASK(19, 0)
>>> +
>>> +#define ISP_AWB_STAT_GAIN10_0                    0xa86c
>>> +#define AWB_STAT_GAIN10_GR_MASK                  GENMASK(9, 0)
>>> +
>>> +#define ISP_AWB_STAT_GAIN10_1                    0xa870
>>> +#define AWB_STAT_GAIN10_R_MASK                   GENMASK(9, 0)
>>> +
>>> +#define ISP_AWB_STAT_GAIN10_2                    0xa874
>>> +#define AWB_STAT_GAIN10_B_MASK                   GENMASK(9, 0)
>>> +
>>> +#define ISP_AWB_STAT_GAIN10_3                    0xa878
>>> +#define AWB_STAT_GAIN10_GB_MASK                  GENMASK(9, 0)
>>> +
>>> +#define ISP_AWB_STAT_SATUR_CTRL                  0xa884
>>> +#define AWB_STAT_SATUR_LOW(x)                    ((x) & 0xffff)
>>> +#define AWB_STAT_SATUR_HIGH(x)                   (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_IDX_ADDR                         0xaa00
>>> +#define ISP_AWB_IDX_DATA                         0xaa04
>>> +#define AWB_IDX_VIDX_DATA(x)                     ((x) & 0xffff)
>>> +#define AWB_IDX_HIDX_DATA(x)                     (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_BLK_WT_ADDR                      0xaa08
>>> +#define ISP_AWB_BLK_WT_DATA                      0xaa0c
>>> +#define AWB_BLK_WT_DATA0(x)                      ((x) & 0xf)
>>> +#define AWB_BLK_WT_DATA1(x)                      (((x) & 0xf) << 4)
>>> +#define AWB_BLK_WT_DATA2(x)                      (((x) & 0xf) << 8)
>>> +#define AWB_BLK_WT_DATA3(x)                      (((x) & 0xf) << 12)
>>> +#define AWB_BLK_WT_DATA4(x)                      (((x) & 0xf) << 16)
>>> +#define AWB_BLK_WT_DATA5(x)                      (((x) & 0xf) << 20)
>>> +#define AWB_BLK_WT_DATA6(x)                      (((x) & 0xf) << 24)
>>> +#define AWB_BLK_WT_DATA7(x)                      (((x) & 0xf) << 28)
>>> +
>>> +#define ISP_WRMIFX3_0_CH0_CTRL0                  0xc400
>>> +#define WRMIFX3_CH0_STRIDE_MASK                  GENMASK(28, 16)
>>> +#define WRMIFX3_CH0_STRIDE_SHIFT                 16
>>> +
>>> +#define ISP_WRMIFX3_0_CH0_CTRL1                  0xc404
>>> +#define WRMIFX3_CH0_PIX_BITS_MODE_MASK           GENMASK(30, 27)
>>> +#define WRMIFX3_CH0_PIX_BITS_MODE_SHIFT          27
>>> +#define WRMIFX3_CH0_PIX_BITS_8BITS               1
>>> +#define WRMIFX3_CH0_PIX_BITS_16BITS              2
>>> +
>>> +#define ISP_WRMIFX3_0_CH1_CTRL0                  0xc408
>>> +#define WRMIFX3_CH1_STRIDE_MASK                  GENMASK(28, 16)
>>> +#define WRMIFX3_CH1_STRIDE_SHIFT                 16
>>> +
>>> +#define ISP_WRMIFX3_0_CH1_CTRL1                  0xc40c
>>> +#define WRMIFX3_CH1_PIX_BITS_MODE_MASK           GENMASK(30, 27)
>>> +#define WRMIFX3_CH1_PIX_BITS_MODE_SHIFT          27
>>> +#define WRMIFX3_CH1_PIX_BITS_16BITS              2
>>> +#define WRMIFX3_CH1_PIX_BITS_32BITS              3
>>> +
>>> +#define ISP_WRMIFX3_0_CH0_BADDR                  0xc440
>>> +/* WRMIF base address need 16 bits alignment */
>>> +#define WRMIFX3_CH0_BADDR(x)                     (((x) >> 4) & 0xffffffff)
>>> +
>>> +#define ISP_WRMIFX3_0_CH1_BADDR                  0xc444
>>> +#define WRMIFX3_CH1_BADDR(x)                     (((x) >> 4) & 0xffffffff)
>>> +
>>> +#define ISP_WRMIFX3_0_FMT_SIZE                   0xc464
>>> +#define WRMIFX3_FMT_HSIZE(x)                     ((x) & 0xffff)
>>> +#define WRMIFX3_FMT_VSIZE(x)                     (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_WRMIFX3_0_FMT_CTRL                   0xc468
>>> +#define WRMIFX3_FMT_MTX_IBITS_MASK               GENMASK(1, 0)
>>> +#define WRMIFX3_FMT_MTX_IBITS_8BIT               0
>>> +#define WRMIFX3_FMT_MTX_IBITS_10BIT              1
>>> +#define WRMIFX3_FMT_MTX_IBITS_12BIT              2
>>> +#define WRMIFX3_FMT_MTX_IBITS_16BIT              3
>>> +#define WRMIFX3_FMT_MTX_UV_SWAP_MASK             BIT(2)
>>> +#define WRMIFX3_FMT_MTX_UV_SWAP_SHIFT            2
>>> +#define WRMIFX3_FMT_MTX_UV_SWAP_VU               0
>>> +#define WRMIFX3_FMT_MTX_UV_SWAP_UV               1
>>> +#define WRMIFX3_FMT_MTX_PLANE_MASK               GENMASK(5, 4)
>>> +#define WRMIFX3_FMT_MTX_PLANE_SHIFT              4
>>> +#define WRMIFX3_FMT_MTX_PLANE_X1                 0
>>> +#define WRMIFX3_FMT_MTX_PLANE_X2                 1
>>> +#define WRMIFX3_FMT_MODE_OUT_MASK                GENMASK(18, 16)
>>> +#define WRMIFX3_FMT_MODE_OUT_SHIFT               16
>>> +#define WRMIFX3_FMT_MODE_OUT_YUV422              1
>>> +#define WRMIFX3_FMT_MODE_OUT_YUV420              2
>>> +#define WRMIFX3_FMT_MODE_OUT_Y_ONLY              3
>>> +
>>> +#define ISP_WRMIFX3_0_WIN_LUMA_H                 0xc420
>>> +#define WRMIFX3_WIN_LUMA_HEND_MASK               GENMASK(28, 16)
>>> +#define WRMIFX3_WIN_LUMA_HEND_SHIFT              16
>>> +#define WRMIFX3_WIN_LUMA_HEND(x)                 ((x) - 1)
>>> +
>>> +#define ISP_WRMIFX3_0_WIN_LUMA_V                 0xc424
>>> +#define WRMIFX3_WIN_LUMA_VEND_MASK               GENMASK(28, 16)
>>> +#define WRMIFX3_WIN_LUMA_VEND_SHIFT              16
>>> +#define WRMIFX3_WIN_LUMA_VEND(x)                 ((x) - 1)
>>> +
>>> +#define ISP_WRMIFX3_0_WIN_CHROM_H                0xc428
>>> +#define WRMIFX3_WIN_CHROM_HEND_MASK              GENMASK(28, 16)
>>> +#define WRMIFX3_WIN_CHROM_HEND_SHIFT             16
>>> +#define WRMIFX3_WIN_CHROM_HEND(x)                (((x) >> 1) - 1)
>>> +
>>> +#define ISP_WRMIFX3_0_WIN_CHROM_V                0xc42c
>>> +#define WRMIFX3_WIN_CHROM_VEND_MASK              GENMASK(28, 16)
>>> +#define WRMIFX3_WIN_CHROM_VEND_SHIFT             16
>>> +#define WRMIFX3_WIN_CHROM_VEND(x)                (((x) >> 1) - 1)
>>> +
>>> +#define ISP_WRMIFX3_0_CRP_HSIZE                  0xc48c
>>> +#define WRMIFX3_CROP_HEND_MASK                   GENMASK(31, 16)
>>> +#define WRMIFX3_CROP_HEND_SHIFT                  16
>>> +#define WRMIFX3_CROP_HEND(x)                     ((x) - 1)
>>> +
>>> +#define ISP_WRMIFX3_0_CRP_VSIZE                  0xc490
>>> +#define WRMIFX3_CROP_VEND_MASK                   GENMASK(31, 16)
>>> +#define WRMIFX3_CROP_VEND_SHIFT                  16
>>> +#define WRMIFX3_CROP_VEND(x)                     ((x) - 1)
>>> +
>>> +#define VIU_DMAWR_BADDR0                         0xc840
>>> +#define VIU_DMAWR_AF_BADDR_MASK                  GENMASK(27, 0)
>>> +/* AF base address need 16 bits alignment */
>>> +#define VIU_DMAWR_AF_BADDR(x)                    ((x) >> 4)
>>> +
>>> +#define VIU_DMAWR_BADDR1                         0xc844
>>> +#define VIU_DMAWR_AWB_BADDR_MASK                  GENMASK(27, 0)
>>> +/* AWB base address need 16 bits alignment */
>>> +#define VIU_DMAWR_AWB_BADDR(x)                    ((x) >> 4)
>>> +
>>> +#define VIU_DMAWR_BADDR2                         0xc848
>>> +#define VIU_DMAWR_AE_BADDR_MASK                  GENMASK(27, 0)
>>> +/* AE base address need 16 bits alignment */
>>> +#define VIU_DMAWR_AE_BADDR(x)                    ((x) >> 4)
>>> +
>>> +#define VIU_DMAWR_SIZE0                          0xc854
>>> +#define VIU_DMAWR_SIZE_AF_MASK                   GENMASK(15, 0)
>>> +#define VIU_DMAWR_SIZE_AWB_MASK                  GENMASK(31, 16)
>>> +#define VIU_DMAWR_SIZE_AWB_SHIFT                 16
>>> +
>>> +#define VIU_DMAWR_SIZE1                          0xc858
>>> +#define VIU_DMAWR_SIZE_AE_MASK                   GENMASK(15, 0)
>>> +
>>> +#endif
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
>>> new file mode 100644
>>> index 000000000000..01d99b66cb32
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
>>> @@ -0,0 +1,768 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/pm_runtime.h>
>>> +
>>> +#include "c3-isp-common.h"
>>> +#include "c3-isp-regs.h"
>>> +
>>> +static const struct c3_isp_mbus_format_info c3_isp_rsz_mbus_formats[] = {
>>> +   /* YUV formats */
>>> +   {
>>> +           .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
>>> +           .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>>> +                           | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +   }, {
>>> +           .mbus_code      = MEDIA_BUS_FMT_YUYV8_1_5X8,
>>> +           .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>>> +                           | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +   }, {
>>> +           .mbus_code      = MEDIA_BUS_FMT_YUYV8_2X8,
>> These mbus_codes come from the ISP, I presume after debayering.
>> Is the different samples number (5X8, 2X8) a representation of the
>> format on the internal bus between the ISP and the resizers ?
>>
>>> +           .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>>> +                           | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>>> +           .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +           .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +   },
>>> +};
>>> +
>>> +/* The normal parameters of pps module */
>>> +static const int pps_lut_tap4_s11[C3_PPS_TAP4_S11_H_NUM][4] =  {
>>> +   {  0, 511,   0,   0}, { -5, 511,   5,   0}, {-10, 511,  11,   0},
>>> +   {-14, 510,  17,  -1}, {-18, 508,  23,  -1}, {-22, 506,  29,  -1},
>>> +   {-25, 503,  36,  -2}, {-28, 500,  43,  -3}, {-32, 496,  51,  -3},
>>> +   {-34, 491,  59,  -4}, {-37, 487,  67,  -5}, {-39, 482,  75,  -6},
>>> +   {-41, 476,  84,  -7}, {-42, 470,  92,  -8}, {-44, 463, 102,  -9},
>>> +   {-45, 456, 111, -10}, {-45, 449, 120, -12}, {-47, 442, 130, -13},
>>> +   {-47, 434, 140, -15}, {-47, 425, 151, -17}, {-47, 416, 161, -18},
>>> +   {-47, 407, 172, -20}, {-47, 398, 182, -21}, {-47, 389, 193, -23},
>>> +   {-46, 379, 204, -25}, {-45, 369, 215, -27}, {-44, 358, 226, -28},
>>> +   {-43, 348, 237, -30}, {-43, 337, 249, -31}, {-41, 326, 260, -33},
>>> +   {-40, 316, 271, -35}, {-39, 305, 282, -36}, {-37, 293, 293, -37}
>>> +};
>>> +
>>> +static const struct c3_isp_mbus_format_info
>>> +*rsz_find_format_by_code(u32 code, u32 pad)
>>> +{
>>> +   int i;
>> unsigned
>>
>>> +
>>> +   for (i = 0; i < ARRAY_SIZE(c3_isp_rsz_mbus_formats); i++) {
>>> +           const struct c3_isp_mbus_format_info *info =
>>> +                   &c3_isp_rsz_mbus_formats[i];
>>> +
>>> +           if (info->mbus_code == code && info->pads & BIT(pad))
>>> +                   return info;
>>> +   }
>>> +
>>> +   return NULL;
>>> +}
>>> +
>>> +static void c3_isp_rsz_cfg_fmt(struct c3_isp_resizer *rsz,
>>> +                          struct v4l2_subdev_state *state)
>>> +{
>>> +   struct v4l2_mbus_framefmt *fmt;
>>> +
>>> +   fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>>> +
>>> +   c3_isp_write(rsz->isp, C3_DISP_REG(ISP_DISP0_TOP_IN_SIZE, rsz->id),
>>> +                DISP_TOP_IN_HSIZE(fmt->width) | DISP_TOP_IN_VSIZE(fmt->height));
>>> +}
>>> +
>>> +static void c3_isp_rsz_crop_enable(struct c3_isp_resizer *rsz,
>>> +                              struct v4l2_subdev_state *state)
>>> +{
>>> +   struct v4l2_rect *crop;
>>> +
>>> +   crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>>> +
>>> +   c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_START, rsz->id),
>>> +                DISP_CRP2_VSTART(crop->top) | DISP_CRP2_HSTART(crop->left));
>>> +   c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_SIZE, rsz->id),
>>> +                DISP_CRP2_VSIZE(crop->height) | DISP_CRP2_HSIZE(crop->width));
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
>>> +                      DISP_CRP2_EN, DISP_CRP2_EN);
>>> +}
>>> +
>>> +static void c3_isp_rsz_pps_size(struct c3_isp_resizer *rsz,
>>> +                           struct c3_isp_pps_io_size *io_size)
>>> +{
>>> +   int thsize = io_size->thsize;
>>> +   int tvsize = io_size->tvsize;
>>> +   u32 ohsize = io_size->ohsize;
>>> +   u32 ovsize = io_size->ovsize;
>>> +   u32 ihsize = io_size->ihsize;
>>> +   u32 max_hsize = io_size->max_hsize;
>>> +   int step_h_integer, step_v_integer;
>>> +   int step_h_fraction, step_v_fraction;
>>> +   int yuv444to422_en;
>>> +
>>> +   /* Calculate the integer part of horizonal scaler step */
>>> +   step_h_integer = thsize / ohsize;
>>> +
>>> +   /* Calculate the vertical part of horizonal scaler step */
>>> +   step_v_integer = tvsize / ovsize;
>>> +
>>> +   /*
>>> +    * Calculate the fraction part of horizonal scaler step.
>>> +    * step_h_fraction = (source / dest) * 2^24,
>>> +    * so step_h_fraction = ((source << 12) / dest) << 12.
>>> +    */
>>> +   step_h_fraction = ((thsize << 12) / ohsize) << 12;
>>> +
>>> +   /*
>>> +    * Calculate the fraction part of vertical scaler step
>>> +    * step_v_fraction = (source / dest) * 2^24,
>>> +    * so step_v_fraction = ((source << 12) / dest) << 12.
>>> +    */
>>> +   step_v_fraction = ((tvsize << 12) / ovsize) << 12;
>>> +
>>> +   yuv444to422_en = ihsize > (max_hsize / 2) ? 1 : 0;
>>> +
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_444TO422, rsz->id),
>>> +                      PPS_444TO422_EN_MASK, yuv444to422_en);
>>> +
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>>> +                      PPS_VSC_FRACTION_PART_MASK, step_v_fraction);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>>> +                      PPS_VSC_INTEGER_PART_MASK,
>>> +                      step_v_integer << PPS_VSC_INTEGER_PART_SHIFT);
>>> +
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>>> +                      PPS_HSC_FRACTION_PART_MASK, step_h_fraction);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>>> +                      PPS_HSC_INTEGER_PART_MASK,
>>> +                      step_h_integer << PPS_HSC_INTEGER_PART_SHIFT);
>>> +
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
>>> +                      PPS_PREHSC_LUMA_COEF0_MASK, PPS_PREHSC_LUMA_COEF0_INIT);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
>>> +                      PPS_PREHSC_LUMA_COEF1_MASK,
>>> +                      PPS_PREHSC_LUMA_COEF1_INIT << PPS_PREHSC_LUMA_COEF1_SHIFT);
>>> +
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
>>> +                      PPS_PREHSC_LUMA_COEF2_MASK, PPS_PREHSC_LUMA_COEF2_INIT);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
>>> +                      PPS_PREHSC_LUMA_COEF3_MASK,
>>> +                      PPS_PREHSC_LUMA_COEF3_INIT << PPS_PREHSC_LUMA_COEF3_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_rsz_pps_lut(struct c3_isp_resizer *rsz, u32 ctype)
>>> +{
>>> +   int i;
>> unsigned
>>
>>> +
>>> +   /*
>>> +    * Default value of this register is 0,
>>> +    * so only need to set SCALE_LUMA_COEF_S11_MODE
>>> +    * and SCALE_LUMA_CTYPE.
>> You can fit this in 2 lines ?
>>
>>> +    * This register needs to be written in one time.
>>> +    */
>>> +   c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_LUMA, rsz->id),
>>> +                SCALE_LUMA_COEF_S11_MODE | SCALE_LUMA_CTYPE(ctype));
>>> +
>>> +   for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
>>> +           c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>>> +                        SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
>>> +                        SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
>>> +           c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>>> +                        SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
>>> +                        SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][3]));
>>> +   }
>>> +
>>> +   /*
>>> +    * Default value of this register is 0,
>>> +    * so only need to set SCALE_CHRO_COEF_S11_MODE
>>> +    * and SCALE_CHRO_CTYPE.
>> same
>>
>>> +    * This register needs to be written in one time.
>>> +    */
>>> +   c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_CHRO, rsz->id),
>>> +                SCALE_CHRO_COEF_S11_MODE | SCALE_CHRO_CTYPE(ctype));
>>> +
>>> +   for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
>>> +           c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>>> +                        SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
>>> +                        SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
>>> +           c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>>> +                        SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
>>> +                        SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][3]));
>>> +   }
>>> +}
>>> +
>>> +static void c3_isp_rsz_pps_disable(struct c3_isp_resizer *rsz)
>>> +{
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_HSC_EN_MASK, 0);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_VSC_EN_MASK, 0);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_PREVSC_EN_MASK, 0);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_PREHSC_EN_MASK, 0);
>>> +}
>>> +
>>> +static int c3_isp_rsz_pps_enable(struct c3_isp_resizer *rsz,
>>> +                            struct v4l2_subdev_state *state)
>>> +{
>>> +   struct v4l2_rect *crop;
>>> +   struct v4l2_rect *cmps;
>>> +   int max_hsize;
>>> +   int hsc_en, vsc_en;
>>> +   int preh_en, prev_en;
>>> +   u32 reg_prehsc_rate;
>>> +   u32 reg_prevsc_flt_num;
>>> +   int pre_vscale_max_hsize;
>>> +   u32 ihsize_after_pre_hsc;
>>> +   u32 ihsize_after_pre_hsc_alt;
>>> +   u32 reg_vsc_tap_num_alt;
>>> +   u32 ihsize;
>>> +   u32 ivsize;
>>> +   struct c3_isp_pps_io_size io_size;
>>> +
>>> +   crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>>> +   cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>>> +
>>> +   ihsize = crop->width;
>>> +   ivsize = crop->height;
>>> +
>>> +   hsc_en = (ihsize == cmps->width) ? C3_SCALE_DIS : C3_SCALE_EN;
>>> +   vsc_en = (ivsize == cmps->height) ? C3_SCALE_DIS : C3_SCALE_EN;
>>> +
>>> +   /* Disable pps when there no need to use pps */
>>> +   if (!hsc_en && !vsc_en) {
>>> +           c3_isp_rsz_pps_disable(rsz);
>>> +           return 0;
>>> +   }
>>> +
>>> +   /*
>>> +    * Pre-scale needs to be enable
>>> +    * if the down scaling factor exceeds 4.
>>> +    */
>>> +   preh_en = (ihsize > cmps->width * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
>>> +   prev_en = (ivsize > cmps->height * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
>>> +
>>> +   if (rsz->id == C3_ISP_RSZ_2) {
>>> +           max_hsize = C3_ISP_MAX_WIDTH;
>>> +           /*
>>> +            * Set vertical tap number and
>>> +            * the max hsize of pre-vertical scale.
>>> +            */
>>> +           reg_prevsc_flt_num = 4;
>>> +           pre_vscale_max_hsize = max_hsize / 2;
>>> +   } else {
>>> +           max_hsize = C3_ISP_DEFAULT_WIDTH;
>>> +           preh_en  = (ihsize > max_hsize) ? C3_SCALE_EN : C3_SCALE_DIS;
>>> +           /*
>>> +            * Set vertical tap number and
>>> +            * the max hsize of pre-vertical scale.
>>> +            */
>>> +           if (ihsize > (max_hsize / 2) &&
>>> +               ihsize <= max_hsize && prev_en) {
>>> +                   reg_prevsc_flt_num = 2;
>>> +                   pre_vscale_max_hsize = max_hsize;
>>> +           } else {
>>> +                   reg_prevsc_flt_num = 4;
>>> +                   pre_vscale_max_hsize = max_hsize / 2;
>>> +           }
>>> +   }
>>> +
>>> +   /*
>>> +    * Set pre-horizonal scale rate and
>>> +    * the hsize of after pre-horizonal scale.
>>> +    */
>>> +   if (preh_en) {
>>> +           reg_prehsc_rate = 1;
>>> +           ihsize_after_pre_hsc = DIV_ROUND_UP(ihsize, 2);
>>> +   } else {
>>> +           reg_prehsc_rate = 0;
>>> +           ihsize_after_pre_hsc = ihsize;
>>> +   }
>>> +
>>> +   /* Change pre-horizonal scale rate */
>>> +   if (prev_en && ihsize_after_pre_hsc >= pre_vscale_max_hsize)
>>> +           reg_prehsc_rate += 1;
>>> +
>>> +   /* Set the actual hsize of after pre-horizonal scale */
>>> +   if (preh_en)
>>> +           ihsize_after_pre_hsc_alt =
>>> +                   DIV_ROUND_UP(ihsize, 1 << reg_prehsc_rate);
>>> +   else
>>> +           ihsize_after_pre_hsc_alt = ihsize;
>>> +
>>> +   /* Set vertical scaler bank length */
>>> +   if (ihsize_after_pre_hsc_alt <= (max_hsize / 2))
>>> +           reg_vsc_tap_num_alt = 4;
>>> +   else if (ihsize_after_pre_hsc_alt <= max_hsize)
>>> +           reg_vsc_tap_num_alt = prev_en ? 2 : 4;
>>> +   else
>>> +           reg_vsc_tap_num_alt = prev_en ? 4 : 2;
>>> +
>>> +   io_size.thsize = ihsize_after_pre_hsc_alt;
>>> +   io_size.tvsize = prev_en ? DIV_ROUND_UP(ivsize, 2) : ivsize;
>>> +   io_size.ohsize = cmps->width;
>>> +   io_size.ovsize = cmps->height;
>>> +   io_size.ihsize = ihsize;
>>> +   io_size.max_hsize = max_hsize;
>>> +
>>> +   c3_isp_rsz_pps_size(rsz, &io_size);
>>> +   c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_0);
>>> +   c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_2);
>>> +
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_VSC_TAP_NUM_MASK, reg_vsc_tap_num_alt);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_HSC_TAP_NUM_MASK,
>>> +                      PPS_HSC_TAP_NUM_INIT << PPS_HSC_TAP_NUM_SHIFT);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_PREVSC_FLT_NUM_MASK,
>>> +                      reg_prevsc_flt_num << PPS_PREVSC_FLT_NUM_SHIFT);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_PREHSC_FLT_NUM_MASK,
>>> +                      PPS_PREHSC_FLT_NUM_INIT << PPS_PREHSC_FLT_NUM_SHIFT);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_PREVSC_RATE_MASK, prev_en << PPS_PREVSC_RATE_SHIFT);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_PREHSC_RATE_MASK, reg_prehsc_rate << PPS_PREHSC_RATE_SHIFT);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_HSC_EN_MASK, hsc_en << PPS_HSC_EN_SHIFT);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_VSC_EN_MASK, vsc_en << PPS_VSC_EN_SHIFT);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_PREVSC_EN_MASK, prev_en << PPS_PREVSC_EN_SHIFT);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_PREHSC_EN_MASK, preh_en << PPS_PREHSC_EN_SHIFT);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_HSC_NOR_RS_BITS_MASK,
>>> +                      PPS_HSC_NOR_RS_BITS_INIT << PPS_HSC_NOR_RS_BITS_SHIFT);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                      PPS_VSC_NOR_RS_BITS_MASK,
>>> +                      PPS_VSC_NOR_RS_BITS_INIT << PPS_VSC_NOR_RS_BITS_SHIFT);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz)
>>> +{
>>> +   struct v4l2_subdev_state *state;
>>> +
>>> +   state = v4l2_subdev_lock_and_get_active_state(&rsz->sd);
>>> +
>>> +   c3_isp_rsz_cfg_fmt(rsz, state);
>>> +   c3_isp_rsz_crop_enable(rsz, state);
>>> +   c3_isp_rsz_pps_enable(rsz, state);
>>> +
>>> +   c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN,
>>> +                      TOP_DISP_EN(rsz->id), TOP_DISP_EN(rsz->id));
>>> +
>>> +   v4l2_subdev_unlock_state(state);
>>> +}
>>> +
>>> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
>>> +{
>>> +   c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, TOP_DISP_EN(rsz->id), 0);
>>> +   c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
>>> +                      DISP_CRP2_EN, 0x0);
>>> +
>>> +   c3_isp_rsz_pps_disable(rsz);
>>> +}
>>> +
>>> +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
>>> +                             struct v4l2_subdev_state *state,
>>> +                             struct v4l2_subdev_krouting *routing)
>>> +{
>>> +   static const struct v4l2_mbus_framefmt format = {
>>> +           .width = C3_ISP_DEFAULT_WIDTH,
>>> +           .height = C3_ISP_DEFAULT_HEIGHT,
>>> +           .code = C3_ISP_RSZ_DEF_PAD_FMT,
>>> +           .field = V4L2_FIELD_NONE,
>>> +           .colorspace = V4L2_COLORSPACE_SRGB,
>>> +           .ycbcr_enc = V4L2_YCBCR_ENC_601,
>>> +           .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>>> +           .xfer_func = V4L2_XFER_FUNC_SRGB,
>>> +   };
>>> +   int ret;
>>> +
>>> +   ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
>>> +                              struct v4l2_subdev_state *state)
>>> +{
>>> +   struct v4l2_subdev_route routes;
>>> +   struct v4l2_subdev_krouting routing;
>>> +
>>> +   routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
>>> +   routes.sink_stream = 0;
>>> +   routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
>>> +   routes.source_stream = 0;
>>> +   routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>>> +
>>> +   routing.num_routes = 1;
>>> +   routing.routes = &routes;
>>> +
>>> +   return c3_isp_rsz_cfg_routing(sd, state, &routing);
>>> +}
>>> +
>>> +static int c3_isp_rsz_set_routing(struct v4l2_subdev *sd,
>>> +                             struct v4l2_subdev_state *state,
>>> +                             enum v4l2_subdev_format_whence which,
>>> +                             struct v4l2_subdev_krouting *routing)
>>> +{
>>> +   bool is_streaming = v4l2_subdev_is_streaming(sd);
>>> +
>>> +   if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>>> +           return -EBUSY;
>>> +
>>> +   return c3_isp_rsz_cfg_routing(sd, state, routing);
>>> +}
>> Unless there are reasons I missed, I would drop routing support from
>> the resizers
>>
>>> +
>>> +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
>>> +                                struct v4l2_subdev_state *state,
>>> +                                struct v4l2_subdev_mbus_code_enum *code)
>>> +{
>>> +   if (code->index >= ARRAY_SIZE(c3_isp_rsz_mbus_formats))
>>> +           return -EINVAL;
>>> +
>>> +   code->code = c3_isp_rsz_mbus_formats[code->index].mbus_code;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static void c3_isp_rsz_set_sink_fmt(struct v4l2_subdev_state *state,
>>> +                               struct v4l2_subdev_format *format)
>>> +{
>>> +   struct v4l2_mbus_framefmt *sink_fmt;
>>> +   const struct c3_isp_mbus_format_info *isp_fmt;
>>> +   struct v4l2_rect *sink_crop;
>>> +   struct v4l2_rect *sink_cmps;
>>> +
>>> +   sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>> +   sink_crop = v4l2_subdev_state_get_crop(state, format->pad);
>>> +   sink_cmps = v4l2_subdev_state_get_compose(state, format->pad);
>>> +
>>> +   isp_fmt = rsz_find_format_by_code(format->format.code, format->pad);
>>> +   if (!isp_fmt)
>>> +           sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>>> +   else
>>> +           sink_fmt->code = format->format.code;
>>> +
>>> +   sink_fmt->width = clamp_t(u32, format->format.width,
>>> +                             C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>>> +   sink_fmt->height = clamp_t(u32, format->format.height,
>>> +                              C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>>> +
>>> +   sink_crop->width = sink_fmt->width;
>>> +   sink_crop->height = sink_fmt->height;
>>> +   sink_crop->left = 0;
>>> +   sink_crop->top = 0;
>>> +
>>> +   sink_cmps->width = sink_crop->width;
>>> +   sink_cmps->height = sink_crop->height;
>>> +   sink_cmps->left = 0;
>>> +   sink_cmps->top = 0;
>>> +
>>> +   format->format = *sink_fmt;
>>> +}
>>> +
>>> +static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state,
>>> +                                 struct v4l2_subdev_format *format)
>>> +{
>>> +   const struct c3_isp_mbus_format_info *rsz_fmt;
>>> +   struct v4l2_mbus_framefmt *src_fmt;
>>> +   struct v4l2_rect *sink_crop;
>>> +   struct v4l2_rect *sink_cmps;
>>> +
>>> +   src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>> +   sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>>> +   sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>>> +
>>> +   rsz_fmt = rsz_find_format_by_code(format->format.code, format->pad);
>>> +   if (!rsz_fmt)
>>> +           src_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>>> +   else
>>> +           src_fmt->code = format->format.code;
>>> +
>>> +   src_fmt->width = clamp_t(u32, format->format.width,
>>> +                            C3_ISP_MIN_WIDTH, sink_crop->width);
>>> +   src_fmt->height = clamp_t(u32, format->format.height,
>>> +                             C3_ISP_MIN_HEIGHT, sink_crop->height);
>>> +
>>> +   /* The sink compose size must be same with the source size. */
>>> +   sink_cmps->width = src_fmt->width;
>>> +   sink_cmps->height = src_fmt->height;
>> Shouldn't it be the other way around ? The source sizes should always
>> match the sink compose rectangle sizes ?
>>
>>> +
>>> +   format->format = *src_fmt;
>>> +}
>>> +
>>> +static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd,
>>> +                         struct v4l2_subdev_state *state,
>>> +                         struct v4l2_subdev_format *format)
>>> +{
>>> +   if (format->pad == C3_ISP_RESIZER_PAD_SINK) {
>>> +           c3_isp_rsz_set_sink_fmt(state, format);
>>> +   } else if (format->pad == C3_ISP_RESIZER_PAD_SOURCE) {
>>> +           c3_isp_rsz_set_source_fmt(state, format);
>>> +   } else {
>> This can't happen
>>
>>> +           dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
>>> +           return -ENOTTY;
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd,
>>> +                               struct v4l2_subdev_state *state,
>>> +                               struct v4l2_subdev_selection *sel)
>>> +{
>>> +   struct v4l2_mbus_framefmt *fmt;
>>> +   struct v4l2_rect *crop;
>>> +   struct v4l2_rect *cmps;
>>> +
>>> +   if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
>>> +           return -EINVAL;
>>> +
>>> +   switch (sel->target) {
>>> +   case V4L2_SEL_TGT_CROP_BOUNDS:
>>> +           fmt = v4l2_subdev_state_get_format(state, sel->pad);
>>> +           sel->r.width = fmt->width;
>>> +           sel->r.height = fmt->height;
>>> +           sel->r.left = 0;
>>> +           sel->r.top = 0;
>>> +           break;
>>> +   case V4L2_SEL_TGT_COMPOSE_BOUNDS:
>>> +           crop = v4l2_subdev_state_get_crop(state, sel->pad);
>>> +           sel->r.width = crop->width;
>>> +           sel->r.height = crop->height;
>>> +           sel->r.left = 0;
>>> +           sel->r.top = 0;
>>> +           break;
>>> +   case V4L2_SEL_TGT_CROP:
>>> +           crop = v4l2_subdev_state_get_crop(state, sel->pad);
>>> +           sel->r = *crop;
>>> +           break;
>>> +   case V4L2_SEL_TGT_COMPOSE:
>>> +           cmps = v4l2_subdev_state_get_compose(state, sel->pad);
>>> +           sel->r = *cmps;
>>> +           break;
>>> +   default:
>>> +           return -EINVAL;
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd,
>>> +                               struct v4l2_subdev_state *state,
>>> +                               struct v4l2_subdev_selection *sel)
>>> +{
>>> +   struct v4l2_mbus_framefmt *fmt;
>>> +   struct v4l2_rect *crop;
>>> +   struct v4l2_rect *cmps;
>>> +
>>> +   if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
>>> +           return -EINVAL;
>>> +
>>> +   switch (sel->target) {
>>> +   case V4L2_SEL_TGT_CROP:
>>> +           fmt = v4l2_subdev_state_get_format(state, sel->pad);
>>> +           crop = v4l2_subdev_state_get_crop(state, sel->pad);
>>> +
>>> +           sel->r.left = clamp_t(s32, sel->r.left, 0, fmt->width - 1);
>>> +           sel->r.top = clamp_t(s32, sel->r.top, 0, fmt->height - 1);
>>> +           sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH,
>>> +                                fmt->width - sel->r.left);
>>> +           sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT,
>>> +                                 fmt->height - sel->r.top);
>>> +
>>> +           crop->width = ALIGN(sel->r.width, 2);
>>> +           crop->height = ALIGN(sel->r.height, 2);
>>> +           crop->left = sel->r.left;
>>> +           crop->top = sel->r.top;
>>> +
>>> +           sel->r = *crop;
>>> +           break;
>>> +   case V4L2_SEL_TGT_COMPOSE:
>>> +           crop = v4l2_subdev_state_get_crop(state, sel->pad);
>>> +           cmps = v4l2_subdev_state_get_compose(state, sel->pad);
>>> +
>>> +           sel->r.left = 0;
>>> +           sel->r.top = 0;
>>> +           sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH, crop->width);
>>> +           sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT, crop->height);
>>> +
>>> +           cmps->width = ALIGN(sel->r.width, 2);
>>> +           cmps->height = ALIGN(sel->r.height, 2);
>>> +           cmps->left = sel->r.left;
>>> +           cmps->top = sel->r.top;
>>> +
>>> +           sel->r = *cmps;
>>> +
>>> +           fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
>>> +           fmt->width = cmps->width;
>>> +           fmt->height = cmps->height;
>>> +           break;
>>> +   default:
>>> +           return -EINVAL;
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_rsz_init_state(struct v4l2_subdev *sd,
>>> +                            struct v4l2_subdev_state *state)
>>> +{
>>> +   struct v4l2_mbus_framefmt *sink_fmt;
>>> +   struct v4l2_mbus_framefmt *src_fmt;
>>> +   struct v4l2_rect *crop;
>>> +   struct v4l2_rect *cmps;
>>> +
>>> +   sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>>> +   sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>>> +   sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>>> +   sink_fmt->field = V4L2_FIELD_NONE;
>>> +   sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>>> +   sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>> +   sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>>> +   sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>> +   sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>>> +
>>> +   crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>>> +   crop->width = C3_ISP_DEFAULT_WIDTH;
>>> +   crop->height = C3_ISP_DEFAULT_HEIGHT;
>>> +   crop->left = 0;
>>> +   crop->top = 0;
>>> +
>>> +   cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>>> +   cmps->width = C3_ISP_DEFAULT_WIDTH;
>>> +   cmps->height = C3_ISP_DEFAULT_HEIGHT;
>>> +   cmps->left = 0;
>>> +   cmps->top = 0;
>>> +
>>> +   src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
>>> +   *src_fmt = *sink_fmt;
>>> +
>>> +   return c3_isp_rsz_init_routing(sd, state);
>>> +}
>>> +
>>> +static const struct v4l2_subdev_pad_ops c3_isp_rsz_pad_ops = {
>>> +   .enum_mbus_code = c3_isp_rsz_enum_mbus_code,
>>> +   .get_fmt = v4l2_subdev_get_fmt,
>>> +   .set_fmt = c3_isp_rsz_set_fmt,
>>> +   .get_selection = c3_isp_rsz_get_selection,
>>> +   .set_selection = c3_isp_rsz_set_selection,
>>> +   .set_routing = c3_isp_rsz_set_routing,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_ops c3_isp_rsz_subdev_ops = {
>>> +   .pad = &c3_isp_rsz_pad_ops,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_internal_ops c3_isp_rsz_internal_ops = {
>>> +   .init_state = c3_isp_rsz_init_state,
>>> +};
>>> +
>>> +/* Media entity operations */
>>> +static const struct media_entity_operations c3_isp_rsz_entity_ops = {
>>> +   .link_validate = v4l2_subdev_link_validate,
>>> +};
>>> +
>>> +static int c3_isp_rsz_register(struct c3_isp_resizer *rsz)
>>> +{
>>> +   struct v4l2_subdev *sd = &rsz->sd;
>>> +   int ret;
>>> +
>>> +   v4l2_subdev_init(sd, &c3_isp_rsz_subdev_ops);
>>> +   sd->owner = THIS_MODULE;
>>> +   sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>> +   sd->internal_ops = &c3_isp_rsz_internal_ops;
>>> +   snprintf(sd->name, sizeof(sd->name), "isp-resizer%u", rsz->id);
>> maybe "c3-isp_resizer%u"
>>
>>> +
>>> +   sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
>>> +   sd->entity.ops = &c3_isp_rsz_entity_ops;
>>> +
>>> +   sd->dev = rsz->isp->dev;
>>> +   v4l2_set_subdevdata(sd, rsz);
>>> +
>>> +   rsz->pads[C3_ISP_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>>> +   rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>>> +   ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_PAD_MAX, rsz->pads);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   ret = v4l2_subdev_init_finalize(sd);
>>> +   if (ret)
>>> +           goto err_entity_cleanup;
>>> +
>>> +   ret = v4l2_device_register_subdev(&rsz->isp->v4l2_dev, sd);
>>> +   if (ret)
>>> +           goto err_subdev_cleanup;
>>> +
>>> +   return 0;
>>> +
>>> +err_subdev_cleanup:
>>> +   v4l2_subdev_cleanup(sd);
>>> +err_entity_cleanup:
>>> +   media_entity_cleanup(&sd->entity);
>>> +   return ret;
>>> +}
>>> +
>>> +static void c3_isp_rsz_unregister(struct c3_isp_resizer *rsz)
>>> +{
>>> +   struct v4l2_subdev *sd = &rsz->sd;
>>> +
>>> +   v4l2_device_unregister_subdev(sd);
>>> +   v4l2_subdev_cleanup(sd);
>>> +   media_entity_cleanup(&sd->entity);
>>> +}
>>> +
>>> +int c3_isp_resizers_register(struct c3_isp_device *isp)
>>> +{
>>> +   u32 i;
>>> +   int ret;
>>> +
>>> +   for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
>>> +           struct c3_isp_resizer *rsz = &isp->resizers[i];
>>> +
>>> +           rsz->id = i;
>>> +           rsz->isp = isp;
>>> +
>>> +           if (rsz->id == C3_ISP_RSZ_0)
>>> +                   rsz->cap = &isp->caps[C3_ISP_CAP_DEV_0];
>>> +           else if (rsz->id == C3_ISP_RSZ_1)
>>> +                   rsz->cap = &isp->caps[C3_ISP_CAP_DEV_1];
>>> +           else
>>> +                   rsz->cap = &isp->caps[C3_ISP_CAP_DEV_2];
>>> +
>>> +           ret = c3_isp_rsz_register(rsz);
>>> +           if (ret) {
>>> +                   rsz->isp = NULL;
>>> +                   c3_isp_resizers_unregister(isp);
>>> +                   return ret;
>>> +           }
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +void c3_isp_resizers_unregister(struct c3_isp_device *isp)
>>> +{
>>> +   u32 i;
>>> +
>>> +   for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
>>> +           struct c3_isp_resizer *rsz = &isp->resizers[i];
>>> +
>>> +           if (rsz->isp)
>>> +                   c3_isp_rsz_unregister(rsz);
>>> +   };
>>> +}
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
>>> new file mode 100644
>>> index 000000000000..72024442d48f
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
>>> @@ -0,0 +1,488 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/cleanup.h>
>>> +#include <linux/pm_runtime.h>
>>> +
>>> +#include <media/v4l2-ctrls.h>
>>> +#include <media/v4l2-event.h>
>>> +#include <media/v4l2-ioctl.h>
>>> +#include <media/v4l2-mc.h>
>>> +#include <media/videobuf2-dma-contig.h>
>>> +
>>> +#include "c3-isp-common.h"
>>> +#include "c3-isp-regs.h"
>>> +#include "include/uapi/c3-isp-config.h"
>>> +
>>> +/* Hardware configuration */
>>> +
>>> +static void c3_isp_stats_af_init(struct c3_isp_stats *stats)
>>> +{
>>> +   c3_isp_write(stats->isp, ISP_AF_ROI0_WIN01,
>>> +                AF_ROI_XYWIN_00_INIT | AF_ROI_XYWIN_01_INIT);
>>> +
>>> +   c3_isp_write(stats->isp, ISP_AF_ROI0_WIN23,
>>> +                AF_ROI_XYWIN_02_INIT | AF_ROI_XYWIN_03_INIT);
>>> +
>>> +   c3_isp_write(stats->isp, ISP_AF_ROI1_WIN01,
>>> +                AF_ROI_XYWIN_10_INIT | AF_ROI_XYWIN_11_INIT);
>>> +
>>> +   c3_isp_write(stats->isp, ISP_AF_ROI1_WIN23,
>>> +                AF_ROI_XYWIN_12_INIT | AF_ROI_XYWIN_13_INIT);
>>> +
>>> +   /* 0: old statistics output, 1: new statistics output. */
>>> +   c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL,
>>> +                      AF_STAT_SELECT, 0x1 << AF_STAT_SELECT_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_stats_ae_init(struct c3_isp_stats *stats)
>>> +{
>>> +   c3_isp_write(stats->isp, ISP_AE_ROI0_WIN01,
>>> +                AE_ROI_XYWIN_00_INIT | AE_ROI_XYWIN_01_INIT);
>>> +
>>> +   c3_isp_write(stats->isp, ISP_AE_ROI0_WIN23,
>>> +                AE_ROI_XYWIN_02_INIT | AE_ROI_XYWIN_03_INIT);
>>> +
>>> +   c3_isp_write(stats->isp, ISP_AE_ROI1_WIN01,
>>> +                AE_ROI_XYWIN_10_INIT | AE_ROI_XYWIN_11_INIT);
>>> +
>>> +   c3_isp_write(stats->isp, ISP_AE_ROI1_WIN23,
>>> +                AE_ROI_XYWIN_12_INIT | AE_ROI_XYWIN_13_INIT);
>>> +
>>> +   c3_isp_write(stats->isp, ISP_AE_STAT_THD01,
>>> +                AE_STAT_THRD0_INIT | AE_STAT_THRD1_INIT);
>>> +
>>> +   c3_isp_write(stats->isp, ISP_AE_STAT_THD23,
>>> +                AE_STAT_THRD2_INIT | AE_STAT_THRD3_INIT);
>>> +
>>> +   /* Set 0 when ae_stat_switch is not 0 */
>>> +   c3_isp_update_bits(stats->isp, ISP_AE_CTRL,
>>> +                      AE_CTRL_INPUT_2LINE_TOGETHER, 0);
>>> +
>>> +   /* Configure ae luma mode */
>>> +   c3_isp_update_bits(stats->isp, ISP_AE_CTRL, AE_CTRL_LUMA_MODE_MASK,
>>> +                      AE_CTRL_LUMA_MODE_FILTER << AE_CTRL_LUMA_MODE_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_stats_awb_init(struct c3_isp_stats *stats)
>>> +{
>>> +   /* Initialize the awb statistics rectangle of image */
>>> +   c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN01,
>>> +                AWB_ROI_XYWIN_00_INIT | AWB_ROI_XYWIN_01_INIT);
>>> +   c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN23,
>>> +                AWB_ROI_XYWIN_02_INIT | AWB_ROI_XYWIN_03_INIT);
>>> +
>>> +   c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN01,
>>> +                AWB_ROI_XYWIN_10_INIT | AWB_ROI_XYWIN_11_INIT);
>>> +   c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN23,
>>> +                AWB_ROI_XYWIN_12_INIT | AWB_ROI_XYWIN_13_INIT);
>>> +}
>>> +
>>> +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
>>> +{
>>> +   struct c3_isp_device *isp = stats->isp;
>>> +   struct c3_isp_stats_info *stats_info = stats->buff->vaddr;
>>> +   u32 awb_dma_size = sizeof(stats_info->awb_stats);
>>> +   u32 ae_dma_size = sizeof(stats_info->ae_stats);
>>> +   u32 awb_dma_addr = stats->buff->paddr;
>>> +   u32 af_dma_addr;
>>> +   u32 ae_dma_addr;
>>> +
>>> +   ae_dma_addr = awb_dma_addr + awb_dma_size;
>>> +   af_dma_addr = ae_dma_addr + ae_dma_size;
>>> +
>>> +   c3_isp_update_bits(isp, VIU_DMAWR_BADDR0, VIU_DMAWR_AF_BADDR_MASK,
>>> +                      VIU_DMAWR_AF_BADDR(af_dma_addr));
>>> +   c3_isp_update_bits(isp, VIU_DMAWR_BADDR1, VIU_DMAWR_AWB_BADDR_MASK,
>>> +                      VIU_DMAWR_AWB_BADDR(awb_dma_addr));
>>> +   c3_isp_update_bits(isp, VIU_DMAWR_BADDR2, VIU_DMAWR_AE_BADDR_MASK,
>>> +                      VIU_DMAWR_AE_BADDR(ae_dma_addr));
>>> +}
>>> +
>>> +static void c3_isp_stats_enable(struct c3_isp_stats *stats)
>>> +{
>>> +   c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>> +                      TOP_3A_AE_STAT_EN, TOP_3A_AE_STAT_EN);
>>> +   c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>> +                      TOP_3A_AWB_STAT_EN, TOP_3A_AWB_STAT_EN);
>>> +   c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>> +                      TOP_3A_AF_STAT_EN, TOP_3A_AF_STAT_EN);
>>> +}
>>> +
>>> +static void c3_isp_stats_disable(struct c3_isp_stats *stats)
>>> +{
>>> +   c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>> +                      TOP_3A_AE_STAT_EN, 0);
>>> +   c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>> +                      TOP_3A_AWB_STAT_EN, 0);
>>> +   c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>> +                      TOP_3A_AF_STAT_EN, 0);
>>> +}
>>> +
>>> +/* The unit of dma_size is 16 bytes */
>>> +static void c3_isp_stats_cfg_dmawr_size(struct c3_isp_stats *stats)
>>> +{
>>> +   u32 dma_size;
>>> +
>>> +   dma_size = sizeof(struct af_stats_info) / 16;
>>> +   c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0,
>>> +                      VIU_DMAWR_SIZE_AF_MASK, dma_size);
>>> +
>>> +   dma_size = sizeof(struct awb_stats_info) / 16;
>>> +   c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE_AWB_MASK,
>>> +                      dma_size << VIU_DMAWR_SIZE_AWB_SHIFT);
>>> +
>>> +   dma_size = sizeof(struct ae_stats_info) / 16;
>>> +   c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1,
>>> +                      VIU_DMAWR_SIZE_AE_MASK, dma_size);
>>> +}
>>> +
>>> +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
>>> +{
>>> +   stats->buff = list_first_entry_or_null(&stats->pending,
>>> +                                          struct c3_isp_vb2_buffer, list);
>>> +   if (stats->buff) {
>>> +           c3_isp_stats_cfg_dmawr_addr(stats);
>>> +           list_del(&stats->buff->list);
>>> +   }
>>> +}
>>> +
>>> +static void c3_isp_stats_start(struct c3_isp_stats *stats)
>>> +{
>>> +   c3_isp_stats_af_init(stats);
>>> +   c3_isp_stats_ae_init(stats);
>>> +   c3_isp_stats_awb_init(stats);
>>> +
>>> +   c3_isp_stats_cfg_dmawr_size(stats);
>>> +   c3_isp_stats_cfg_buff(stats);
>>> +   c3_isp_stats_enable(stats);
>>> +
>>> +   stats->is_streaming = true;
>>> +}
>>> +
>>> +static void c3_isp_stats_stop(struct c3_isp_stats *stats)
>>> +{
>>> +   stats->is_streaming = false;
>>> +
>>> +   c3_isp_stats_disable(stats);
>>> +}
>>> +
>>> +static void c3_isp_stats_return_buffers(struct c3_isp_stats *stats,
>>> +                                   enum vb2_buffer_state state)
>>> +{
>>> +   unsigned long flags;
>>> +   struct c3_isp_vb2_buffer *buff;
>>> +
>>> +   spin_lock_irqsave(&stats->buff_lock, flags);
>>> +
>>> +   if (stats->buff) {
>>> +           vb2_buffer_done(&stats->buff->vb.vb2_buf, state);
>>> +           stats->buff = NULL;
>>> +   }
>>> +
>>> +   while (!list_empty(&stats->pending)) {
>>> +           buff = list_first_entry(&stats->pending,
>>> +                                   struct c3_isp_vb2_buffer, list);
>>> +           list_del(&buff->list);
>>> +           vb2_buffer_done(&buff->vb.vb2_buf, state);
>>> +   }
>>> +
>>> +   spin_unlock_irqrestore(&stats->buff_lock, flags);
>>> +}
>>> +
>>> +static int c3_isp_stats_querycap(struct file *file, void *fh,
>>> +                            struct v4l2_capability *cap)
>>> +{
>>> +   strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>>> +   strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_stats_enum_fmt(struct file *file, void *fh,
>>> +                            struct v4l2_fmtdesc *f)
>>> +{
>>> +   struct c3_isp_stats *stats = video_drvdata(file);
>>> +
>>> +   if (f->index > 0 || f->type != stats->vb2_q.type)
>>> +           return -EINVAL;
>>> +
>>> +   f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_stats_g_fmt(struct file *file, void *fh,
>>> +                         struct v4l2_format *f)
>>> +{
>>> +   struct c3_isp_stats *stats = video_drvdata(file);
>>> +
>>> +   f->fmt.meta = stats->vfmt.fmt.meta;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
>>> +   .vidioc_querycap                = c3_isp_stats_querycap,
>>> +   .vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
>>> +   .vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
>>> +   .vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
>>> +   .vidioc_try_fmt_meta_cap        = c3_isp_stats_g_fmt,
>>> +   .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>>> +   .vidioc_querybuf                = vb2_ioctl_querybuf,
>>> +   .vidioc_qbuf                    = vb2_ioctl_qbuf,
>>> +   .vidioc_expbuf                  = vb2_ioctl_expbuf,
>>> +   .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>>> +   .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>>> +   .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>>> +   .vidioc_streamon                = vb2_ioctl_streamon,
>>> +   .vidioc_streamoff               = vb2_ioctl_streamoff,
>>> +   .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>>> +   .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>>> +};
>>> +
>>> +static const struct v4l2_file_operations isp_stats_v4l2_fops = {
>>> +   .open = v4l2_fh_open,
>>> +   .release = vb2_fop_release,
>>> +   .poll = vb2_fop_poll,
>>> +   .unlocked_ioctl = video_ioctl2,
>>> +   .mmap = vb2_fop_mmap,
>>> +};
>>> +
>>> +static int c3_isp_stats_vb2_queue_setup(struct vb2_queue *q,
>>> +                                   unsigned int *num_buffers,
>>> +                                   unsigned int *num_planes,
>>> +                                   unsigned int sizes[],
>>> +                                   struct device *alloc_devs[])
>>> +{
>>> +   if (*num_planes) {
>>> +           if (*num_planes != 1)
>>> +                   return -EINVAL;
>>> +
>>> +           if (sizes[0] < sizeof(struct c3_isp_stats_info))
>>> +                   return -EINVAL;
>>> +   } else {
>>> +           *num_planes = 1;
>>> +           sizes[0] = sizeof(struct c3_isp_stats_info);
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
>>> +{
>>> +   struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>> +   struct c3_isp_vb2_buffer *buf =
>>> +                   container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>> +   struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>>> +   unsigned long flags;
>>> +
>>> +   spin_lock_irqsave(&stats->buff_lock, flags);
>>> +
>>> +   list_add_tail(&buf->list, &stats->pending);
>>> +
>>> +   spin_unlock_irqrestore(&stats->buff_lock, flags);
>>> +}
>>> +
>>> +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
>>> +{
>>> +   struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>>> +   unsigned int size = stats->vfmt.fmt.meta.buffersize;
>>> +
>>> +   if (vb2_plane_size(vb, 0) < size) {
>>> +           dev_err(stats->isp->dev,
>>> +                   "User buffer too small (%ld < %u)\n",
>>> +                   vb2_plane_size(vb, 0), size);
>>> +           return -EINVAL;
>>> +   }
>>> +
>>> +   vb2_set_plane_payload(vb, 0, size);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
>>> +{
>>> +   struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>> +   struct c3_isp_vb2_buffer *buf =
>>> +                   container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>> +   struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>>> +
>>> +   buf->vaddr = vb2_plane_vaddr(vb, 0);
>>> +   buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>>> +
>>> +   memset(buf->vaddr, 0, stats->vfmt.fmt.meta.buffersize);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int c3_isp_stats_vb2_start_streaming(struct vb2_queue *q,
>>> +                                       unsigned int count)
>>> +{
>>> +   struct c3_isp_stats *stats = vb2_get_drv_priv(q);
>>> +   int ret;
>>> +
>>> +   guard(mutex)(&stats->isp->lock);
>>> +
>>> +   ret = pm_runtime_resume_and_get(stats->isp->dev);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   ret = video_device_pipeline_start(&stats->vdev, &stats->isp->pipe);
>>> +   if (ret) {
>>> +           dev_err(stats->isp->dev,
>>> +                   "Failed to start stats pipeline: %d\n", ret);
>>> +           goto err_pm_put;
>>> +   }
>>> +
>>> +   if (c3_isp_pipeline_ready(stats->isp)) {
>>> +           ret = v4l2_subdev_enable_streams(&stats->isp->core.sd,
>>> +                                            C3_ISP_CORE_PAD_SOURCE_STATS,
>>> +                                            BIT(0));
>>> +           if (ret)
>>> +                   goto err_pipeline_stop;
>>> +   }
>>> +
>>> +   c3_isp_stats_start(stats);
>>> +
>>> +   return 0;
>>> +
>>> +err_pipeline_stop:
>>> +   video_device_pipeline_stop(&stats->vdev);
>>> +err_pm_put:
>>> +   pm_runtime_put(stats->isp->dev);
>>> +   c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_QUEUED);
>>> +   return ret;
>>> +}
>>> +
>>> +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
>>> +{
>>> +   struct c3_isp_stats *stats = vb2_get_drv_priv(q);
>>> +
>>> +   guard(mutex)(&stats->isp->lock);
>>> +
>>> +   c3_isp_stats_stop(stats);
>>> +   c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_ERROR);
>>> +
>>> +   if (stats->isp->pipe.start_count == 1)
>>> +           v4l2_subdev_disable_streams(&stats->isp->core.sd,
>>> +                                       C3_ISP_CORE_PAD_SOURCE_STATS,
>>> +                                       BIT(0));
>>> +
>>> +   video_device_pipeline_stop(&stats->vdev);
>>> +   pm_runtime_put(stats->isp->dev);
>>> +}
>>> +
>>> +static const struct vb2_ops isp_stats_vb2_ops = {
>>> +   .queue_setup = c3_isp_stats_vb2_queue_setup,
>>> +   .buf_queue = c3_isp_stats_vb2_buf_queue,
>>> +   .buf_prepare = c3_isp_stats_vb2_buf_prepare,
>>> +   .buf_init = c3_isp_stats_vb2_buf_init,
>>> +   .wait_prepare = vb2_ops_wait_prepare,
>>> +   .wait_finish = vb2_ops_wait_finish,
>>> +   .start_streaming = c3_isp_stats_vb2_start_streaming,
>>> +   .stop_streaming = c3_isp_stats_vb2_stop_streaming,
>>> +};
>>> +
>>> +int c3_isp_stats_register(struct c3_isp_device *isp)
>>> +{
>>> +   struct c3_isp_stats *stats = &isp->stats;
>>> +   struct video_device *vdev = &stats->vdev;
>>> +   struct vb2_queue *vb2_q = &stats->vb2_q;
>>> +   int ret;
>>> +
>>> +   memset(stats, 0, sizeof(*stats));
>>> +   stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
>>> +   stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_info);
>>> +   stats->isp = isp;
>>> +   INIT_LIST_HEAD(&stats->pending);
>>> +   spin_lock_init(&stats->buff_lock);
>>> +
>>> +   mutex_init(&stats->lock);
>>> +
>>> +   snprintf(vdev->name, sizeof(vdev->name), "isp-stats");
>>> +   vdev->fops = &isp_stats_v4l2_fops;
>>> +   vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
>>> +   vdev->v4l2_dev = &isp->v4l2_dev;
>>> +   vdev->lock = &stats->lock;
>>> +   vdev->minor = -1;
>>> +   vdev->queue = vb2_q;
>>> +   vdev->release = video_device_release_empty;
>>> +   vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
>>> +   vdev->vfl_dir = VFL_DIR_RX;
>>> +   video_set_drvdata(vdev, stats);
>>> +
>>> +   vb2_q->drv_priv = stats;
>>> +   vb2_q->mem_ops = &vb2_dma_contig_memops;
>>> +   vb2_q->ops = &isp_stats_vb2_ops;
>>> +   vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
>>> +   vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>>> +   vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>>> +   vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>>> +   vb2_q->dev = isp->dev;
>>> +   vb2_q->lock = &stats->lock;
>>> +   vb2_q->min_queued_buffers = 2;
>>> +
>>> +   ret = vb2_queue_init(vb2_q);
>>> +   if (ret)
>>> +           goto err_destroy;
>>> +
>>> +   stats->pad.flags = MEDIA_PAD_FL_SINK;
>>> +   ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
>>> +   if (ret)
>>> +           goto err_queue_release;
>>> +
>>> +   ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>>> +   if (ret) {
>>> +           dev_err(isp->dev,
>>> +                   "Failed to register %s: %d\n", vdev->name, ret);
>>> +           goto err_entity_cleanup;
>>> +   }
>>> +
>>> +   return 0;
>>> +
>>> +err_entity_cleanup:
>>> +   media_entity_cleanup(&vdev->entity);
>>> +err_queue_release:
>>> +   vb2_queue_release(vb2_q);
>>> +err_destroy:
>>> +   mutex_destroy(&stats->lock);
>>> +   return ret;
>>> +}
>>> +
>>> +void c3_isp_stats_unregister(struct c3_isp_device *isp)
>>> +{
>>> +   struct c3_isp_stats *stats = &isp->stats;
>>> +
>>> +   vb2_queue_release(&stats->vb2_q);
>>> +   media_entity_cleanup(&stats->vdev.entity);
>>> +   video_unregister_device(&stats->vdev);
>>> +   mutex_destroy(&stats->lock);
>>> +}
>>> +
>>> +int c3_isp_stats_done(struct c3_isp_device *isp)
>>> +{
>>> +   struct c3_isp_stats *stats = &isp->stats;
>>> +   struct c3_isp_vb2_buffer *buff = stats->buff;
>>> +   unsigned long flags;
>>> +
>>> +   if (!stats->is_streaming)
>>> +           return -EINVAL;
>>> +
>>> +   spin_lock_irqsave(&stats->buff_lock, flags);
>>> +
>>> +   if (buff) {
>>> +           buff->vb.sequence = stats->isp->frm_sequence;
>>> +           buff->vb.vb2_buf.timestamp = ktime_get();
>>> +           buff->vb.field = V4L2_FIELD_NONE;
>>> +           vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
>>> +   }
>>> +
>>> +   c3_isp_stats_cfg_buff(stats);
>>> +
>>> +   spin_unlock_irqrestore(&stats->buff_lock, flags);
>>> +
>>> +   return 0;
>>> +}
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
>>> new file mode 100644
>>> index 000000000000..84ff5741357a
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
>>> @@ -0,0 +1,537 @@
>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#ifndef __C3_ISP_CONFIG_H__
>>> +#define __C3_ISP_CONFIG_H__
>> When moving to include/uapi/linux/media/amlogic prefix this with
>> _UAPI_
>>
>>> +
>>> +#define AF_STAT_BLKH_NUM             17
>>> +#define AF_STAT_BLKV_NUM             15
>>> +#define AF_STAT_BLK_NUM              (AF_STAT_BLKH_NUM * AF_STAT_BLKV_NUM)
>>> +/* AF stats block size need to be aligned with 2 */
>>> +#define AF_STAT_BLK_SIZE             ALIGN(AF_STAT_BLK_NUM, 2)
>>> +#define AE_HISTOGRAM_SIZE       1024
>>> +#define AE_STAT_BLKH_NUM             17
>>> +#define AE_STAT_BLKV_NUM             15
>>> +#define AE_STAT_BLK_NUM              (AE_STAT_BLKH_NUM * AE_STAT_BLKV_NUM)
>>> +/* AE stats block size need to be aligned with 2 */
>>> +#define AE_STAT_BLK_SIZE             ALIGN(AE_STAT_BLK_NUM, 2)
>>> +#define AE_BLOCK_WT_NUM              255
>>> +#define AE_BLK_WT_DATA_NUM_OF_GROUP  8
>>> +#define AWB_STAT_BLKH_NUM            32
>>> +#define AWB_STAT_BLKV_NUM            24
>>> +#define AWB_STAT_BLK_NUM             (AWB_STAT_BLKH_NUM * AWB_STAT_BLKV_NUM)
>>> +/* AWB stats block size need to be aligned with 2 */
>>> +#define AWB_STAT_BLK_SIZE            ALIGN(AWB_STAT_BLK_NUM, 2)
>>> +#define AWB_BLOCK_WT_NUM             768
>>> +#define AWB_BLK_WT_DATA_NUM_OF_GROUP 8
>>> +#define AWB_STAT_BLC20_NUM           4
>>> +#define AWB_STAT_GAIN10_NUM          4
>>> +#define BLC_OFFSET_NUM               5
>>> +#define GAMMA_LUT_GROUP_NUM          4
>>> +#define GAMMA_LUT_POINT_NUM          129
>>> +#define GAMMA_LUT_DATA_NUM_OF_GROUP  2
>>> +
>>> +/**
>>> + * struct awb_zone_stats - AWB statistics of a block
>>> + *
>>> + * AWB zone stats is aligned with 8 bytes
>>> + *
>>> + * @rg: the ratio of R / G in a zone
>>> + * @bg: the ratio of B / G in a zone
>>> + * @pixel_sum: the total number of pixels used in a zone
>>> + */
>>> +struct awb_zone_stats {
>>> +   u16 rg;
>>> +   u16 bg;
>>> +   u32 pixel_sum;
>>> +};
>>> +
>>> +/**
>>> + * struct awb_stats_info - Auto white balance statistics information.
>>> + *
>>> + * AWB statistical information of all blocks.
>>> + *
>>> + * @awb_stats: array of auto white balance statistics
>>> + */
>>> +struct awb_stats_info {
>>> +   struct awb_zone_stats awb_stats[AWB_STAT_BLK_SIZE];
>>> +};
>>> +
>>> +/**
>>> + * struct ae_zone_stats - AE statistics of a block
>>> + *
>>> + * AE zone stats is aligned with 8 bytes.
>>> + * This is a 5-bin histogram and the total sum is
>>> + * normalized to 0xffff.
>>> + * So hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4)
>>> + *
>>> + * @hist0: the global normalized pixel count for bin 0
>>> + * @hist1: the global normalized pixel count for bin 1
>>> + * @hist3: the global normalized pixel count for bin 3
>>> + * @hist4: the global normalized pixel count for bin 4
>>> + */
>>> +struct ae_zone_stats {
>>> +   u16 hist0;
>> Should you include  <linux/types.h> and use types prefixed with __
>> (__u16) ?
>>
>>> +   u16 hist1;
>>> +   u16 hist3;
>>> +   u16 hist4;
>>> +};
>>> +
>>> +/**
>>> + * struct ae_stats_info - Exposure statistics information
>>> + *
>>> + * AE statistical information consists of
>>> + * all blocks information and a 1024-bin histogram.
>>> + *
>>> + * @ae_stats: array of auto exposure block statistics
>>> + * @hist: a 1024-bin histogram for the entire image
>>> + */
>>> +struct ae_stats_info {
>>> +   struct ae_zone_stats ae_stats[AE_STAT_BLK_SIZE];
>>> +   u32 hist[AE_HISTOGRAM_SIZE];
>>> +};
>>> +
>>> +/**
>>> + * struct af_zone_stats - AF statistics of a block
>>> + *
>>> + * AF block stats is aligned with 8 bytes.
>>> + * The zonal accumulated contrast metrics are stored
>>> + * in floating point format with 16 bits mantissa and
>>> + * 5 or 6 bits exponent.
>>> + * Apart from contrast metrics we accumulate squared image and
>>> + * quartic image data over the zone.
>>> + *
>>> + * @i2_mat: the mantissa of zonal squared image pixel sum
>>> + * @i4_mat: the mantissa of zonal quartic image pixel sum
>>> + * @e4_mat: the mantissa of zonal multi-directional quartic edge sum
>>> + * @i2_exp: the exponent of zonal squared image pixel sum
>>> + * @i4_exp: the exponent of zonal quartic image pixel sum
>>> + * @e4_exp: the exponent of zonal multi-directional quartic edge sum
>>> + */
>>> +struct af_zone_stats {
>>> +   u16 i2_mat;
>>> +   u16 i4_mat;
>>> +   u16 e4_mat;
>>> +   u16 i2_exp: 5;
>>> +   u16 i4_exp: 6;
>>> +   u16 e4_exp: 5;
>>> +};
>>> +
>>> +/**
>>> + * struct af_stats_info - Auto Focus statistics information
>>> + *
>>> + * AF statistical information of each block
>>> + *
>>> + * @af_stats: array of auto focus block statistics
>>> + */
>>> +struct af_stats_info {
>>> +   struct af_zone_stats af_stats[AF_STAT_BLK_SIZE];
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_stats_info - V4L2_META_FMT_C3ISP_STATS
>>> + *
>>> + * Contains ISP statistics
>>> + *
>>> + * @awb_stats: auto white balance stats
>>> + * @ae_stats: auto exposure stats
>>> + * @af_stats: auto focus stats
>>> + */
>>> +struct c3_isp_stats_info {
>>> +   struct awb_stats_info awb_stats;
>>> +   struct ae_stats_info ae_stats;
>>> +   struct af_stats_info af_stats;
>>> +};
>>> +
>>> +/**
>>> + * enum c3_isp_param_buffer_version -  C3 ISP parameters block versioning
>>> + *
>>> + * @C3_ISP_PARAM_BUFFER_V0: First version of C3 ISP parameters block
>>> + */
>>> +enum c3_isp_param_buffer_version {
>>> +   C3_ISP_PARAM_BUFFER_V0,
>>> +};
>>> +
>>> +/**
>>> + * enum c3_isp_param_block_type - Enumeration of C3 ISP parameter blocks
>>> + *
>>> + * Each block configures a specific processing block of the C3 ISP.
>>> + * The block type allows the driver to correctly interpret
>>> + * the parameters block data.
>>> + *
>>> + * @C3_ISP_PARAM_BLOCK_WB_CHANGE: White balance change parameters
>>> + * @C3_ISP_PARAM_BLOCK_WB_LUMA: White balance Luma-based parameters
>>> + * @C3_ISP_PARAM_BLOCK_WB_TRIANGLE: White balance triangle parameters
>>> + * @C3_ISP_PARAM_BLOCK_AWB_STATS: AWB statistics parameters
>>> + * @C3_ISP_PARAM_BLOCK_AE_STATS: AE statistics parameters
>>> + * @C3_ISP_PARAM_BLOCK_AF_STATS: AF statistics parameters
>>> + * @C3_ISP_PARAM_BLOCK_PST_GAMMA: post gamma parameters
>>> + * @C3_ISP_PARAM_BLOCK_DMSC: demosaic parameters
>>> + * @C3_ISP_PARAM_BLOCK_CCM: Color correction matrix parameters
>>> + * @C3_ISP_PARAM_BLOCK_CSC: Color space conversion parameters
>>> + * @C3_ISP_PARAM_BLOCK_BLC: Black level correction parameters
>>> + * @C3_ISP_PARAM_BLOCK_SENTINEL: First non-valid block index
>>> + */
>>> +enum c3_isp_param_block_type {
>>> +   C3_ISP_PARAM_BLOCK_WB_CHANGE,
>>> +   C3_ISP_PARAM_BLOCK_WB_LUMA,
>>> +   C3_ISP_PARAM_BLOCK_WB_TRIANGLE,
>>> +   C3_ISP_PARAM_BLOCK_AWB_STATS,
>>> +   C3_ISP_PARAM_BLOCK_AE_STATS,
>>> +   C3_ISP_PARAM_BLOCK_AF_STATS,
>>> +   C3_ISP_PARAM_BLOCK_PST_GAMMA,
>>> +   C3_ISP_PARAM_BLOCK_DMSC,
>>> +   C3_ISP_PARAM_BLOCK_CCM,
>>> +   C3_ISP_PARAM_BLOCK_CSC,
>>> +   C3_ISP_PARAM_BLOCK_BLC,
>>> +   C3_ISP_PARAM_BLOCK_SENTINEL
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_param_block_header - C3 ISP parameter block header
>>> + *
>>> + * This structure represents the common part of all the ISP configuration
>>> + * blocks. Each parameters block shall embed an instance of this structure type
>>> + * as its first member, followed by the block-specific configuration data. The
>>> + * driver inspects this common header to discern the block type and its size and
>>> + * properly handle the block content by casting it to the correct block-specific
>>> + * type.
>>> + *
>>> + * @type: The parameters block type (enum c3_isp_param_block_type)
>>> + * @enabled: Block enabled/disabled flag
>>> + * @size: Size (in bytes) of parameters block
>>> + */
>>> +
>>> +struct c3_isp_param_block_header {
>>> +   enum c3_isp_param_block_type type;
>>> +   bool enabled;
>>> +   size_t size;
>>> +};
>> What is the size of this structure ? Is it aligned or does the
>> compiler inserts padding bytes ? In general, I would try to align
>> everything to 8 bytes to avoid the compiler inserting padding bytes.
>>
>> A tool that can help you identifies holes is pahole. Just write a
>> small userspace program that includes types from this header and
>> declare a variable of each type of defined in this header. Pass the
>> executable to pahole and it will show the memory layout of each
>> member.
>>
>>> +
>>> +/**
>>> + * struct wb_change_cfg - White Balance configuration
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @wb_gain: white balance gain of each color
>>> + * wb_gain[0]: Gr gain, range 0~0xfff
>>> + * wb_gain[1]: R gain, range 0~0xfff
>>> + * wb_gain[2]: B gain, range 0~0xfff
>>> + * wb_gain[3]: Gb gain, range 0~0xfff
>>> + * wb_gain[4]: Ir gain, range 0~0xfff
>>> + * @wb_limit: white balance limit of each color
>>> + * wb_limit[0]: Gr limit, 16 bits float
>>> + * wb_limit[1]: R limit, 16 bits float
>>> + * wb_limit[2]: B limit, 16 bits float
>>> + * wb_limit[3]: Gb limit, 16 bits float
>>> + * wb_limit[4]: Ir limit, 16 bits float
>>> + * @ae_gain_grbgi: Gain of each color before blending to luma
>>> + * ae_gain_grbgi[0]: Gr gain, range 0~255
>>> + * ae_gain_grbgi[1]: R gain, range 0~255
>>> + * ae_gain_grbgi[2]: B gain, range 0~255
>>> + * ae_gain_grbgi[3]: Gb gain, range 0~255
>>> + * ae_gain_grbgi[4]: Ir gain, range 0~255
>>> + * @ae_bl12_grbgi: Offset of each color before ae_gain_grbgi
>>> + * ae_bl12_grbgi[0]: Gr offset, range 0~4095
>>> + * ae_bl12_grbgi[1]: R offset, range 0~4095
>>> + * ae_bl12_grbgi[2]: B offset, range 0~4095
>>> + * ae_bl12_grbgi[3]: Gb offset, range 0~4095
>>> + * ae_bl12_grbgi[4]: Ir offset, range 0~4095
>>> + */
>>> +struct wb_change_cfg {
>>> +   struct c3_isp_param_block_header header;
>>> +   u32 wb_gain[5];
>>> +   u32 wb_limit[5];
>>> +   u32 ae_gain_grbgi[5];
>>> +   u32 ae_bl12_grbgi[5];
>>> +};
>>> +
>>> +/**
>>> + * struct wb_luma_cfg - White Balance Luma-based configuration
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @awb_stat_blc20: BLC in AWB statistic
>>> + * awb_stat_blc20[0]: Gr blc, range 0~0xfffff
>>> + * awb_stat_blc20[1]: R blc, range 0~0xfffff
>>> + * awb_stat_blc20[2]: B blc, range 0~0xfffff
>>> + * awb_stat_blc20[3]: Gb blc, range 0~0xfffff
>>> + * @awb_stat_gain10: Gain in AWB statistic
>>> + * awb_stat_gain10[0]: Gr gain, range 0~1023
>>> + * awb_stat_gain10[1]: R gain, range 0~1023
>>> + * awb_stat_gain10[2]: B gain, range 0~1023
>>> + * awb_stat_gain10[3]: Gb gain, range 0~1023
>>> + * @awb_stat_satur_low: AWB statistic under-saturation threshold
>>> + * value: range 0~65535
>>> + * @awb_stat_satur_high: AWB statistic over-saturation threshold
>>> + * value: range 0~65535
>>> + */
>>> +struct wb_luma_cfg {
>>> +   struct c3_isp_param_block_header header;
>>> +   u32 awb_stat_blc20[AWB_STAT_BLC20_NUM];
>>> +   u32 awb_stat_gain10[AWB_STAT_GAIN10_NUM];
>>> +   u32 awb_stat_satur_low;
>>> +   u32 awb_stat_satur_high;
>>> +};
>>> +
>>> +/**
>>> + * struct wb_triangle_cfg - White Balance Triangle
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @awb_stat_satur_vald: AWB statistic over saturation control
>>> + * value: 0: disable, 1: enable
>>> + * @awb_stat_rg_min: min value of r/g
>>> + * value: 0~4095
>>> + * @awb_stat_rg_max: max value of r/g
>>> + * value: 0~4095
>>> + * @awb_stat_bg_min: min value of b/g
>>> + * value: 0~4095
>>> + * @awb_stat_bg_max: max value of b/g
>>> + * value: 0~4095
>>> + * @awb_stat_rg_low: low value of r/g
>>> + * value: 0~4095
>>> + * @awb_stat_rg_high: high value of r/g
>>> + * value: 0~4095
>>> + * @awb_stat_bg_low: low value of b/g
>>> + * value: 0~4095
>>> + * @awb_stat_bg_high: high value of b/g
>>> + * value: 0~4095
>>> + */
>>> +struct wb_triangle_cfg {
>>> +   struct c3_isp_param_block_header header;
>>> +   u32 awb_stat_satur_vald;
>>> +   u32 awb_stat_rg_min;
>>> +   u32 awb_stat_rg_max;
>>> +   u32 awb_stat_bg_min;
>>> +   u32 awb_stat_bg_max;
>>> +   u32 awb_stat_rg_low;
>>> +   u32 awb_stat_rg_high;
>>> +   u32 awb_stat_bg_low;
>>> +   u32 awb_stat_bg_high;
>>> +};
>>> +
>>> +/**
>>> + * struct awb_stats_cfg - AWB statistics configuration
>>> + *
>>> + * This structure contains AWB statistics control information.
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @awb_stat_switch: the switch of AWB statistics
>>> + * value: 0~7
>>> + * @awb_stat_blk_weight: Array of weights for AWB statistics blocks
>>> + * value: 0~15
>>> + */
>>> +struct awb_stats_cfg {
>>> +   struct c3_isp_param_block_header header;
>>> +   u8 awb_stat_switch;
>>> +   u32 awb_stat_blk_weight[AWB_BLOCK_WT_NUM];
>>> +};
>>> +
>>> +/**
>>> + * struct ae_stats_cfg - AE statistics configuration
>>> + *
>>> + * This structure contains AE statistics control information.
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @ae_stat_switch: the switch of AE statistics
>>> + * value: 0~3
>>> + * @ae_stat_blk_weight: Array of weights for AE statistics blocks
>>> + * value: 0~15
>>> + */
>>> +struct ae_stats_cfg {
>>> +   struct c3_isp_param_block_header header;
>>> +   u8 ae_stat_switch;
>>> +   u32 ae_stat_blk_weight[AE_BLOCK_WT_NUM];
>>> +};
>>> +
>>> +/**
>>> + * struct af_stats_cfg - AF statistics configuration
>>> + *
>>> + * This structure contains AF statistics control information.
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @af_stat_switch: the switch of AF statistics
>>> + * value: 0~3
>>> + */
>>> +struct af_stats_cfg {
>>> +   struct c3_isp_param_block_header header;
>>> +   u8 af_stat_switch;
>>> +};
>>> +
>>> +/**
>>> + * struct pst_gamma_cfg - Post gamma configuration
>>> + *
>>> + * This structure contains post gamma parameters
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @pst_gamma_lut: LUT for P-Stitch gamma
>>> + * value: 0~65535
>>> + */
>>> +struct pst_gamma_cfg {
>>> +   struct c3_isp_param_block_header header;
>>> +   u32 pst_gamma_lut[GAMMA_LUT_GROUP_NUM][GAMMA_LUT_POINT_NUM];
>>> +};
>>> +
>>> +/**
>>> + * struct dmsc_cfg - Demosaic configuration
>>> + *
>>> + * This structure contains demosaic parameters
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + */
>>> +struct dmsc_cfg {
>>> +   struct c3_isp_param_block_header header;
>>> +};
>>> +
>>> +/**
>>> + * struct ccm_cfg - ISP CCM configuration
>>> + *
>>> + * This structure holds the parameters for configuring the CCM,
>>> + * which is used for color correction.
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @ccm_4x3matrix: A 3x4 matrix used for color correction
>>> + * value: 0~8191
>>> + */
>>> +struct ccm_cfg {
>>> +   struct c3_isp_param_block_header header;
>>> +   u32 ccm_4x3matrix[3][4];
>>> +};
>>> +
>>> +/**
>>> + * struct csc_cfg - ISP Color Space Conversion configuration
>>> + *
>>> + * This structure contains settings for color space conversion.
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @cm0_offset_inp: Input offset values for the 0-order color matrix
>>> + * value: 0~8191
>>> + * @cm0_offset_oup: Output offset values for the 0-order color matrix
>>> + * value: 0~8191
>>> + * @cm0_3x3mtrx_rs: matrix right shift for cm0
>>> + * value: 0~3
>>> + * @cm0_3x3matrix: A 3x3 matrix used for the color matrix
>>> + * value: 0~8191
>>> + */
>>> +struct csc_cfg {
>>> +   struct c3_isp_param_block_header header;
>>> +   u32 cm0_offset_inp[3];
>>> +   u32 cm0_offset_oup[3];
>>> +   u32 cm0_3x3mtrx_rs;
>>> +   u32 cm0_3x3matrix[3][3];
>>> +};
>>> +
>>> +/**
>>> + * struct blc_cfg - ISP Black Level Correction (BLC) configuration
>>> + *
>>> + * This structure holds the parameters for BLC in image processing.
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @fe_bl_ofst: Array of front-end BLC offsets for each color each channels
>>> + * fe_bl_ofst[0]: Gr blc offset, range 0~0x1fffff
>>> + * fe_bl_ofst[1]: R blc offset, range 0~0x1fffff
>>> + * fe_bl_ofst[2]: B blc offset, range 0~0x1fffff
>>> + * fe_bl_ofst[3]: Gb blc offset, range 0~0x1fffff
>>> + * fe_bl_ofst[4]: Ir blc offset, range 0~0x1fffff
>>> + * @blc_ofst: Array of LSWB BLC offsets
>>> + * blc_ofst[0]: Gr blc offset, 16 bits float
>>> + * blc_ofst[1]: R blc offset, 16 bits float
>>> + * blc_ofst[2]: B blc offset, 16 bits float
>>> + * blc_ofst[3]: Gb blc offset, 16 bits float
>>> + * blc_ofst[4]: Ir blc offset, 16 bits float
>>> + */
>>> +struct blc_cfg {
>>> +   struct c3_isp_param_block_header header;
>>> +   u32 fe_bl_ofst[BLC_OFFSET_NUM];
>>> +   u32 blc_ofst[BLC_OFFSET_NUM];
>>> +};
>>> +
>>> +/**
>>> + * define C3_ISP_PARAMS_MAX_SIZE - Maximum size of all C3 ISP Parameters
>>> + *
>>> + * Though the parameters for the C3 ISP are passed as optional blocks, the
>>> + * driver still needs to know the absolute maximum size so that it can allocate
>>> + * a buffer sized appropriately to accommodate userspace attempting to set all
>>> + * possible parameters in a single frame.
>>> + */
>>> +#define C3_ISP_PARAMS_MAX_SIZE                 \
>>> +   (sizeof(struct wb_change_cfg) +        \
>>> +   sizeof(struct wb_luma_cfg)   +         \
>>> +   sizeof(struct wb_triangle_cfg) +       \
>>> +   sizeof(struct awb_stats_cfg) +          \
>>> +   sizeof(struct ae_stats_cfg) +           \
>>> +   sizeof(struct af_stats_cfg) +           \
>>> +   sizeof(struct pst_gamma_cfg) +         \
>>> +   sizeof(struct dmsc_cfg) +              \
>>> +   sizeof(struct ccm_cfg) +               \
>>> +   sizeof(struct csc_cfg) +               \
>>> +   sizeof(struct blc_cfg))
>>> +
>>> +/**
>>> + * struct c3_isp_params_buffer - C3 ISP configuration parameters
>>> + *
>>> + * This struct contains the configuration parameters of the C3 ISP
>>> + * algorithms, serialized by userspace into an opaque data buffer. Each
>>> + * configuration parameter block is represented by a block-specific structure
>>> + * which contains a :c:type:`c3_isp_param_block_header` entry as first
>>> + * member. Userspace populates the @data buffer with configuration parameters
>>> + * for the blocks that it intends to configure. As a consequence, the data
>>> + * buffer effective size changes according to the number of ISP blocks that
>>> + * userspace intends to configure.
>>> + *
>>> + * The parameters buffer is versioned by the @version field to allow modifying
>>> + * and extending its definition. Userspace should populate the @version field to
>>> + * inform the driver about the version it intends to use. The driver will parse
>>> + * and handle the @data buffer according to the data layout specific to the
>>> + * indicated revision and return an error if the desired revision is not
>>> + * supported.
>>> + *
>>> + * For each ISP block that userspace wants to configure, a block-specific
>>> + * structure is appended to the @data buffer, one after the other without gaps
>>> + * in between nor overlaps. Userspace shall populate the @total_size field with
>>> + * the effective size, in bytes, of the @data buffer.
>>> + *
>>> + * The expected memory layout of the parameters buffer is::
>>> + *
>>> + * +-------------------- struct c3_isp_params_buffer ------------------+
>>> + * | version = C3_ISP_PARAM_BUFFER_V0;                                   |
>>> + * | total_size = sizeof(sizeof(struct wb_change_cfg))                   |
>>> + * |              sizeof(sizeof(struct wb_luma_cfg));                    |
>>> + * | +------------------------- data  ---------------------------------+ |
>>> + * | | +------------------ struct wb_change_cfg) --------------------+ | |
>>> + * | | | +---------  struct c3_isp_param_block_header header  -----+ | | |
>>> + * | | | | type = C3_ISP_PARAM_BLOCK_WB_CHANGE;                    | | | |
>>> + * | | | | enabled = true;                                            | | | |
>>> + * | | | | size =                                                  | | | |
>>> + * | | | |    sizeof(struct c3_isp_param_block_header header);     | | | |
>>> + * | | | +---------------------------------------------------------+ | | |
>>> + * | | | wb_gain[5] = ...;                                           | | |
>>> + * | | | wb_limit[5] = ...;                                          | | |
>>> + * | | | ae_gain_grbgi[5] = ...;                                     | | |
>>> + * | | | ae_bl12_grbgi[5] = ...;                                     | | |
>>> + * | | +------------------ struct wb_luma_cfg -----------------------+ | |
>>> + * | | | +---------- struct c3_isp_param_block_header header ------+ | | |
>>> + * | | | | type = C3_ISP_PARAM_BLOCK_WB_LUMA;                      | | | |
>>> + * | | | | enabled = true;                                            | | | |
>>> + * | | | | size = sizeof(struct wb_luma_cfg);                      | | | |
>>> + * | | | +---------------------------------------------------------+ | | |
>>> + * | | | awb_stat_blc20[4] = ...;                                    | | |
>>> + * | | | awb_stat_gain10[4] = ...;                                   | | |
>>> + * | | | awb_stat_satur_low = ...;                                   | | |
>>> + * | | | awb_stat_satur_high = ...;                                  | | |
>>> + * | | +-------------------------------------------------------------+ | |
>>> + * | +-----------------------------------------------------------------+ |
>>> + * +---------------------------------------------------------------------+
>>> + *
>>> + * @version: The C3 ISP parameters buffer version
>>> + * @total_size: The C3 ISP configuration data effective size,
>>> + *         excluding this header
>>> + * @data: The C3 ISP configuration blocks data
>>> + */
>>> +struct c3_isp_params_buffer {
>>> +   enum c3_isp_param_buffer_version version;
>>> +   size_t total_size;
>>> +   u8 data[C3_ISP_PARAMS_MAX_SIZE];
>>> +};
>>> +
>>> +#endif
>>>
>>> --
>>> 2.46.1
>>>
>>>
>>>

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

* Re: [PATCH v3 7/9] media: platform: Add c3 ISP driver
  2024-11-08 12:34     ` Keke Li
@ 2024-11-08 14:04       ` Jacopo Mondi
  2024-11-11  3:00         ` Keke Li
  0 siblings, 1 reply; 37+ messages in thread
From: Jacopo Mondi @ 2024-11-08 14:04 UTC (permalink / raw)
  To: Keke Li
  Cc: Jacopo Mondi, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-media, devicetree,
	linux-kernel, kieran.bingham, laurent.pinchart, dan.scally

Hi Keke

On Fri, Nov 08, 2024 at 08:34:41PM +0800, Keke Li wrote:
> Hi Jacopo
>
> Thanks very much for your reply.
>
> On 2024/11/8 00:03, Jacopo Mondi wrote:
> > [ EXTERNAL EMAIL ]
> >
> > Hi Keke
> >
> >     a first pass of review without going into details about the
> > ISP parameters and stats but mostly on architecture.
> >
> > On Wed, Sep 18, 2024 at 02:07:18PM +0800, Keke Li via B4 Relay wrote:
> > > From: Keke Li <keke.li@amlogic.com>
> > >
> > > The C3 ISP supports multi-camera and muti-exposure
> > > high dynamic range (HDR). It brings together some
> > > advanced imaging technologies to provide good image quality.
> > > This driver mainly responsible for driving ISP pipeline
> > > to process raw image.
> > >
> > > Signed-off-by: Keke Li <keke.li@amlogic.com>
> > > ---
> > >   drivers/media/platform/amlogic/Kconfig             |   1 +
> > >   drivers/media/platform/amlogic/Makefile            |   1 +
> > >   drivers/media/platform/amlogic/c3-isp/Kconfig      |  17 +
> > >   drivers/media/platform/amlogic/c3-isp/Makefile     |  10 +
> > >   .../media/platform/amlogic/c3-isp/c3-isp-capture.c | 759 ++++++++++++++++++
> > >   .../media/platform/amlogic/c3-isp/c3-isp-common.h  | 327 ++++++++
> > >   .../media/platform/amlogic/c3-isp/c3-isp-core.c    | 675 ++++++++++++++++
> > >   drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c | 486 ++++++++++++
> > >   .../media/platform/amlogic/c3-isp/c3-isp-params.c  | 857 +++++++++++++++++++++
> > >   .../media/platform/amlogic/c3-isp/c3-isp-regs.h    | 683 ++++++++++++++++
> > >   .../media/platform/amlogic/c3-isp/c3-isp-resizer.c | 768 ++++++++++++++++++
> > >   .../media/platform/amlogic/c3-isp/c3-isp-stats.c   | 488 ++++++++++++
> > >   .../amlogic/c3-isp/include/uapi/c3-isp-config.h    | 537 +++++++++++++
> > This really should be in include/uapi/linux/media/amlogic
> > so that it is made available to userspace.
>
>
> OK. will move c3-isp-config.h to include/uapi/linux/media/amlogic
>
> > >   13 files changed, 5609 insertions(+)
> > >
> > > diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
> > > index df09717b28d0..ebda6b7edc2d 100644
> > > --- a/drivers/media/platform/amlogic/Kconfig
> > > +++ b/drivers/media/platform/amlogic/Kconfig
> > > @@ -2,6 +2,7 @@
> > >
> > >   comment "Amlogic media platform drivers"
> > >
> > > +source "drivers/media/platform/amlogic/c3-isp/Kconfig"
> > >   source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
> > >   source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
> > I start wondering if this shouldn't rather be organized in an
> > drivers/media/platform/amlogic/c3/ folder with an
> > drivers/media/platform/amlogic/c3/isp/ subfolder
> >
> > Do you know if any of the csi-2-rx, adapter or ISP can
> > be used in other SoCs ?
> >
>
> Yes, csi-2-rx, adapter and ISP will be used in T7 SoC in the future.
>
> Is there need to create "drivers/media/platform/amlogic/c3/" in this case?
>
> How to handle this case?
>

I see... well, you have the c3- prefix already in each of these
drivers, so having a c3/ folder isn't a problem even if all three
components will be used on another SoC.

It would be different if, in example, the CSI-2_RX and Adapt are used
in C3 only, but the ISP is used in multiple modules.

At this point is up to you. If you keep the c3- prefix in all modules
I would rather move them to a c3/ subfolder to avoid conflicts when
you will add a different generation to the media/platform/amlogic
folder.

Up to you ;)

> > >   source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
> > > diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
> > > index b370154b090c..d0d9363d4d8d 100644
> > > --- a/drivers/media/platform/amlogic/Makefile
> > > +++ b/drivers/media/platform/amlogic/Makefile
> > > @@ -1,5 +1,6 @@
> > >   # SPDX-License-Identifier: GPL-2.0-only
> > >
> > > +obj-y += c3-isp/
> > >   obj-y += c3-mipi-adapter/
> > >   obj-y += c3-mipi-csi2/
> > >   obj-y += meson-ge2d/
> > > diff --git a/drivers/media/platform/amlogic/c3-isp/Kconfig b/drivers/media/platform/amlogic/c3-isp/Kconfig
> > > new file mode 100644
> > > index 000000000000..e317c1e81750
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-isp/Kconfig
> > > @@ -0,0 +1,17 @@
> > > +# SPDX-License-Identifier: GPL-2.0-only
> > > +
> > > +config VIDEO_C3_ISP
> > > +     tristate "Amlogic C3 Image Signal Processor (ISP) driver"
> > > +     depends on ARCH_MESON || COMPILE_TEST
> > > +     depends on VIDEO_DEV
> > > +     depends on OF
> > > +     select MEDIA_CONTROLLER
> > > +     select V4L2_FWNODE
> > > +     select VIDEO_V4L2_SUBDEV_API
> > > +     select VIDEOBUF2_DMA_CONTIG
> > > +     help
> > > +       Video4Linux2 driver for Amlogic C3 ISP pipeline.
> > > +       C3 ISP pipeline mainly for processing raw image
> >            The C3 ISP is used for processing raw images
> > > +       and output result to memory.
> > Or something similar, but "C3 ISP pipeline mainly for processing"
> > doesn't sound right to be
>
>
> OK, I see
>
> > > +
> > > +       To compile this driver as a module choose m here.
> > > diff --git a/drivers/media/platform/amlogic/c3-isp/Makefile b/drivers/media/platform/amlogic/c3-isp/Makefile
> > > new file mode 100644
> > > index 000000000000..b1b064170b57
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-isp/Makefile
> > > @@ -0,0 +1,10 @@
> > > +# SPDX-License-Identifier: GPL-2.0-only
> > > +
> > > +c3-isp-objs := c3-isp-dev.o \
> > > +            c3-isp-params.o \
> > > +            c3-isp-stats.o \
> > > +            c3-isp-capture.o \
> > > +            c3-isp-core.o \
> > > +            c3-isp-resizer.o
> > > +
> > > +obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
> > > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
> > > new file mode 100644
> > > index 000000000000..ee9a7a17a203
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
> > > @@ -0,0 +1,759 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#include <linux/cleanup.h>
> > > +#include <linux/pm_runtime.h>
> > > +
> > > +#include <media/v4l2-ctrls.h>
> > > +#include <media/v4l2-event.h>
> > > +#include <media/v4l2-ioctl.h>
> > > +#include <media/v4l2-mc.h>
> > > +#include <media/videobuf2-dma-contig.h>
> > > +
> > > +#include "c3-isp-common.h"
> > > +#include "c3-isp-regs.h"
> > > +
> > > +static const struct c3_isp_capture_format cap_formats[] = {
> > > +     {
> > > +             .mbus_code = MEDIA_BUS_FMT_Y8_1X8,
> > > +             .fourcc = V4L2_PIX_FMT_GREY,
> > > +             .depth = 8,
> > > +     },
> > > +     {
> > > +             .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
> > Does the 5X8 version represents the format on the internal bus between
> > the resizers and the capture device ?
> >
> > How does format propagation work from the ISP to the resizers and the
> > capture devices ? I mean, is there an internal bus where the number of
> > samples (5X8 vs 2X8) changes depending on the output format ?
> >
>
> There is no internal bus between the resizer and the capture device.
>

I presume there is at least a data path, maybe as internal memory
buffers, between the different components of the ISP though.

> The output format should be only configured in capture device.
>

Ok, what I'm after here is finding out the reason why you have
different mbus codes associated with different output formats. Same
thing as per the below question about the mbus codes used between the
ISP and the resizers.

The mbus code should describe the format as it actually is in the data
path between the different ISP components. In example, if your
demosaicing block outputs data in a specific format with a specific
bit-depth (in example RGB data with a 30 bits wide bus) you should use
MEDIA_BUS_FMT_RGB101010_1X30.

However, if this information is not available, or not relevant as it
doesn't influence the behaviour of any of the ISP blocks, I think
using MEDIA_BUS_FMT_FIXED should be fine.

Otherwise, if there are reasons to use MEDIA_BUS_FMT_YUYV8_1_5X8 over
MEDIA_BUS_FMT_YUYV8_2X8 it's fine, but please explain them :)

Thanks
  j

> > > +             .fourcc = V4L2_PIX_FMT_NV12,
> > > +             .depth = 12,
> > > +     }, {
> > > +             .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
> > > +             .fourcc = V4L2_PIX_FMT_NV21,
> > > +             .depth = 12,
> > > +     }, {
> > > +             .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> > > +             .fourcc = V4L2_PIX_FMT_NV16,
> > > +             .depth = 16,
> > > +     }, {
> > > +             .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> > > +             .fourcc = V4L2_PIX_FMT_NV61,
> > > +             .depth = 16,
> > > +     },
> > > +};
> > > +
> > > +/* Hardware configuration */
> > > +
> > > +/* Set the address of wrmifx3(write memory interface) */
> > > +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
> > > +{
> > > +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> > > +     struct c3_isp_vb2_buffer *buff = cap->buff;
> > > +     u32 offset;
> > > +
> > > +     c3_isp_write(cap->isp,
> > > +                  C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
> > > +                  WRMIFX3_CH0_BADDR(buff->paddr));
> > > +
> > > +     if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
> > > +         pix->pixelformat == V4L2_PIX_FMT_NV21 ||
> > > +         pix->pixelformat == V4L2_PIX_FMT_NV16 ||
> > > +         pix->pixelformat == V4L2_PIX_FMT_NV61) {
> > > +             offset = pix->width * pix->height;
> > > +             c3_isp_write(cap->isp,
> > > +                          C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
> > > +                          WRMIFX3_CH1_BADDR(buff->paddr + offset));
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_cap_output_size(struct c3_isp_capture *cap)
> > > +{
> > > +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
> > > +                        DISP_OUT_VSIZE_MASK, pix->height);
> > > +     c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
> > > +                        DISP_OUT_HSIZE_MASK, pix->width << DISP_OUT_HSIZE_SHIFT);
> > > +}
> > > +
> > > +static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
> > > +{
> > > +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> > > +     u32 stride;
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
> > > +
> > > +     /* Grey has 1 plane*/
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        WRMIFX3_FMT_MTX_PLANE_MASK,
> > > +                        WRMIFX3_FMT_MTX_PLANE_X1 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
> > > +
> > > +     /* Set Y only as output format */
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        WRMIFX3_FMT_MODE_OUT_MASK,
> > > +                        WRMIFX3_FMT_MODE_OUT_Y_ONLY << WRMIFX3_FMT_MODE_OUT_SHIFT);
> > > +
> > > +     /* The unit of stride is 128 bits */
> > > +     stride = DIV_ROUND_UP(fmt->width * 8, 128);
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> > > +                        WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> > > +                        WRMIFX3_CH0_PIX_BITS_MODE_MASK,
> > > +                        WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
> > > +}
> > > +
> > > +static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, u32 swap_uv)
> > > +{
> > > +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> > > +     u32 stride;
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        WRMIFX3_FMT_MTX_UV_SWAP_MASK,
> > > +                        swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
> > > +
> > > +     /* NV12 or NV21 has 2 planes*/
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        WRMIFX3_FMT_MTX_PLANE_MASK,
> > > +                        WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
> > > +
> > > +     /* Set YUV420 as output format */
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        WRMIFX3_FMT_MODE_OUT_MASK,
> > > +                        WRMIFX3_FMT_MODE_OUT_YUV420 << WRMIFX3_FMT_MODE_OUT_SHIFT);
> > > +
> > > +     /* The unit of stride is 128 bits */
> > > +     stride = DIV_ROUND_UP(fmt->width * 8, 128);
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> > > +                        WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
> > > +                        WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> > > +                        WRMIFX3_CH0_PIX_BITS_MODE_MASK,
> > > +                        WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
> > > +                        WRMIFX3_CH1_PIX_BITS_MODE_MASK,
> > > +                        WRMIFX3_CH1_PIX_BITS_16BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
> > > +}
> > > +
> > > +static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, u32 swap_uv)
> > > +{
> > > +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> > > +     u32 stride;
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_16BIT);
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        WRMIFX3_FMT_MTX_UV_SWAP_MASK,
> > > +                        swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
> > > +
> > > +     /* NV16 or NV61 has 2 planes*/
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        WRMIFX3_FMT_MTX_PLANE_MASK,
> > > +                        WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
> > > +
> > > +     /* Set YUV422 as output format */
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
> > > +                        WRMIFX3_FMT_MODE_OUT_MASK,
> > > +                        WRMIFX3_FMT_MODE_OUT_YUV422 << WRMIFX3_FMT_MODE_OUT_SHIFT);
> > > +
> > > +     /* The unit of stride is 128 bits */
> > > +     stride = DIV_ROUND_UP(fmt->width * 16, 128);
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
> > > +                        WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
> > > +                        WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
> > > +                        WRMIFX3_CH0_PIX_BITS_MODE_MASK,
> > > +                        WRMIFX3_CH0_PIX_BITS_16BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
> > > +                        WRMIFX3_CH1_PIX_BITS_MODE_MASK,
> > > +                        WRMIFX3_CH1_PIX_BITS_32BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
> > > +}
> > > +
> > > +static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
> > > +{
> > > +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
> > > +
> > > +     if (fmt->pixelformat == V4L2_PIX_FMT_GREY) {
> > > +             c3_isp_cap_wrmifx3_grey(cap);
> > > +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV12) {
> > > +             c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
> > > +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV21) {
> > > +             c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
> > > +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV16) {
> > > +             c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
> > > +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV61) {
> > > +             c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
> > > +     } else {
> > > +             dev_err(cap->isp->dev, "Invalid pixel format: 0x%x\n", fmt->pixelformat);
> > > +             return;
> > > +     }
> > > +
> > > +     c3_isp_write(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id),
> > > +                  WRMIFX3_FMT_HSIZE(fmt->width) | WRMIFX3_FMT_VSIZE(fmt->height));
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
> > > +                        WRMIFX3_WIN_LUMA_HEND_MASK,
> > > +                        WRMIFX3_WIN_LUMA_HEND(fmt->width) << WRMIFX3_WIN_LUMA_HEND_SHIFT);
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
> > > +                        WRMIFX3_WIN_LUMA_VEND_MASK,
> > > +                        WRMIFX3_WIN_LUMA_VEND(fmt->height) << WRMIFX3_WIN_LUMA_VEND_SHIFT);
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_HSIZE, cap->id),
> > > +                        WRMIFX3_CROP_HEND_MASK,
> > > +                        WRMIFX3_CROP_HEND(fmt->width) << WRMIFX3_CROP_HEND_SHIFT);
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_VSIZE, cap->id),
> > > +                        WRMIFX3_CROP_VEND_MASK,
> > > +                        WRMIFX3_CROP_VEND(fmt->height) << WRMIFX3_CROP_VEND_SHIFT);
> > > +
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
> > > +                        WRMIFX3_WIN_CHROM_HEND_MASK,
> > > +                        WRMIFX3_WIN_CHROM_HEND(fmt->width) << WRMIFX3_WIN_CHROM_HEND_SHIFT);
> > > +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
> > > +                        WRMIFX3_WIN_CHROM_VEND_MASK,
> > > +                        WRMIFX3_WIN_CHROM_VEND(fmt->height) << WRMIFX3_WIN_CHROM_VEND_SHIFT);
> > > +}
> > > +
> > > +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)
> > This function is called from two locations, one with the buff_lock
> > held, the other one without, if I'm not mistaken.
> >
>
> Will add the buff_lock in c3_isp_cap_start().
>
> > > +{
> > > +     cap->buff = list_first_entry_or_null(&cap->pending,
> > > +                                          struct c3_isp_vb2_buffer, list);
> > > +     if (cap->buff) {
> > > +             c3_isp_cap_wrmifx3_buff(cap);
> > > +             list_del(&cap->buff->list);
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_cap_start(struct c3_isp_capture *cap)
> > > +{
> > > +     c3_isp_cap_cfg_buff(cap);
> > > +
> > > +     c3_isp_cap_output_size(cap);
> > > +     c3_isp_cap_wrmifx3_size(cap);
> > > +
> > > +     c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN,
> > > +                        TOP_WRMIF_EN(cap->id), TOP_WRMIF_EN(cap->id));
> > > +
> > > +     cap->is_streaming = true;
> > > +}
> > > +
> > > +static void c3_isp_cap_stop(struct c3_isp_capture *cap)
> > > +{
> > > +     cap->is_streaming = false;
> > > +
> > > +     c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, TOP_WRMIF_EN(cap->id), 0);
> > > +}
> > > +
> > > +static int c3_isp_cap_done(struct c3_isp_capture *cap)
> > > +{
> > > +     struct c3_isp_vb2_buffer *buff = cap->buff;
> > > +     unsigned long flags;
> > > +
> > > +     if (!cap->is_streaming)
> > > +             return -EINVAL;
> > Is this an error condition or 0 should be returnes ?
> >
>
> If  return 0,  there is no need to check the return value of
> c3_isp_cap_done().
>
> So will modify this function from "int c3_isp_cap_done()" to
>
> "void c3_isp_cap_done()".
>
> > > +
> > > +     spin_lock_irqsave(&cap->buff_lock, flags);
> > > +
> > > +     if (buff) {
> > > +             buff->vb.sequence = cap->isp->frm_sequence;
> > > +             buff->vb.vb2_buf.timestamp = ktime_get();
> > > +             buff->vb.field = V4L2_FIELD_NONE;
> > > +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
> > > +     }
> > > +
> > > +     c3_isp_cap_cfg_buff(cap);
> > > +
> > > +     spin_unlock_irqrestore(&cap->buff_lock, flags);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +/* V4L2 video operations */
> > > +
> > > +static const struct c3_isp_capture_format
> > > +*c3_cap_find_fmt(u32 fourcc)
> > > +{
> > > +     unsigned int i;
> > > +
> > > +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
> > > +             if (cap_formats[i].fourcc == fourcc)
> > > +                     return &cap_formats[i];
> > > +     }
> > > +
> > > +     return NULL;
> > > +}
> > > +
> > > +static void c3_cap_try_fmt(struct c3_isp_capture *cap,
> > > +                        struct v4l2_pix_format *pix)
> > > +{
> > > +     const struct c3_isp_capture_format *fmt;
> > > +
> > > +     fmt = c3_cap_find_fmt(pix->pixelformat);
> > > +     if (!fmt)
> > > +             fmt = &cap_formats[0];
> > > +
> > > +     pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> > > +     pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> > > +     pix->pixelformat = fmt->fourcc;
> > > +     pix->field = V4L2_FIELD_NONE;
> > > +     pix->colorspace = V4L2_COLORSPACE_SRGB;
> > > +     pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
> > > +     pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> > > +
> > > +     /* ISP hardware requires 16 bytes alignment */
> > > +     pix->bytesperline = ALIGN(pix->width, 16);
> > > +     pix->sizeimage = pix->bytesperline * pix->height * fmt->depth / 8;
> > > +}
> > > +
> > > +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
> > > +                                   enum vb2_buffer_state state)
> > > +{
> > > +     unsigned long flags;
> > > +     struct c3_isp_vb2_buffer *buff;
> > > +
> > > +     spin_lock_irqsave(&cap->buff_lock, flags);
> > > +
> > > +     if (cap->buff) {
> > > +             vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
> > > +             cap->buff = NULL;
> > > +     }
> > > +
> > > +     while (!list_empty(&cap->pending)) {
> > > +             buff = list_first_entry(&cap->pending,
> > > +                                     struct c3_isp_vb2_buffer, list);
> > > +             list_del(&buff->list);
> > > +             vb2_buffer_done(&buff->vb.vb2_buf, state);
> > > +     }
> > > +
> > > +     spin_unlock_irqrestore(&cap->buff_lock, flags);
> > > +}
> > > +
> > > +static int c3_isp_cap_querycap(struct file *file, void *fh,
> > > +                            struct v4l2_capability *cap)
> > > +{
> > > +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
> > > +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_cap_enum_fmt(struct file *file, void *fh,
> > > +                            struct v4l2_fmtdesc *f)
> > > +{
> > > +     const struct c3_isp_capture_format *fmt;
> > > +     unsigned int index = 0;
> > > +     unsigned int i;
> > > +
> > > +     if (!f->mbus_code) {
> > > +             if (f->index >= ARRAY_SIZE(cap_formats))
> > > +                     return -EINVAL;
> > > +
> > > +             fmt = &cap_formats[f->index];
> > > +             f->pixelformat = fmt->fourcc;
> > > +             return 0;
> > > +     }
> > > +
> > > +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
> > > +             fmt = &cap_formats[i];
> > > +             if (f->mbus_code != fmt->mbus_code)
> > > +                     continue;
> > > +
> > > +             if (index++ == f->index) {
> > > +                     f->pixelformat = cap_formats[i].fourcc;
> > > +                     return 0;
> > > +             }
> > > +     }
> > > +
> > > +     return -EINVAL;
> > > +}
> > > +
> > > +static int c3_isp_cap_g_fmt(struct file *file, void *fh,
> > > +                         struct v4l2_format *f)
> > > +{
> > > +     struct c3_isp_capture *cap = video_drvdata(file);
> > > +
> > > +     f->fmt.pix = cap->vfmt.fmt.pix;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_cap_s_fmt(struct file *file, void *fh,
> > > +                         struct v4l2_format *f)
> > > +{
> > > +     struct c3_isp_capture *cap = video_drvdata(file);
> > > +
> > > +     c3_cap_try_fmt(cap, &f->fmt.pix);
> > > +     cap->vfmt = *f;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_cap_try_fmt(struct file *file, void *fh,
> > > +                           struct v4l2_format *f)
> > > +{
> > > +     struct c3_isp_capture *cap = video_drvdata(file);
> > > +
> > > +     c3_cap_try_fmt(cap, &f->fmt.pix);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
> > > +                                struct v4l2_frmsizeenum *fsize)
> > > +{
> > > +     const struct c3_isp_capture_format *fmt;
> > > +
> > > +     if (fsize->index)
> > > +             return -EINVAL;
> > > +
> > > +     fmt = c3_cap_find_fmt(fsize->pixel_format);
> > > +     if (!fmt)
> > > +             return -EINVAL;
> > > +
> > > +     fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> > > +     fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
> > > +     fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
> > > +     fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
> > > +     fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
> > > +     fsize->stepwise.step_width = 2;
> > > +     fsize->stepwise.step_height = 2;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
> > > +     .vidioc_querycap                = c3_isp_cap_querycap,
> > > +     .vidioc_enum_fmt_vid_cap        = c3_isp_cap_enum_fmt,
> > > +     .vidioc_g_fmt_vid_cap           = c3_isp_cap_g_fmt,
> > > +     .vidioc_s_fmt_vid_cap           = c3_isp_cap_s_fmt,
> > > +     .vidioc_try_fmt_vid_cap         = c3_isp_cap_try_fmt,
> > > +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
> > > +     .vidioc_querybuf                = vb2_ioctl_querybuf,
> > > +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
> > > +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
> > > +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
> > > +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
> > > +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
> > > +     .vidioc_streamon                = vb2_ioctl_streamon,
> > > +     .vidioc_streamoff               = vb2_ioctl_streamoff,
> > > +     .vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
> > > +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
> > > +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
> > > +};
> > > +
> > > +static const struct v4l2_file_operations isp_cap_v4l2_fops = {
> > > +     .open = v4l2_fh_open,
> > > +     .release = vb2_fop_release,
> > > +     .poll = vb2_fop_poll,
> > > +     .unlocked_ioctl = video_ioctl2,
> > > +     .mmap = vb2_fop_mmap,
> > > +};
> > > +
> > > +static int c3_isp_cap_link_validate(struct media_link *link)
> > > +{
> > > +     struct video_device *vdev =
> > > +             media_entity_to_video_device(link->sink->entity);
> > > +     struct v4l2_subdev *sd =
> > > +             media_entity_to_v4l2_subdev(link->source->entity);
> > > +     struct c3_isp_capture *cap = video_get_drvdata(vdev);
> > > +     struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
> > > +     struct v4l2_subdev_format src_fmt = {
> > > +             .which = V4L2_SUBDEV_FORMAT_ACTIVE,
> > > +             .pad = link->source->index,
> > > +     };
> > > +     const struct c3_isp_capture_format *cap_fmt =
> > > +                             c3_cap_find_fmt(pix_fmt->pixelformat);
> > > +     int ret;
> > > +
> > > +     ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     if (src_fmt.format.width != pix_fmt->width ||
> > > +         src_fmt.format.height != pix_fmt->height ||
> > > +         src_fmt.format.code != cap_fmt->mbus_code) {
> > > +             dev_err(cap->isp->dev,
> > > +                     "link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n",
> > > +                     link->source->entity->name, link->source->index,
> > > +                     link->sink->entity->name, link->sink->index,
> > > +                     src_fmt.format.code, src_fmt.format.width,
> > > +                     src_fmt.format.height, cap_fmt->mbus_code,
> > > +                     pix_fmt->width, pix_fmt->height);
> > > +
> > > +             return -EPIPE;
> > > +     };
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static const struct media_entity_operations isp_cap_entity_ops = {
> > > +     .link_validate = c3_isp_cap_link_validate,
> > > +};
> > > +
> > > +static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
> > > +                               unsigned int *num_buffers,
> > > +                               unsigned int *num_planes,
> > > +                               unsigned int sizes[],
> > > +                               struct device *alloc_devs[])
> > > +{
> > > +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> > > +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
> > > +
> > > +     if (*num_planes) {
> > > +             if (*num_planes != 1)
> > > +                     return -EINVAL;
> > > +
> > > +             if (sizes[0] < pix->sizeimage)
> > > +                     return -EINVAL;
> > > +     } else {
> > > +             *num_planes = 1;
> > > +             sizes[0] = pix->sizeimage;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
> > > +{
> > > +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > > +     struct c3_isp_vb2_buffer *buf =
> > > +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> > > +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> > > +     unsigned long flags;
> > > +
> > > +     spin_lock_irqsave(&cap->buff_lock, flags);
> > > +
> > > +     list_add_tail(&buf->list, &cap->pending);
> > > +
> > > +     spin_unlock_irqrestore(&cap->buff_lock, flags);
> > > +}
> > > +
> > > +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
> > > +{
> > > +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> > > +     unsigned int size = cap->vfmt.fmt.pix.sizeimage;
> > > +
> > > +     if (vb2_plane_size(vb, 0) < size) {
> > > +             dev_err(cap->isp->dev,
> > > +                     "User buffer too small (%ld < %u)\n",
> > > +                     vb2_plane_size(vb, 0), size);
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     vb2_set_plane_payload(vb, 0, size);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
> > > +{
> > > +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > > +     struct c3_isp_vb2_buffer *buf =
> > > +             container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> > > +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
> > > +
> > > +     buf->vaddr = vb2_plane_vaddr(vb, 0);
> > > +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
> > > +
> > > +     memset(buf->vaddr, 0, cap->vfmt.fmt.pix.sizeimage);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
> > > +                                   unsigned int count)
> > > +{
> > > +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> > > +     int ret;
> > > +
> > > +     guard(mutex)(&cap->isp->lock);
> > > +
> > > +     ret = pm_runtime_resume_and_get(cap->isp->dev);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
> > > +     if (ret) {
> > > +             dev_err(cap->isp->dev,
> > > +                     "Failed to start cap%u pipeline: %d\n", cap->id, ret);
> > > +             goto err_pm_put;
> > > +     }
> > > +
> > > +     if (c3_isp_pipeline_ready(cap->isp)) {
> > > +             ret = v4l2_subdev_enable_streams(&cap->isp->core.sd,
> > > +                                              C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > > +                                              BIT(0));
> > > +             if (ret)
> > > +                     goto err_pipeline_stop;
> > > +     }
> > > +
> > > +     c3_isp_rsz_start(cap->rsz);
> > > +     c3_isp_cap_start(cap);
> > > +
> > > +     return 0;
> > > +
> > > +err_pipeline_stop:
> > > +     video_device_pipeline_stop(&cap->vdev);
> > > +err_pm_put:
> > > +     pm_runtime_put(cap->isp->dev);
> > > +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
> > > +{
> > > +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
> > > +
> > > +     guard(mutex)(&cap->isp->lock);
> > > +
> > > +     c3_isp_cap_stop(cap);
> > > +     c3_isp_rsz_stop(cap->rsz);
> > > +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
> > > +
> > > +     if (cap->isp->pipe.start_count == 1)
> > > +             v4l2_subdev_disable_streams(&cap->isp->core.sd,
> > > +                                         C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > > +                                         BIT(0));
> > > +
> > > +     video_device_pipeline_stop(&cap->vdev);
> > > +     pm_runtime_put(cap->isp->dev);
> > > +}
> > > +
> > > +static const struct vb2_ops isp_video_vb2_ops = {
> > > +     .queue_setup = c3_isp_vb2_queue_setup,
> > > +     .buf_queue = c3_isp_vb2_buf_queue,
> > > +     .buf_prepare = c3_isp_vb2_buf_prepare,
> > > +     .buf_init = c3_isp_vb2_buf_init,
> > > +     .wait_prepare = vb2_ops_wait_prepare,
> > > +     .wait_finish = vb2_ops_wait_finish,
> > > +     .start_streaming = c3_isp_vb2_start_streaming,
> > > +     .stop_streaming = c3_isp_vb2_stop_streaming,
> > > +};
> > > +
> > > +static int c3_isp_register_capture(struct c3_isp_capture *cap)
> > > +{
> > > +     struct video_device *vdev = &cap->vdev;
> > > +     struct vb2_queue *vb2_q = &cap->vb2_q;
> > > +     int ret;
> > > +
> > > +     snprintf(vdev->name, sizeof(vdev->name), "isp-cap%u", cap->id);
> > > +     vdev->fops = &isp_cap_v4l2_fops;
> > > +     vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
> > > +     vdev->v4l2_dev = &cap->isp->v4l2_dev;
> > > +     vdev->entity.ops = &isp_cap_entity_ops;
> > > +     vdev->lock = &cap->lock;
> > > +     vdev->minor = -1;
> > > +     vdev->queue = vb2_q;
> > > +     vdev->release = video_device_release_empty;
> > > +     vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> > > +     vdev->vfl_dir = VFL_DIR_RX;
> > > +     video_set_drvdata(vdev, cap);
> > > +
> > > +     vb2_q->drv_priv = cap;
> > > +     vb2_q->mem_ops = &vb2_dma_contig_memops;
> > > +     vb2_q->ops = &isp_video_vb2_ops;
> > > +     vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> > > +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
> > > +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> > > +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
> > > +     vb2_q->dev = cap->isp->dev;
> > > +     vb2_q->lock = &cap->lock;
> > > +     vb2_q->min_queued_buffers = 2;
> > I'm not sure what your plans regarding libcamera are, but be aware
> > we're going to get stricter about this value. Ideally, we aim to have
> > ISP driver set this value to 0 so that the ISP operates (produce
> > statistics) even if no buffer is queued to the capture devices. The
> > reason is that we want algorithms to run even if there are no capture
> > buffers provided.
> >
> > In order to allow the driver to operate with no buffers, you could
> > probably allocate a scratch buffer in the driver and use it whenever
> > you receive a frame completed IRQ and have no buffers available in the
> > cap->provided queue.
> >
> > See rkisp1_dummy_buf_create() in the RkISP1 driver and how dummy_buf
> > is used there.
> >
>
> OK,  will refer rkisp1 driver  and add dummy buffer.
>
> > > +
> > > +     ret = vb2_queue_init(vb2_q);
> > > +     if (ret < 0)
> > > +             goto err_destroy;
> > > +
> > > +     cap->pad.flags = MEDIA_PAD_FL_SINK;
> > > +     ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
> > > +     if (ret < 0)
> > > +             goto err_queue_release;
> > > +
> > > +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> > > +     if (ret < 0) {
> > > +             dev_err(cap->isp->dev,
> > > +                     "Failed to register %s: %d\n", vdev->name, ret);
> > > +             goto err_entity_cleanup;
> > > +     }
> > > +
> > > +     return 0;
> > > +
> > > +err_entity_cleanup:
> > > +     media_entity_cleanup(&vdev->entity);
> > > +err_queue_release:
> > > +     vb2_queue_release(vb2_q);
> > > +err_destroy:
> > > +     mutex_destroy(&cap->lock);
> > > +     return ret;
> > > +}
> > > +
> > > +int c3_isp_captures_register(struct c3_isp_device *isp)
> > > +{
> > > +     int ret;
> > > +     unsigned int i;
> > > +     struct c3_isp_capture *cap;
> > > +
> > > +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
> > > +             cap = &isp->caps[i];
> > > +             memset(cap, 0, sizeof(*cap));
> > > +
> > > +             cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
> > > +             cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
> > > +             cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
> > > +             cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
> > > +             cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
> > > +
> > > +             c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
> > > +
> > > +             cap->id = i;
> > > +             if (cap->id == C3_ISP_CAP_DEV_0)
> > > +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_0];
> > > +             else if (cap->id == C3_ISP_CAP_DEV_1)
> > > +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_1];
> > > +             else
> > > +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_2];
> > > +
> > > +             cap->isp = isp;
> > > +             INIT_LIST_HEAD(&cap->pending);
> > > +             spin_lock_init(&cap->buff_lock);
> > > +             mutex_init(&cap->lock);
> > > +
> > > +             ret = c3_isp_register_capture(cap);
> > > +             if (ret) {
> > > +                     cap->isp = NULL;
> > > +                     mutex_destroy(&cap->lock);
> > > +                     c3_isp_captures_unregister(isp);
> > > +                     return ret;
> > > +             }
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +void c3_isp_captures_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     unsigned int i;
> > > +     struct c3_isp_capture *cap;
> > > +
> > > +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
> > > +             cap = &isp->caps[i];
> > > +
> > > +             if (!cap->isp)
> > > +                     continue;
> > > +             vb2_queue_release(&cap->vb2_q);
> > > +             media_entity_cleanup(&cap->vdev.entity);
> > > +             video_unregister_device(&cap->vdev);
> > > +             mutex_destroy(&cap->lock);
> > > +     }
> > > +}
> > > +
> > > +void c3_isp_captures_done(struct c3_isp_device *isp)
> > > +{
> > > +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
> > > +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
> > > +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
> > > +}
> > > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
> > > new file mode 100644
> > > index 000000000000..19f2a3bc29c9
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
> > > @@ -0,0 +1,327 @@
> > > +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#ifndef __C3_ISP_COMMON_H__
> > > +#define __C3_ISP_COMMON_H__
> > > +
> > > +#include <linux/clk.h>
> > > +
> > > +#include <media/media-device.h>
> > > +#include <media/videobuf2-core.h>
> > > +#include <media/v4l2-device.h>
> > > +#include <media/v4l2-subdev.h>
> > > +#include <media/videobuf2-v4l2.h>
> > > +
> > > +#define C3_ISP_DRIVER_NAME            "c3-isp"
> > > +#define C3_ISP_CLOCK_NUM_MAX          3
> > > +
> > > +#define C3_ISP_CORE_DEF_SINK_PAD_FMT  MEDIA_BUS_FMT_SRGGB10_1X10
> > > +#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUYV8_1_5X8
> > > +#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUYV8_1_5X8
> > > +#define C3_ISP_DEFAULT_WIDTH          1920
> > > +#define C3_ISP_DEFAULT_HEIGHT         1080
> > > +#define C3_ISP_MAX_WIDTH              2888
> > > +#define C3_ISP_MAX_HEIGHT             2240
> > > +#define C3_ISP_MIN_WIDTH              160
> > > +#define C3_ISP_MIN_HEIGHT             120
> > > +
> > > +#define C3_DISP_INTER                 0x400
> > > +#define C3_DISP_REG(base, id)         ((base) + (id) * C3_DISP_INTER)
> > > +#define C3_WRMIFX3_INTER              0x100
> > > +#define C3_WRMIFX3_REG(base, id)      ((base) + (id) * C3_WRMIFX3_INTER)
> > > +#define C3_PPS_TAP4_S11_H_NUM         33
> > > +#define C3_PPS_LUT_CTYPE_0            0
> > > +#define C3_PPS_LUT_CTYPE_2            2
> > > +#define C3_SCALE_EN                   1
> > > +#define C3_SCALE_DIS                  0
> > > +
> > > +#define C3_ISP_PHASE_OFFSET_0         0
> > > +#define C3_ISP_PHASE_OFFSET_1         1
> > > +#define C3_ISP_PHASE_OFFSET_NONE      0xff
> > > +
> > > +enum c3_isp_core_pads {
> > > +     C3_ISP_CORE_PAD_SINK_VIDEO,
> > > +     C3_ISP_CORE_PAD_SINK_PARAMS,
> > > +     C3_ISP_CORE_PAD_SOURCE_STATS,
> > > +     C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > > +     C3_ISP_CORE_PAD_MAX
> > > +};
> > > +
> > > +enum c3_isp_resizer_ids {
> > > +     C3_ISP_RSZ_0,
> > > +     C3_ISP_RSZ_1,
> > > +     C3_ISP_RSZ_2,
> > > +     C3_ISP_NUM_RSZ
> > > +};
> > > +
> > > +enum c3_isp_resizer_pads {
> > > +     C3_ISP_RESIZER_PAD_SINK,
> > > +     C3_ISP_RESIZER_PAD_SOURCE,
> > > +     C3_ISP_RESIZER_PAD_MAX
> > > +};
> > > +
> > > +enum c3_isp_cap_devs {
> > > +     C3_ISP_CAP_DEV_0,
> > > +     C3_ISP_CAP_DEV_1,
> > > +     C3_ISP_CAP_DEV_2,
> > > +     C3_ISP_NUM_CAP_DEVS
> > > +};
> > > +
> > > +/**
> > > + * struct c3_isp_pps_io_size - isp scaler input and output size
> > > + *
> > > + * @thsize: input horizontal size of after preprocessing
> > > + * @tvsize: input vertical size of after preprocessing
> > > + * @ohsize: output horizontal size
> > > + * @ovsize: output vertical size
> > > + * @ihsize: input horizontal size
> > > + * @max_hsize: maximum horizontal size
> > > + */
> > > +struct c3_isp_pps_io_size {
> > > +     u32 thsize;
> > > +     u32 tvsize;
> > > +     u32 ohsize;
> > > +     u32 ovsize;
> > > +     u32 ihsize;
> > > +     u32 max_hsize;
> > > +};
> > > +
> > > +/**
> > > + * @mbus_code: the mbus code
> > > + * @pads: save the pad flag of this mbus_code
> > > + * @xofst: horizontal phase offset of hardware
> > > + * @yofst: vertical phase offset of hardware
> > > + */
> > > +struct c3_isp_mbus_format_info {
> > > +     u32 mbus_code;
> > > +     u32 pads;
> > > +     u8 xofst;
> > > +     u8 yofst;
> > > +};
> > > +
> > > +/**
> > > + * @mbus_code: the mbus code
> > > + * @fourcc: pixel format
> > > + * @depth: pixel width
> > > + */
> > > +struct c3_isp_capture_format {
> > > +     u32 mbus_code;
> > > +     u32 fourcc;
> > > +     u8 depth;
> > > +};
> > > +
> > > +/**
> > > + * struct c3_isp_vb2_buffer - A container of vb2 buffer
> > > + *
> > > + * @vb: vb2 buffer
> > > + * @vaddr: buffer virtual address
> > > + * @paddr: buffer physical address
> > > + * @list: entry of the buffer in the queue
> > > + */
> > > +struct c3_isp_vb2_buffer {
> > > +     struct vb2_v4l2_buffer vb;
> > > +     void *vaddr;
> > > +     dma_addr_t paddr;
> > > +     struct list_head list;
> > > +};
> > > +
> > > +/**
> > > + * struct c3_isp_core - ISP core subdev
> > > + *
> > > + * @sd: ISP sub-device
> > > + * @pads: ISP sub-device pads
> > > + * @src_sd: source sub-device
> > > + * @isp: pointer to c3_isp_device
> > > + * @src_sd_pad: source sub-device pad
> > > + */
> > > +struct c3_isp_core {
> > > +     struct v4l2_subdev sd;
> > > +     struct media_pad pads[C3_ISP_CORE_PAD_MAX];
> > > +     struct v4l2_subdev *src_sd;
> > > +     u16 src_sd_pad;
> > > +     struct c3_isp_device *isp;
> > > +};
> > > +
> > > +/**
> > > + * struct c3_isp_resizer - ISP resizer subdev
> > > + *
> > > + * @id: resizer id
> > > + * @sd: resizer sub-device
> > > + * @pads: resizer sub-device pads
> > > + * @isp: pointer to c3_isp_device
> > > + * @cap: pointer to c3_isp_capture
> > > + */
> > > +struct c3_isp_resizer {
> > > +     enum c3_isp_resizer_ids id;
> > > +     struct v4l2_subdev sd;
> > > +     struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
> > > +     struct c3_isp_device *isp;
> > > +     struct c3_isp_capture *cap;
> > > +};
> > > +
> > > +/**
> > > + * struct c3_isp_stats - ISP statistics device
> > > + *
> > > + * @vb2_q: vb2 buffer queue
> > > + * @vdev: video node
> > > + * @vfmt: v4l2_format of the metadata format
> > > + * @pad: media pad
> > > + * @lock: protects vb2_q, vdev
> > > + * @is_streaming: stats status
> > > + * @isp: pointer to c3_isp_device
> > > + * @buff: in use buffer
> > > + * @buff_lock: protects stats buffer
> > > + * @pending: stats buffer list head
> > > + */
> > > +struct c3_isp_stats {
> > > +     struct vb2_queue vb2_q;
> > > +     struct video_device vdev;
> > > +     struct v4l2_format vfmt;
> > > +     struct media_pad pad;
> > > +
> > > +     struct mutex lock; /* Protects vb2_q, vdev */
> > > +     bool is_streaming;
> > > +     struct c3_isp_device *isp;
> > > +
> > > +     struct c3_isp_vb2_buffer *buff;
> > > +     spinlock_t buff_lock; /* Protects stream buffer */
> > > +     struct list_head pending;
> > > +};
> > > +
> > > +/**
> > > + * struct c3_isp_params - ISP parameters device
> > > + *
> > > + * @vb2_q: vb2 buffer queue
> > > + * @vdev: video node
> > > + * @vfmt: v4l2_format of the metadata format
> > > + * @pad: media pad
> > > + * @lock: protects vb2_q, vdev
> > > + * @isp: pointer to c3_isp_device
> > > + * @buff: in use buffer
> > > + * @buff_lock: protects stats buffer
> > > + * @pending: stats buffer list head
> > > + */
> > > +struct c3_isp_params {
> > > +     struct vb2_queue vb2_q;
> > > +     struct video_device vdev;
> > > +     struct v4l2_format vfmt;
> > > +     struct media_pad pad;
> > > +
> > > +     struct mutex lock; /* Protects vb2_q, vdev */
> > > +     struct c3_isp_device *isp;
> > > +
> > > +     struct c3_isp_vb2_buffer *buff;
> > > +     spinlock_t buff_lock; /* Protects stream buffer */
> > > +     struct list_head pending;
> > > +};
> > > +
> > > +/**
> > > + * struct c3_isp_capture - ISP capture device
> > > + *
> > > + * @id: capture device ID
> > > + * @vb2_q: vb2 buffer queue
> > > + * @vdev: video node
> > > + * @vfmt: v4l2_format of the capture format
> > > + * @pad: media pad
> > > + * @lock: protects vb2_q, vdev
> > > + * @is_streaming: capture device status
> > > + * @isp: pointer to c3_isp_device
> > > + * @rsz: pointer to c3_isp_resizer
> > > + * @buff: in use buffer
> > > + * @buff_lock: protects capture buffer
> > > + * @pending: capture buffer list head
> > > + */
> > > +struct c3_isp_capture {
> > > +     enum c3_isp_cap_devs id;
> > > +     struct vb2_queue vb2_q;
> > > +     struct video_device vdev;
> > > +     struct v4l2_format vfmt;
> > > +     struct media_pad pad;
> > > +
> > > +     struct mutex lock; /* Protects vb2_q, vdev */
> > > +     bool is_streaming;
> > > +     struct c3_isp_device *isp;
> > > +     struct c3_isp_resizer *rsz;
> > > +
> > > +     struct c3_isp_vb2_buffer *buff;
> > > +     spinlock_t buff_lock; /* Protects stream buffer */
> > > +     struct list_head pending;
> > > +};
> > > +
> > > +/**
> > > + * struct c3_isp_info - ISP information
> > > + *
> > > + * @clocks: array of ISP clock names
> > > + * @clock_rates: array of ISP clock rate
> > > + * @clock_num: actual clock number
> > > + */
> > > +struct c3_isp_info {
> > > +     char *clocks[C3_ISP_CLOCK_NUM_MAX];
> > > +     u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
> > > +     u32 clock_num;
> > > +};
> > > +
> > > +/**
> > > + * struct c3_isp_device - ISP platform device
> > > + *
> > > + * @dev: pointer to the struct device
> > > + * @base: base register address
> > > + * @clks: array of clocks
> > > + * @notifier: notifier to register on the v4l2-async API
> > > + * @v4l2_dev: v4l2_device variable
> > > + * @media_dev: media device variable
> > > + * @pipe: media pipeline
> > > + * @core: ISP core subdev
> > > + * @resizer: ISP resizer subdev
> > > + * @stats: ISP stats device
> > > + * @params: ISP params device
> > > + * @caps: array of ISP capture device
> > > + * @frm_sequence: used to record frame id
> > > + * @lock: protect ISP device
> > > + * @info: version-specific ISP information
> > > + */
> > > +struct c3_isp_device {
> > > +     struct device *dev;
> > > +     void __iomem *base;
> > > +     struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
> > > +
> > > +     struct v4l2_async_notifier notifier;
> > > +     struct v4l2_device v4l2_dev;
> > > +     struct media_device media_dev;
> > > +     struct media_pipeline pipe;
> > > +
> > > +     struct c3_isp_core core;
> > > +     struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
> > > +     struct c3_isp_stats stats;
> > > +     struct c3_isp_params params;
> > > +     struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
> > > +
> > > +     u32 frm_sequence;
> > > +     struct mutex lock; /* Protect ISP device */
> > > +     const struct c3_isp_info *info;
> > > +};
> > > +
> > > +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
> > > +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
> > > +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
> > > +bool c3_isp_pipeline_ready(struct c3_isp_device *isp);
> > > +
> > > +int c3_isp_core_register(struct c3_isp_device *isp);
> > > +void c3_isp_core_unregister(struct c3_isp_device *isp);
> > > +int c3_isp_resizers_register(struct c3_isp_device *isp);
> > > +void c3_isp_resizers_unregister(struct c3_isp_device *isp);
> > > +void c3_isp_rsz_start(struct c3_isp_resizer *rsz);
> > > +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz);
> > > +int c3_isp_captures_register(struct c3_isp_device *isp);
> > > +void c3_isp_captures_unregister(struct c3_isp_device *isp);
> > > +void c3_isp_captures_done(struct c3_isp_device *isp);
> > > +int c3_isp_stats_register(struct c3_isp_device *isp);
> > > +void c3_isp_stats_unregister(struct c3_isp_device *isp);
> > > +int c3_isp_stats_done(struct c3_isp_device *isp);
> > > +int c3_isp_params_register(struct c3_isp_device *isp);
> > > +void c3_isp_params_unregister(struct c3_isp_device *isp);
> > > +int c3_isp_params_done(struct c3_isp_device *isp);
> > > +
> > > +#endif
> > > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
> > > new file mode 100644
> > > index 000000000000..d3672aff9fd2
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
> > > @@ -0,0 +1,675 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#include <linux/pm_runtime.h>
> > > +
> > > +#include "c3-isp-common.h"
> > > +#include "c3-isp-regs.h"
> > > +#include "include/uapi/c3-isp-config.h"
> > > +
> > > +#define C3_ISP_CORE_SUBDEV_NAME        "isp-core"
> > > +
> > > +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
> > > +     /* RAW formats */
> > > +     {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SBGGR10_1X10,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_0,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_1,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SGBRG10_1X10,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_1,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_1,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SGRBG10_1X10,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_0,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_0,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SRGGB10_1X10,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_1,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_0,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SBGGR12_1X12,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_0,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_1,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SGBRG12_1X12,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_1,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_1,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SGRBG12_1X12,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_0,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_0,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_SRGGB12_1X12,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_1,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_0,
> > > +     },
> > > +     /* YUV formats */
> > > +     {
> > > +             .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_1_5X8,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_2X8,
> > > +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +     },
> > > +};
> > > +
> > > +static const struct c3_isp_mbus_format_info
> > > +*core_find_format_by_code(u32 code, u32 pad)
> > > +{
> > > +     int i;
> > unsigned
>
>
> Will use unsigned int i.
>
> > > +
> > > +     for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
> > > +             const struct c3_isp_mbus_format_info *info =
> > > +                     &c3_isp_core_mbus_formats[i];
> > > +
> > > +             if (info->mbus_code == code && info->pads & BIT(pad))
> > > +                     return info;
> > > +     }
> > > +
> > > +     return NULL;
> > > +}
> > > +
> > > +static const struct c3_isp_mbus_format_info
> > > +*core_find_format_by_index(u32 index, u32 pad)
> > > +{
> > > +     int i;
> > unsigned
> >
>
> Will use unsigned int i.
>
> > > +
> > > +     for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
> > > +             const struct c3_isp_mbus_format_info *info =
> > > +                     &c3_isp_core_mbus_formats[i];
> > > +
> > > +             if (!(info->pads & BIT(pad)))
> > > +                     continue;
> > > +
> > > +             if (!index)
> > > +                     return info;
> > > +
> > > +             index--;
> > > +     }
> > > +
> > > +     return NULL;
> > > +}
> > > +
> > > +static void c3_isp_core_enable(struct c3_isp_device *isp)
> > > +{
> > > +     /* Select the line sync signal */
> > > +     c3_isp_update_bits(isp, ISP_TOP_IRQ_LINE_THRD,
> > > +                        TOP_IRQ_DIN_HSYNC, TOP_IRQ_DIN_HSYNC);
> > > +
> > > +     /* Enable frame done and stats error irq */
> > > +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
> > > +                        TOP_IRQ_FRAME_DONE, TOP_IRQ_FRAME_DONE);
> > > +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
> > > +                        TOP_IRQ_STATS_ERR, TOP_IRQ_STATS_ERR);
> > > +
> > > +     /* Enable image data to ISP core */
> > > +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
> > > +                        TOP_DATA_PATH_MASK, TOP_PATH_MIPI_TO_CORE);
> > > +}
> > > +
> > > +static void c3_isp_core_disable(struct c3_isp_device *isp)
> > > +{
> > > +     /* Disable image data to ISP core */
> > > +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
> > > +                        TOP_DATA_PATH_MASK, 0x0);
> > > +
> > > +     /* Disable all irq */
> > > +     c3_isp_write(isp, ISP_TOP_IRQ_EN, 0x0);
> > > +}
> > > +
> > > +/* Set the phase offset of blc, wb and lns */
> > > +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
> > > +                               u8 xofst, u8 yofst)
> > > +{
> > > +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
> > > +                        LSWB_BLC_XPHS_OFST_MASK,
> > > +                        xofst << LSWB_BLC_XPHS_OFST_SHIFT);
> > > +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
> > > +                        LSWB_BLC_YPHS_OFST_MASK, yofst);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
> > > +                        LSWB_WB_XPHS_OFST_MASK,
> > > +                        xofst << LSWB_WB_XPHS_OFST_SHIFT);
> > > +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
> > > +                        LSWB_WB_YPHS_OFST_MASK, yofst);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
> > > +                        LSWB_LNS_XPHS_OFST_MASK,
> > > +                        xofst << LSWB_LNS_XPHS_OFST_SHIFT);
> > > +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
> > > +                        LSWB_LNS_YPHS_OFST_MASK, yofst);
> > > +}
> > > +
> > > +/* Set the phase offset of af, ae and awb */
> > > +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
> > > +                             u8 xofst, u8 yofst)
> > > +{
> > > +     c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_XPHS_OFST_MASK,
> > > +                        xofst << AF_CTRL_XPHS_OFST_SHIFT);
> > > +     c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_YPHS_OFST_MASK,
> > > +                        yofst << AF_CTRL_YPHS_OFST_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_XPHS_OFST_MASK,
> > > +                        xofst << AE_CTRL_XPHS_OFST_SHIFT);
> > > +     c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_YPHS_OFST_MASK,
> > > +                        yofst << AE_CTRL_YPHS_OFST_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_XPHS_OFST_MASK,
> > > +                        xofst << AWB_CTRL_XPHS_OFST_SHIFT);
> > > +     c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_YPHS_OFST_MASK, yofst);
> > > +}
> > > +
> > > +/* Set the phase offset of demosaic */
> > > +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
> > > +                              u8 xofst, u8 yofst)
> > > +{
> > > +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_XPHS_OFST_MASK,
> > > +                        xofst << DMS_COMMON_XPHS_OFST_SHIFT);
> > > +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_YPHS_OFST_MASK, yofst);
> > > +}
> > > +
> > > +static void c3_isp_core_cfg_ofst(struct c3_isp_device *isp,
> > > +                              struct v4l2_mbus_framefmt *fmt)
> > > +{
> > > +     const struct c3_isp_mbus_format_info *isp_fmt =
> > > +                     core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
> > > +
> > > +     c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> > > +     c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> > > +     c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
> > > +}
> > > +
> > > +/* Set format of the hardware control module */
> > > +static void c3_isp_core_top_fmt(struct c3_isp_device *isp,
> > > +                             struct v4l2_mbus_framefmt *fmt)
> > > +{
> > > +     c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
> > > +                  TOP_INPUT_HSIZE(fmt->width) | TOP_INPUT_VSIZE(fmt->height));
> > > +
> > > +     c3_isp_write(isp, ISP_TOP_FRM_SIZE,
> > > +                  TOP_FRM_CORE_IHSIZE(fmt->width) | TOP_FRM_CORE_IVSIZE(fmt->height));
> > > +
> > > +     c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE, TOP_HOLD_HSIZE_MASK,
> > > +                        fmt->width << TOP_HOLD_HSIZE_SHIFT);
> > > +}
> > > +
> > > +static void c3_isp_core_af_fmt(struct c3_isp_device *isp,
> > > +                            struct v4l2_mbus_framefmt *fmt)
> > > +{
> > > +     u32 hidx;
> > > +     u32 vidx;
> > > +     int i;
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, AF_HV_STAT_HBLK_NUM_MASK,
> > > +                        AF_STAT_BLKH_NUM << AF_HV_STAT_HBLK_NUM_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM,
> > > +                        AF_HV_STAT_VBLK_NUM_MASK, AF_STAT_BLKV_NUM);
> > > +
> > > +     c3_isp_write(isp, ISP_AF_HV_SIZE,
> > > +                  AF_HV_STAT_HSIZE(fmt->width) | AF_HV_STAT_VSIZE(fmt->height));
> > > +
> > > +     /* Set the index address to 0 position */
> > > +     c3_isp_write(isp, ISP_AF_IDX_ADDR, 0);
> > > +     /*
> > > +      * Calculate and set the coordinates of points in the grid.
> > > +      * hidx and vidx need to be aligned with 2.
> > > +      */
> > > +     for (i = 0; i <= AF_STAT_BLKH_NUM; i++) {
> > > +             hidx = i * fmt->width / AF_STAT_BLKH_NUM;
> > > +             hidx = ALIGN_DOWN(hidx, 2);
> > > +
> > > +             vidx = i * fmt->height / AF_STAT_BLKV_NUM;
> > > +             vidx = min(vidx, fmt->height);
> > > +             vidx = ALIGN_DOWN(vidx, 2);
> > > +             c3_isp_write(isp, ISP_AF_IDX_DATA,
> > > +                          AF_IDX_HIDX_DATA(hidx) | AF_IDX_VIDX_DATA(vidx));
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_core_ae_fmt(struct c3_isp_device *isp,
> > > +                            struct v4l2_mbus_framefmt *fmt)
> > > +{
> > > +     u32 hidx;
> > > +     u32 vidx;
> > > +     int i;
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, AE_HV_STAT_HBLK_NUM_MASK,
> > > +                        AE_STAT_BLKH_NUM << AE_HV_STAT_HBLK_NUM_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM,
> > > +                        AE_HV_STAT_VBLK_NUM_MASK, AE_STAT_BLKV_NUM);
> > > +
> > > +     c3_isp_write(isp, ISP_AE_HV_SIZE,
> > > +                  AE_HV_STAT_HSIZE(fmt->width) | AE_HV_STAT_VSIZE(fmt->height));
> > > +
> > > +     /* Set the index address to 0 position */
> > > +     c3_isp_write(isp, ISP_AE_IDX_ADDR, 0);
> > > +     /*
> > > +      * Calculate and set the coordinates of points in the grid.
> > > +      * hidx and vidx need to be aligned with 2.
> > > +      */
> > > +     for (i = 0; i <= AE_STAT_BLKH_NUM; i++) {
> > > +             hidx = i * fmt->width / AE_STAT_BLKH_NUM;
> > > +             hidx = ALIGN_DOWN(hidx, 2);
> > > +
> > > +             vidx = i * fmt->height / AE_STAT_BLKV_NUM;
> > > +             vidx = min(vidx, fmt->height);
> > > +             vidx = ALIGN_DOWN(vidx, 2);
> > > +
> > > +             c3_isp_write(isp, ISP_AE_IDX_DATA,
> > > +                          AE_IDX_HIDX_DATA(hidx) | AE_IDX_VIDX_DATA(vidx));
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_core_awb_fmt(struct c3_isp_device *isp,
> > > +                             struct v4l2_mbus_framefmt *fmt)
> > > +{
> > > +     u32 hidx;
> > > +     u32 vidx;
> > > +     int i;
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, AWB_HV_STAT_HBLK_NUM_MASK,
> > > +                        AWB_STAT_BLKH_NUM << AWB_HV_STAT_HBLK_NUM_SHIFT);
> > > +     c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM,
> > > +                        AWB_HV_STAT_VBLK_NUM_MASK, AWB_STAT_BLKV_NUM);
> > > +
> > > +     c3_isp_write(isp, ISP_AWB_HV_SIZE,
> > > +                  AWB_HV_STAT_HSIZE(fmt->width) | AWB_HV_STAT_VSIZE(fmt->height));
> > > +
> > > +     /* Set the index address to 0 position */
> > > +     c3_isp_write(isp, ISP_AWB_IDX_ADDR, 0);
> > > +     /*
> > > +      * Calculate and set the coordinates of points in the grid.
> > > +      * hidx and vidx need to be aligned with 2.
> > > +      */
> > > +     for (i = 0; i <= AWB_STAT_BLKH_NUM; i++) {
> > > +             hidx = i * fmt->width / AWB_STAT_BLKH_NUM;
> > > +             hidx = ALIGN_DOWN(hidx, 2);
> > > +
> > > +             vidx = i * fmt->height / AWB_STAT_BLKV_NUM;
> > > +             vidx = min(vidx, fmt->height);
> > > +             vidx = ALIGN_DOWN(vidx, 2);
> > > +
> > > +             c3_isp_write(isp, ISP_AWB_IDX_DATA,
> > > +                          AWB_IDX_HIDX_DATA(hidx) | AWB_IDX_VIDX_DATA(vidx));
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
> > > +                                struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_mbus_framefmt *fmt;
> > > +
> > > +     fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> > > +
> > > +     c3_isp_core_cfg_ofst(isp, fmt);
> > > +     c3_isp_core_top_fmt(isp, fmt);
> > > +     c3_isp_core_af_fmt(isp, fmt);
> > > +     c3_isp_core_ae_fmt(isp, fmt);
> > > +     c3_isp_core_awb_fmt(isp, fmt);
> > > +}
> > > +
> > > +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
> > > +                                   struct v4l2_subdev_state *state,
> > > +                                   u32 pad, u64 streams_mask)
> > > +{
> > > +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
> > > +     u64 sink_streams;
> > > +     int ret;
> > > +
> > > +     core->isp->frm_sequence = 0;
> > > +     c3_isp_core_cfg_format(core->isp, state);
> > > +     c3_isp_core_enable(core->isp);
> > > +
> > > +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > > +                                                    C3_ISP_CORE_PAD_SINK_VIDEO,
> > > +                                                    &streams_mask);
> > > +     ret = v4l2_subdev_enable_streams(core->src_sd,
> > > +                                      core->src_sd_pad, sink_streams);
> > > +     if (ret) {
> > > +             c3_isp_core_disable(core->isp);
> > > +             return ret;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
> > > +                                    struct v4l2_subdev_state *state,
> > > +                                    u32 pad, u64 streams_mask)
> > > +{
> > > +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
> > > +     u64 sink_streams;
> > > +     int ret;
> > > +
> > > +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> > > +                                                    C3_ISP_CORE_PAD_SINK_VIDEO,
> > > +                                                    &streams_mask);
> > > +     ret = v4l2_subdev_disable_streams(core->src_sd,
> > > +                                       core->src_sd_pad, sink_streams);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     c3_isp_core_disable(core->isp);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
> > > +                                struct v4l2_subdev_state *state,
> > > +                                struct v4l2_subdev_krouting *routing)
> > > +{
> > > +     static const struct v4l2_mbus_framefmt format = {
> > > +             .width = C3_ISP_DEFAULT_WIDTH,
> > > +             .height = C3_ISP_DEFAULT_HEIGHT,
> > > +             .code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
> > > +             .field = V4L2_FIELD_NONE,
> > > +             .colorspace = V4L2_COLORSPACE_SRGB,
> > > +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
> > > +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
> > > +             .xfer_func = V4L2_XFER_FUNC_SRGB,
> > > +     };
> > > +     int ret;
> > > +
> > > +     ret = v4l2_subdev_routing_validate(sd, routing,
> > > +                                        V4L2_SUBDEV_ROUTING_NO_SOURCE_STREAM_MIX);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
> > > +                                 struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_subdev_route routes[2];
> > > +     struct v4l2_subdev_krouting routing;
> > > +
> > > +     routes[0].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
> > > +     routes[0].sink_stream = 0;
> > > +     routes[0].source_pad = C3_ISP_CORE_PAD_SOURCE_STATS;
> > > +     routes[0].source_stream = 0;
> > > +     routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> > > +
> > > +     routes[1].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
> > > +     routes[1].sink_stream = 0;
> > > +     routes[1].source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
> > > +     routes[1].source_stream = 0;
> > > +     routes[1].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> > > +
> > > +     routing.num_routes = ARRAY_SIZE(routes);
> > > +     routing.routes = routes;
> > > +
> > > +     return c3_isp_core_cfg_routing(sd, state, &routing);
> > > +}
> > > +
> > > +static int c3_isp_core_set_routing(struct v4l2_subdev *sd,
> > > +                                struct v4l2_subdev_state *state,
> > > +                                enum v4l2_subdev_format_whence which,
> > > +                                struct v4l2_subdev_krouting *routing)
> > I'm not sure I see a reason for the ISP subdev to implement routing.
> > In my understanding it will only receive a single image stream and
> > processes it to 3 DMA output nodes, and to control which output is
> > enabled you use media links.
> >
>
> There is no need to use routing.
>
> Will remove this interface.
>
> > > +{
> > > +     bool is_streaming = v4l2_subdev_is_streaming(sd);
> > > +
> > > +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> > > +             return -EBUSY;
> > > +
> > > +     return c3_isp_core_cfg_routing(sd, state, routing);
> > > +}
> > > +
> > > +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
> > > +                                   struct v4l2_subdev_state *state,
> > > +                                   struct v4l2_subdev_mbus_code_enum *code)
> > > +{
> > > +     const struct c3_isp_mbus_format_info *info;
> > > +     int ret = 0;
> > > +
> > > +     switch (code->pad) {
> > > +     case C3_ISP_CORE_PAD_SINK_VIDEO:
> > > +     case C3_ISP_CORE_PAD_SOURCE_VIDEO:
> > > +             info = core_find_format_by_index(code->index, code->pad);
> > > +             if (!info)
> > > +                     ret = -EINVAL;
> > > +             else
> > > +                     code->code = info->mbus_code;
> > > +
> > > +             break;
> > > +     case C3_ISP_CORE_PAD_SINK_PARAMS:
> > > +     case C3_ISP_CORE_PAD_SOURCE_STATS:
> > > +             if (code->index)
> > > +                     ret = -EINVAL;
> > > +             else
> > > +                     code->code = MEDIA_BUS_FMT_METADATA_FIXED;
> > > +
> > > +             break;
> > > +     default:
> > > +             ret = -EINVAL;
> > > +             break;
> > > +     }
> > > +
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_isp_core_set_sink_fmt(struct v4l2_subdev_state *state,
> > > +                                  struct v4l2_subdev_format *format)
> > > +{
> > > +     struct v4l2_mbus_framefmt *sink_fmt;
> > > +     const struct c3_isp_mbus_format_info *isp_fmt;
> > > +
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
> > > +
> > > +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
> > > +     if (!isp_fmt)
> > > +             sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
> > > +     else
> > > +             sink_fmt->code = format->format.code;
> > > +
> > > +     sink_fmt->width = clamp_t(u32, format->format.width,
> > > +                               C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> > > +     sink_fmt->height = clamp_t(u32, format->format.height,
> > > +                                C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> > > +
> > > +     format->format = *sink_fmt;
> > > +}
> > > +
> > > +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
> > > +                                    struct v4l2_subdev_format *format)
> > > +{
> > > +     const struct c3_isp_mbus_format_info *isp_fmt;
> > > +     struct v4l2_mbus_framefmt *sink_fmt;
> > > +     struct v4l2_mbus_framefmt *src_fmt;
> > > +
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> > > +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
> > > +
> > > +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
> > > +     if (!isp_fmt)
> > > +             src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
> > > +     else
> > > +             src_fmt->code = format->format.code;
> > > +
> > > +     /* The source size must be same with the sink size. */
> > > +     src_fmt->width  = sink_fmt->width;
> > > +     src_fmt->height = sink_fmt->height;
> > > +
> > > +     format->format = *src_fmt;
> > > +}
> > > +
> > > +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
> > > +                            struct v4l2_subdev_state *state,
> > > +                            struct v4l2_subdev_format *format)
> > > +{
> > > +     struct v4l2_mbus_framefmt *fmt;
> > > +
> > > +     switch (format->pad) {
> > > +     case C3_ISP_CORE_PAD_SINK_VIDEO:
> > > +             c3_isp_core_set_sink_fmt(state, format);
> > > +             break;
> > > +     case C3_ISP_CORE_PAD_SINK_PARAMS:
> > > +     case C3_ISP_CORE_PAD_SOURCE_STATS:
> > > +             fmt = v4l2_subdev_state_get_format(state, format->pad);
> > > +             format->format = *fmt;
> > > +             break;
> > > +     case C3_ISP_CORE_PAD_SOURCE_VIDEO:
> > > +             c3_isp_core_set_source_fmt(state, format);
> > > +             break;
> > > +     default:
> > I don't think this can happen. The core validates that format->pad is
> > correct.
> >
> Will remove this branch or use "if" to replace "switch".
> > > +             dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
> > > +             return -ENOTTY;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_core_init_state(struct v4l2_subdev *sd,
> > > +                               struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_mbus_framefmt *sink_fmt;
> > > +     struct v4l2_mbus_framefmt *src_fmt;
> > > +
> > > +     /* Video sink pad */
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
> > > +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
> > > +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> > > +     sink_fmt->field = V4L2_FIELD_NONE;
> > > +     sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
> > > +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> > > +     sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
> > > +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> > > +     sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
> > > +
> > > +     /* Video source pad */
> > > +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
> > > +     src_fmt->width = C3_ISP_DEFAULT_WIDTH;
> > > +     src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> > > +     src_fmt->field = V4L2_FIELD_NONE;
> > > +     src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
> > > +     src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> > > +     src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
> > > +     src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> > > +     src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> > > +
> > > +     /* Parameters pad */
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
> > > +     sink_fmt->width = 0;
> > > +     sink_fmt->height = 0;
> > > +     sink_fmt->field = V4L2_FIELD_NONE;
> > > +     sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
> > > +
> > > +     /* Statistics pad */
> > > +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
> > > +     src_fmt->width = 0;
> > > +     src_fmt->height = 0;
> > > +     src_fmt->field = V4L2_FIELD_NONE;
> > > +     src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
> > > +
> > > +     return c3_isp_core_init_routing(sd, state);
> > > +}
> > > +
> > > +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
> > > +     .enum_mbus_code = c3_isp_core_enum_mbus_code,
> > > +     .get_fmt = v4l2_subdev_get_fmt,
> > > +     .set_fmt = c3_isp_core_set_fmt,
> > > +     .enable_streams = c3_isp_core_enable_streams,
> > > +     .disable_streams = c3_isp_core_disable_streams,
> > > +     .set_routing = c3_isp_core_set_routing,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
> > > +     .pad = &c3_isp_core_pad_ops,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
> > > +     .init_state = c3_isp_core_init_state,
> > > +};
> > > +
> > > +static int c3_isp_core_link_validate(struct media_link *link)
> > > +{
> > > +     if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
> > > +             return 0;
> > > +
> > > +     return v4l2_subdev_link_validate(link);
> > > +}
> > > +
> > > +/* Media entity operations */
> > > +static const struct media_entity_operations c3_isp_core_entity_ops = {
> > > +     .link_validate = c3_isp_core_link_validate,
> > > +};
> > > +
> > > +int c3_isp_core_register(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_core *core = &isp->core;
> > > +     struct v4l2_subdev *sd = &core->sd;
> > > +     int ret;
> > > +
> > > +     v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
> > > +     sd->owner = THIS_MODULE;
> > > +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > > +     sd->internal_ops = &c3_isp_core_internal_ops;
> > > +     snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
> > > +
> > > +     sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
> > > +     sd->entity.ops = &c3_isp_core_entity_ops;
> > > +
> > > +     core->isp = isp;
> > > +     sd->dev = isp->dev;
> > > +     v4l2_set_subdevdata(sd, core);
> > > +
> > > +     core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
> > > +     core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
> > > +     core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
> > > +     core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
> > > +     ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = v4l2_subdev_init_finalize(sd);
> > > +     if (ret)
> > > +             goto err_entity_cleanup;
> > > +
> > > +     ret = v4l2_device_register_subdev(&isp->v4l2_dev, sd);
> > > +     if (ret)
> > > +             goto err_subdev_cleanup;
> > > +
> > > +     return 0;
> > > +
> > > +err_subdev_cleanup:
> > > +     v4l2_subdev_cleanup(sd);
> > > +err_entity_cleanup:
> > > +     media_entity_cleanup(&sd->entity);
> > > +     return ret;
> > > +}
> > > +
> > > +void c3_isp_core_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_core *core = &isp->core;
> > > +     struct v4l2_subdev *sd = &core->sd;
> > > +
> > > +     v4l2_device_unregister_subdev(sd);
> > > +     v4l2_subdev_cleanup(sd);
> > > +     media_entity_cleanup(&sd->entity);
> > > +}
> > > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
> > > new file mode 100644
> > > index 000000000000..a57b9f8dbc3c
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
> > > @@ -0,0 +1,486 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#include <linux/clk.h>
> > > +#include <linux/device.h>
> > > +#include <linux/module.h>
> > > +#include <linux/mutex.h>
> > > +#include <linux/platform_device.h>
> > > +#include <linux/pm_runtime.h>
> > > +
> > > +#include <media/v4l2-common.h>
> > > +#include <media/v4l2-device.h>
> > > +#include <media/v4l2-fwnode.h>
> > > +#include <media/v4l2-mc.h>
> > > +
> > > +#include "c3-isp-common.h"
> > > +#include "c3-isp-regs.h"
> > > +
> > > +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
> > > +{
> > > +     return readl(isp->base + reg);
> > > +}
> > > +
> > > +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
> > > +{
> > > +     writel(val, isp->base + reg);
> > > +}
> > > +
> > > +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
> > > +{
> > > +     u32 orig, tmp;
> > > +
> > > +     orig = c3_isp_read(isp, reg);
> > > +
> > > +     tmp = orig & ~mask;
> > > +     tmp |= val & mask;
> > > +
> > > +     if (tmp != orig)
> > > +             c3_isp_write(isp, reg, tmp);
> > > +}
> > > +
> > > +bool c3_isp_pipeline_ready(struct c3_isp_device *isp)
> > > +{
> > > +     struct media_pipeline_entity_iter iter;
> > > +     unsigned int n_video_devices = 0;
> > > +     struct media_entity *entity;
> > > +     int ret;
> > > +
> > > +     ret = media_pipeline_entity_iter_init(&isp->pipe, &iter);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     media_pipeline_for_each_entity(&isp->pipe, &iter, entity) {
> > > +             if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
> > > +                     n_video_devices++;
> > > +     }
> > > +
> > > +     media_pipeline_entity_iter_cleanup(&iter);
> > > +
> > > +     return n_video_devices == isp->pipe.start_count;
> > > +}
> > As suggested in the review of the -params.c module, I suggest to
> > implement start_streaming on the capture nodes only. From there you
> > can call enable_streams on the ISP subdevice. The ISP subdevice can
> > count how many links to resizers are enabled, and actually start its
> > operation when all the enabled ones have been started.
> >
> > The idea is to use enabled media links to identify how many capture video
> > devices are expected to be used, and only start the ISP (and the
> > downstream subdevices like the CSI-2 Adap and RX and the sensor) when
> > all of linked ones have been started.
> >
>
> OK, Will implement start_streaming on the capture nodes only and count how
> many
>
> links to resizer are enabled in ISP sub device.
>
> > > +
> > > +/* PM runtime suspend */
> > > +static int __maybe_unused c3_isp_runtime_suspend(struct device *dev)
> > > +{
> > > +     struct c3_isp_device *isp = dev_get_drvdata(dev);
> > > +
> > > +     clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +/* PM runtime resume */
> > > +static int __maybe_unused c3_isp_runtime_resume(struct device *dev)
> > > +{
> > > +     struct c3_isp_device *isp = dev_get_drvdata(dev);
> > > +
> > > +     return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
> > > +}
> > > +
> > > +static const struct dev_pm_ops c3_isp_pm_ops = {
> > > +     SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> > > +                             pm_runtime_force_resume)
> > > +     SET_RUNTIME_PM_OPS(c3_isp_runtime_suspend,
> > > +                        c3_isp_runtime_resume, NULL)
> > > +};
> > > +
> > > +/* IRQ handling */
> > > +static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
> > > +{
> > > +     struct c3_isp_device *isp = dev;
> > > +     u32 status;
> > > +
> > > +     /* Get irq status and clear irq status */
> > > +     status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
> > > +     c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
> > > +
> > > +     if (status & TOP_IRQ_FRAME_DONE) {
> > > +             c3_isp_stats_done(isp);
> > > +             c3_isp_params_done(isp);
> > > +             c3_isp_captures_done(isp);
> > > +             isp->frm_sequence++;
> > > +     }
> > > +
> > > +     if (status & TOP_IRQ_STATS_ERR)
> > > +             dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
> > > +
> > > +     return IRQ_HANDLED;
> > > +}
> > > +
> > > +/* Subdev notifier register */
> > > +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
> > > +                            struct v4l2_subdev *sd,
> > > +                            struct v4l2_async_connection *asc)
> > > +{
> > > +     struct c3_isp_device *isp =
> > > +             container_of(notifier, struct c3_isp_device, notifier);
> > > +     struct c3_isp_core *core = &isp->core;
> > > +     struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
> > > +     int ret;
> > > +
> > > +     ret = media_entity_get_fwnode_pad(&sd->entity,
> > > +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
> > > +     if (ret < 0) {
> > > +             dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
> > > +             return ret;
> > > +     }
> > > +
> > > +     core->src_sd = sd;
> > > +     core->src_sd_pad = ret;
> > > +
> > > +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
> > > +                                            MEDIA_LNK_FL_IMMUTABLE);
> > > +}
> > > +
> > > +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
> > > +{
> > > +     struct c3_isp_device *isp =
> > > +             container_of(notifier, struct c3_isp_device, notifier);
> > > +     int ret;
> > > +
> > > +     ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
> > > +     if (ret < 0) {
> > > +             dev_err(isp->dev,
> > > +                     "Failed to register subdev nodes: %d\n", ret);
> > > +             return ret;
> > > +     }
> > > +
> > > +     dev_info(isp->dev, "notify complete\n");
> > > +
> > > +     return media_device_register(&isp->media_dev);
> > > +}
> > > +
> > > +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
> > > +{
> > > +     struct c3_isp_device *isp =
> > > +             container_of(asc->notifier, struct c3_isp_device, notifier);
> > > +
> > > +     media_device_unregister(&isp->media_dev);
> > > +}
> > > +
> > > +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
> > > +     .bound = c3_isp_notify_bound,
> > > +     .complete = c3_isp_notify_complete,
> > > +     .destroy = c3_isp_notify_destroy,
> > > +};
> > > +
> > > +static int c3_isp_async_nf_register(struct c3_isp_device *isp)
> > > +{
> > > +     struct v4l2_async_connection *asc;
> > > +     struct fwnode_handle *ep;
> > > +     int ret;
> > > +
> > > +     v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
> > > +
> > > +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
> > > +                                          FWNODE_GRAPH_ENDPOINT_NEXT);
> > > +     if (!ep)
> > > +             return -ENOTCONN;
> > > +
> > > +     asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
> > > +                                           struct v4l2_async_connection);
> > > +     if (IS_ERR(asc)) {
> > > +             fwnode_handle_put(ep);
> > > +             return PTR_ERR(asc);
> > > +     }
> > > +
> > > +     fwnode_handle_put(ep);
> > > +
> > > +     isp->notifier.ops = &c3_isp_notify_ops;
> > > +     ret = v4l2_async_nf_register(&isp->notifier);
> > > +     if (ret)
> > > +             v4l2_async_nf_cleanup(&isp->notifier);
> > > +
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     v4l2_async_nf_unregister(&isp->notifier);
> > > +     v4l2_async_nf_cleanup(&isp->notifier);
> > > +}
> > > +
> > > +static int c3_isp_v4l2_register(struct c3_isp_device *isp)
> > > +{
> > > +     struct media_device *media_dev = &isp->media_dev;
> > > +     struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
> > > +     int ret;
> > > +
> > > +     /* Initialize media device */
> > > +     strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
> > > +             sizeof(media_dev->model));
> > > +     media_dev->dev = isp->dev;
> > > +
> > > +     media_device_init(media_dev);
> > > +
> > > +     /* Initialize v4l2 device */
> > > +     v4l2_dev->mdev = media_dev;
> > > +     strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
> > > +             sizeof(v4l2_dev->name));
> > > +
> > > +     ret = v4l2_device_register(isp->dev, v4l2_dev);
> > > +     if (ret) {
> > > +             media_device_cleanup(media_dev);
> > > +             dev_err(isp->dev,
> > > +                     "Failed to register V4L2 device: %d\n", ret);
> > > +     }
> > > +
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     v4l2_device_unregister(&isp->v4l2_dev);
> > > +     media_device_cleanup(&isp->media_dev);
> > > +}
> > > +
> > > +static void c3_isp_remove_links(struct c3_isp_device *isp)
> > > +{
> > > +     unsigned int i;
> > > +
> > > +     media_entity_remove_links(&isp->core.sd.entity);
> > > +
> > > +     for (i = 0; i < C3_ISP_NUM_RSZ; i++)
> > > +             media_entity_remove_links(&isp->resizers[i].sd.entity);
> > > +
> > > +     for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
> > > +             media_entity_remove_links(&isp->caps[i].vdev.entity);
> > > +}
> > > +
> > > +static int c3_isp_create_links(struct c3_isp_device *isp)
> > > +{
> > > +     unsigned int i;
> > > +     int ret;
> > > +
> > > +     for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
> > > +             ret = media_create_pad_link(&isp->resizers[i].sd.entity,
> > > +                                         C3_ISP_RESIZER_PAD_SOURCE,
> > > +                                         &isp->resizers[i].cap->vdev.entity,
> > > +                                         0, MEDIA_LNK_FL_ENABLED);
> > This could be made IMMUTABLE
> >
>
> OK,  will add IMMUTABLE flag.
>
> > > +             if (ret) {
> > > +                     dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
> > > +                     goto err_remove_links;
> > > +             }
> > > +
> > > +             ret = media_create_pad_link(&isp->core.sd.entity,
> > > +                                         C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > > +                                         &isp->resizers[i].sd.entity,
> > > +                                         C3_ISP_RESIZER_PAD_SINK,
> > > +                                         MEDIA_LNK_FL_ENABLED);
> > > +             if (ret) {
> > > +                     dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
> > > +                     goto err_remove_links;
> > > +             }
> > > +     }
> > > +
> > > +     ret = media_create_pad_link(&isp->core.sd.entity,
> > > +                                 C3_ISP_CORE_PAD_SOURCE_STATS,
> > > +                                 &isp->stats.vdev.entity,
> > > +                                 0, MEDIA_LNK_FL_ENABLED);
> > > +     if (ret) {
> > > +             dev_err(isp->dev, "Failed to link core and stats\n");
> > > +             goto err_remove_links;
> > > +     }
> > > +
> > > +     ret = media_create_pad_link(&isp->params.vdev.entity, 0,
> > > +                                 &isp->core.sd.entity,
> > > +                                 C3_ISP_CORE_PAD_SINK_PARAMS,
> > > +                                 MEDIA_LNK_FL_ENABLED);
> > > +     if (ret) {
> > > +             dev_err(isp->dev, "Failed to link params and core\n");
> > > +             goto err_remove_links;
> > > +     }
> > > +
> > > +     return 0;
> > > +
> > > +err_remove_links:
> > > +     c3_isp_remove_links(isp);
> > > +     return ret;
> > > +}
> > > +
> > > +static int c3_isp_videos_register(struct c3_isp_device *isp)
> > > +{
> > > +     int ret;
> > > +
> > > +     ret = c3_isp_captures_register(isp);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = c3_isp_stats_register(isp);
> > > +     if (ret)
> > > +             goto err_captures_unregister;
> > > +
> > > +     ret = c3_isp_params_register(isp);
> > > +     if (ret)
> > > +             goto err_stats_unregister;
> > > +
> > > +     ret = c3_isp_create_links(isp);
> > > +     if (ret)
> > > +             goto err_params_unregister;
> > > +
> > > +     return 0;
> > > +
> > > +err_params_unregister:
> > > +     c3_isp_params_unregister(isp);
> > > +err_stats_unregister:
> > > +     c3_isp_stats_unregister(isp);
> > > +err_captures_unregister:
> > > +     c3_isp_captures_unregister(isp);
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_isp_videos_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     c3_isp_remove_links(isp);
> > > +     c3_isp_params_unregister(isp);
> > > +     c3_isp_stats_unregister(isp);
> > > +     c3_isp_captures_unregister(isp);
> > > +}
> > > +
> > > +static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
> > > +{
> > > +     const struct c3_isp_info *info = isp->info;
> > > +     int ret;
> > > +     u32 i;
> > > +
> > > +     for (i = 0; i < info->clock_num; i++)
> > > +             isp->clks[i].id = info->clocks[i];
> > > +
> > > +     ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     for (i = 0; i < info->clock_num; i++) {
> > > +             if (!info->clock_rates[i])
> > > +                     continue;
> > > +             ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);
> > > +             if (ret) {
> > > +                     dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
> > > +                             info->clock_rates[i]);
> > > +                     return ret;
> > > +             }
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_probe(struct platform_device *pdev)
> > > +{
> > > +     struct device *dev = &pdev->dev;
> > > +     struct c3_isp_device *isp;
> > > +     int irq;
> > > +     int ret;
> > > +
> > > +     isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
> > > +     if (!isp)
> > > +             return -ENOMEM;
> > > +
> > > +     isp->info = of_device_get_match_data(dev);
> > > +     isp->dev = dev;
> > > +
> > > +     isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
> > > +     if (IS_ERR(isp->base))
> > > +             return dev_err_probe(dev, PTR_ERR(isp->base),
> > > +                                  "Failed to ioremap resource\n");
> > > +
> > > +     irq = platform_get_irq(pdev, 0);
> > > +     if (irq < 0)
> > > +             return irq;
> > > +
> > > +     ret = c3_isp_cfg_clocks(isp);
> > > +     if (ret)
> > > +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
> > > +
> > > +     platform_set_drvdata(pdev, isp);
> > > +
> > > +     pm_runtime_enable(dev);
> > > +
> > > +     ret = c3_isp_v4l2_register(isp);
> > > +     if (ret)
> > > +             goto err_runtime_disable;
> > > +
> > > +     ret = c3_isp_core_register(isp);
> > > +     if (ret)
> > > +             goto err_v4l2_unregister;
> > > +
> > > +     ret = c3_isp_resizers_register(isp);
> > > +     if (ret)
> > > +             goto err_core_unregister;
> > > +
> > > +     ret = c3_isp_async_nf_register(isp);
> > > +     if (ret)
> > > +             goto err_resizers_unregister;
> > > +
> > > +     ret = c3_isp_videos_register(isp);
> > > +     if (ret)
> > > +             goto err_nf_unregister;
> > > +
> > > +     ret = devm_request_irq(dev, irq,
> > > +                            c3_isp_irq_handler, IRQF_SHARED,
> > > +                            dev_driver_string(dev), isp);
> > > +     if (ret)
> > > +             goto err_streams_unregister;
> > I would request the IRQ before registering devices to userspace.
>
>
> Will move IRQ to the front of registering devices.
>
> > > +
> > > +     mutex_init(&isp->lock);
> > > +
> > > +     return 0;
> > > +
> > > +err_streams_unregister:
> > > +     c3_isp_videos_unregister(isp);
> > > +err_nf_unregister:
> > > +     c3_isp_async_nf_unregister(isp);
> > > +err_resizers_unregister:
> > > +     c3_isp_resizers_unregister(isp);
> > > +err_core_unregister:
> > > +     c3_isp_core_unregister(isp);
> > > +err_v4l2_unregister:
> > > +     c3_isp_v4l2_unregister(isp);
> > > +err_runtime_disable:
> > > +     pm_runtime_disable(dev);
> > > +     return ret;
> > > +};
> > > +
> > > +static void c3_isp_remove(struct platform_device *pdev)
> > > +{
> > > +     struct c3_isp_device *isp = platform_get_drvdata(pdev);
> > > +
> > > +     mutex_destroy(&isp->lock);
> > > +     c3_isp_videos_unregister(isp);
> > > +     c3_isp_async_nf_unregister(isp);
> > > +     c3_isp_core_unregister(isp);
> > > +     c3_isp_resizers_unregister(isp);
> > > +     c3_isp_v4l2_unregister(isp);
> > > +     pm_runtime_disable(isp->dev);
> > > +};
> > > +
> > > +static const struct c3_isp_info isp_info = {
> > > +     .clocks = {"vapb", "isp0"},
> > > +     .clock_rates = {0, 400000000},
> > > +     .clock_num = 2
> > > +};
> > > +
> > > +static const struct of_device_id c3_isp_of_match[] = {
> > > +     { .compatible = "amlogic,c3-isp",
> > > +       .data = &isp_info },
> > > +     { },
> > > +};
> > > +MODULE_DEVICE_TABLE(of, c3_isp_of_match);
> > > +
> > > +static struct platform_driver c3_isp_driver = {
> > > +     .probe = c3_isp_probe,
> > > +     .remove = c3_isp_remove,
> > > +     .driver = {
> > > +             .name = "c3-isp",
> > > +             .of_match_table = c3_isp_of_match,
> > > +             .pm = &c3_isp_pm_ops,
> > > +     },
> > > +};
> > > +
> > > +module_platform_driver(c3_isp_driver);
> > > +
> > > +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
> > > +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
> > > +MODULE_LICENSE("GPL");
> > > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
> > > new file mode 100644
> > > index 000000000000..8a6b7ce86eaf
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
> > > @@ -0,0 +1,857 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#include <linux/pm_runtime.h>
> > > +
> > > +#include <media/v4l2-ctrls.h>
> > > +#include <media/v4l2-event.h>
> > Do you need these ?
> >
>
> Will remove these two lines.
>
> > > +#include <media/v4l2-ioctl.h>
> > > +#include <media/v4l2-mc.h>
> > > +#include <media/videobuf2-dma-contig.h>
> > See below about using the dma-contig vb2 ops
>
>
> Will use vmalloc ops.
>
> > > +
> > > +#include "c3-isp-common.h"
> > > +#include "c3-isp-regs.h"
> > > +#include "include/uapi/c3-isp-config.h"
> > > +
> > > +typedef void (*block_handler)(struct c3_isp_device *isp,
> > > +                           struct c3_isp_param_block_header *block);
> > > +
> > > +struct c3_isp_block_handler {
> > > +     size_t size;
> > > +     block_handler handler;
> > > +};
> > > +
> > > +/* Hardware configuration */
> > > +
> > > +static void c3_isp_params_cfg_wb_change(struct c3_isp_device *isp,
> > > +                                     struct c3_isp_param_block_header *block)
> > > +{
> > > +     struct wb_change_cfg *wb = (struct wb_change_cfg *)block;
> > > +
> > > +     if (!block->enabled) {
> > > +             c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
> > > +                                TOP_BEO_CTRL_WB_EN, false);
> > > +             return;
> > > +     }
> > > +
> > > +     c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
> > > +                        TOP_BEO_CTRL_WB_EN, TOP_BEO_CTRL_WB_EN);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, LSWB_WB_GAIN0_MASK,
> > > +                        wb->wb_gain[0] << LSWB_WB_GAIN0_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0,
> > > +                        LSWB_WB_GAIN1_MASK, wb->wb_gain[1]);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, LSWB_WB_GAIN2_MASK,
> > > +                        wb->wb_gain[2] << LSWB_WB_GAIN2_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1,
> > > +                        LSWB_WB_GAIN3_MASK, wb->wb_gain[3]);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN2,
> > > +                        LSWB_WB_GAIN4_MASK, wb->wb_gain[4]);
> > > +
> > > +     c3_isp_write(isp, ISP_LSWB_WB_LIMIT0,
> > > +                  LSWB_WB_LIMIT0(wb->wb_limit[0]) | LSWB_WB_LIMIT1(wb->wb_limit[1]));
> > > +
> > > +     c3_isp_write(isp, ISP_LSWB_WB_LIMIT1,
> > > +                  LSWB_WB_LIMIT2(wb->wb_limit[2]) | LSWB_WB_LIMIT3(wb->wb_limit[3]));
> > > +
> > > +     c3_isp_update_bits(isp, ISP_LSWB_WB_LIMIT2, LSWB_WB_LIMIT4_MASK,
> > > +                        wb->wb_limit[4]);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_BL12_GRBGI0_MASK,
> > > +                        wb->ae_bl12_grbgi[0]);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_GAIN_GRBGI0_MASK,
> > > +                        wb->ae_gain_grbgi[0] << AE_CRTL2_GAIN_GRBGI0_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_BL12_GRBGI1_MASK,
> > > +                        wb->ae_bl12_grbgi[1]);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_GAIN_GRBGI1_MASK,
> > > +                        wb->ae_gain_grbgi[1] << AE_CRTL2_GAIN_GRBGI1_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_BL12_GRBGI2_MASK,
> > > +                        wb->ae_bl12_grbgi[2]);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_GAIN_GRBGI2_MASK,
> > > +                        wb->ae_gain_grbgi[2] << AE_CRTL2_GAIN_GRBGI2_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_BL12_GRBGI3_MASK,
> > > +                        wb->ae_bl12_grbgi[3]);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_GAIN_GRBGI3_MASK,
> > > +                        wb->ae_gain_grbgi[3] << AE_CRTL2_GAIN_GRBGI3_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_BL12_GRBGI4_MASK,
> > > +                        wb->ae_bl12_grbgi[4]);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_GAIN_GRBGI4_MASK,
> > > +                        wb->ae_gain_grbgi[4] << AE_CRTL2_GAIN_GRBGI4_SHIFT);
> > > +}
> > > +
> > > +static void c3_isp_params_cfg_wb_luma(struct c3_isp_device *isp,
> > > +                                   struct c3_isp_param_block_header *block)
> > > +{
> > > +     struct wb_luma_cfg *wb_luma = (struct wb_luma_cfg *)block;
> > > +
> > > +     if (!block->enabled)
> > > +             return;
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_0,
> > > +                        AWB_STAT_BLC20_GR_MASK, wb_luma->awb_stat_blc20[0]);
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_1,
> > > +                        AWB_STAT_BLC20_R_MASK, wb_luma->awb_stat_blc20[1]);
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_2,
> > > +                        AWB_STAT_BLC20_B_MASK, wb_luma->awb_stat_blc20[2]);
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_3,
> > > +                        AWB_STAT_BLC20_GB_MASK, wb_luma->awb_stat_blc20[3]);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_0,
> > > +                        AWB_STAT_GAIN10_GR_MASK, wb_luma->awb_stat_gain10[0]);
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_1,
> > > +                        AWB_STAT_GAIN10_R_MASK, wb_luma->awb_stat_gain10[1]);
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_2,
> > > +                        AWB_STAT_GAIN10_B_MASK, wb_luma->awb_stat_gain10[2]);
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_3,
> > > +                        AWB_STAT_GAIN10_GB_MASK, wb_luma->awb_stat_gain10[3]);
> > > +
> > > +     c3_isp_write(isp, ISP_AWB_STAT_SATUR_CTRL,
> > > +                  AWB_STAT_SATUR_LOW(wb_luma->awb_stat_satur_low) |
> > > +                  AWB_STAT_SATUR_HIGH(wb_luma->awb_stat_satur_high));
> > > +}
> > > +
> > > +static void c3_isp_params_cfg_wb_triangle(struct c3_isp_device *isp,
> > > +                                       struct c3_isp_param_block_header *block)
> > > +{
> > > +     struct wb_triangle_cfg *wb = (struct wb_triangle_cfg *)block;
> > > +
> > > +     if (!block->enabled)
> > > +             return;
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_CTRL2, AWB_STAT_SATUR_VALID_MASK,
> > > +                        wb->awb_stat_satur_vald);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MIN_MASK,
> > > +                        wb->awb_stat_rg_min);
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MAX_MASK,
> > > +                        wb->awb_stat_rg_max << AWB_STAT_RG_MAX_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MIN_MASK,
> > > +                        wb->awb_stat_bg_min);
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MAX_MASK,
> > > +                        wb->awb_stat_bg_max << AWB_STAT_BG_MAX_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_LOW_MASK,
> > > +                        wb->awb_stat_rg_low);
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_HIGH_MASK,
> > > +                        wb->awb_stat_rg_high << AWB_STAT_RG_HIGH_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_LOW_MASK,
> > > +                        wb->awb_stat_bg_low);
> > > +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_HIGH_MASK,
> > > +                        wb->awb_stat_bg_high << AWB_STAT_BG_HIGH_SHIFT);
> > > +}
> > > +
> > > +static void c3_isp_params_cfg_awb_stats(struct c3_isp_device *isp,
> > > +                                     struct c3_isp_param_block_header *block)
> > > +{
> > > +     struct awb_stats_cfg *awb_stats = (struct awb_stats_cfg *)block;
> > > +     u32 *weight = awb_stats->awb_stat_blk_weight;
> > > +     int idx_base;
> > > +     int group;
> > > +     int i;
> > > +
> > > +     if (!block->enabled)
> > > +             return;
> > > +
> > > +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AWB_STAT_SWITCH_MASK,
> > > +                        awb_stats->awb_stat_switch << TOP_3A_AWB_STAT_SWITCH_SHIFT);
> > > +
> > > +     /* Calculate the group number */
> > > +     group = AWB_BLOCK_WT_NUM / AWB_BLK_WT_DATA_NUM_OF_GROUP;
> > > +
> > > +     /* Set the weight address to 0 position */
> > > +     c3_isp_write(isp, ISP_AWB_BLK_WT_ADDR, 0);
> > > +     for (i = 0; i < group; i++) {
> > you can now use
> >          for (unsigned int i = 0; ...)
> >
> > if 'i' is not needed outside of the loop
>
>
> Will use unsigned int i in for loop.
>
> > > +             idx_base = i * AWB_BLK_WT_DATA_NUM_OF_GROUP;
> > > +             c3_isp_write(isp, ISP_AWB_BLK_WT_DATA,
> > > +                          AWB_BLK_WT_DATA0(weight[idx_base + 0]) |
> > > +                          AWB_BLK_WT_DATA1(weight[idx_base + 1]) |
> > > +                          AWB_BLK_WT_DATA2(weight[idx_base + 2]) |
> > > +                          AWB_BLK_WT_DATA3(weight[idx_base + 3]) |
> > > +                          AWB_BLK_WT_DATA4(weight[idx_base + 4]) |
> > > +                          AWB_BLK_WT_DATA5(weight[idx_base + 5]) |
> > > +                          AWB_BLK_WT_DATA6(weight[idx_base + 6]) |
> > > +                          AWB_BLK_WT_DATA7(weight[idx_base + 7]));
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_params_cfg_ae_stats(struct c3_isp_device *isp,
> > > +                                    struct c3_isp_param_block_header *block)
> > > +{
> > > +     struct ae_stats_cfg *ae_stats = (struct ae_stats_cfg *)block;
> > > +     u32 *weight = ae_stats->ae_stat_blk_weight;
> > > +     int idx_base;
> > > +     int group;
> > > +     int i;
> > > +
> > > +     if (!block->enabled)
> > > +             return;
> > > +
> > > +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AE_STAT_SWITCH_MASK,
> > > +                        ae_stats->ae_stat_switch << TOP_3A_AE_STAT_SWITCH_SHIFT);
> > > +
> > > +     /* Calculate the group number */
> > > +     group = AE_BLOCK_WT_NUM / AE_BLK_WT_DATA_NUM_OF_GROUP;
> > > +
> > > +     /* Set the weight address to 0 position */
> > > +     c3_isp_write(isp, ISP_AE_BLK_WT_ADDR, 0);
> > > +     for (i = 0; i < group; i++) {
> > > +             idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
> > > +             c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
> > > +                          AE_BLK_WT_DATA0(weight[idx_base + 0]) |
> > > +                          AE_BLK_WT_DATA1(weight[idx_base + 1]) |
> > > +                          AE_BLK_WT_DATA2(weight[idx_base + 2]) |
> > > +                          AE_BLK_WT_DATA3(weight[idx_base + 3]) |
> > > +                          AE_BLK_WT_DATA4(weight[idx_base + 4]) |
> > > +                          AE_BLK_WT_DATA5(weight[idx_base + 5]) |
> > > +                          AE_BLK_WT_DATA6(weight[idx_base + 6]) |
> > > +                          AE_BLK_WT_DATA7(weight[idx_base + 7]));
> > > +     }
> > > +
> > > +     /* Write the last weight data */
> > > +     idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
> > > +     c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
> > > +                  AE_BLK_WT_DATA0(weight[idx_base + 0]) |
> > > +                  AE_BLK_WT_DATA1(weight[idx_base + 1]) |
> > > +                  AE_BLK_WT_DATA2(weight[idx_base + 2]) |
> > > +                  AE_BLK_WT_DATA3(weight[idx_base + 3]) |
> > > +                  AE_BLK_WT_DATA4(weight[idx_base + 4]) |
> > > +                  AE_BLK_WT_DATA5(weight[idx_base + 5]) |
> > > +                  AE_BLK_WT_DATA6(weight[idx_base + 6]));
> > > +}
> > > +
> > > +static void c3_isp_params_cfg_af_stats(struct c3_isp_device *isp,
> > > +                                    struct c3_isp_param_block_header *block)
> > > +{
> > > +     struct af_stats_cfg *af_stats = (struct af_stats_cfg *)block;
> > > +
> > > +     if (!block->enabled)
> > > +             return;
> > > +
> > > +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AF_STAT_SWITCH_MASK,
> > > +                        af_stats->af_stat_switch << TOP_3A_AF_STAT_SWITCH_SHIFT);
> > > +}
> > > +
> > > +static void c3_isp_params_cfg_pst_gamma(struct c3_isp_device *isp,
> > > +                                     struct c3_isp_param_block_header *block)
> > > +{
> > > +     struct pst_gamma_cfg *gamma = (struct pst_gamma_cfg *)block;
> > > +     int idx_base;
> > > +     int i, j;
> > > +
> > > +     if (!block->enabled) {
> > > +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, false);
> > > +             return;
> > > +     }
> > > +
> > > +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, TOP_BED_GAMMA_EN);
> > > +
> > > +     for (j = 0; j < GAMMA_LUT_GROUP_NUM; j++) {
> > > +             c3_isp_write(isp, ISP_PST_GAMMA_LUT_ADDR, PST_GAMMA_LUT_ADDR(j));
> > > +
> > > +             /* Calculate the block number */
> > > +             for (i = 0; i < GAMMA_LUT_POINT_NUM / GAMMA_LUT_DATA_NUM_OF_GROUP; i++) {
> > > +                     idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
> > > +                     c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
> > > +                                  PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]) |
> > > +                                  PST_GAMMA_LUT_DATA1(gamma->pst_gamma_lut[j][idx_base + 1]));
> > > +             }
> > > +
> > > +             /* Write the last one lut data of group j */
> > > +             idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
> > > +             c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
> > > +                          PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]));
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_params_cfg_dmsc(struct c3_isp_device *isp,
> > > +                                struct c3_isp_param_block_header *block)
> > > +{
> > > +     if (!block->enabled) {
> > > +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, false);
> > > +             return;
> > > +     }
> > > +
> > > +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, TOP_BED_DMSC_EN);
> > > +}
> > > +
> > > +/* Configure 4 x 3 ccm matrix */
> > > +static void c3_isp_params_cfg_ccm(struct c3_isp_device *isp,
> > > +                               struct c3_isp_param_block_header *block)
> > > +{
> > > +     struct ccm_cfg *ccm = (struct ccm_cfg *)block;
> > > +
> > > +     if (!block->enabled) {
> > > +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, false);
> > > +             return;
> > > +     }
> > > +
> > > +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, TOP_BED_CCM_EN);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_00_MASK,
> > > +                        ccm->ccm_4x3matrix[0][0]);
> > > +     c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_01_MASK,
> > > +                        ccm->ccm_4x3matrix[0][1] << CCM_MTX_01_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_02_MASK,
> > > +                        ccm->ccm_4x3matrix[0][2]);
> > > +     c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_03_MASK,
> > > +                        ccm->ccm_4x3matrix[0][3] << CCM_MTX_03_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_10_MASK,
> > > +                        ccm->ccm_4x3matrix[1][0]);
> > > +     c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_11_MASK,
> > > +                        ccm->ccm_4x3matrix[1][1] << CCM_MTX_11_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_12_MASK,
> > > +                        ccm->ccm_4x3matrix[1][2]);
> > > +     c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_13_MASK,
> > > +                        ccm->ccm_4x3matrix[1][3] << CCM_MTX_13_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_20_MASK,
> > > +                        ccm->ccm_4x3matrix[2][0]);
> > > +     c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_21_MASK,
> > > +                        ccm->ccm_4x3matrix[2][1] << CCM_MTX_21_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_22_MASK,
> > > +                        ccm->ccm_4x3matrix[2][2]);
> > > +     c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_23_MASK,
> > > +                        ccm->ccm_4x3matrix[2][3] << CCM_MTX_23_SHIFT);
> > > +}
> > > +
> > > +/* Configure color space conversion matrix parameters */
> > > +static void c3_isp_params_cfg_csc(struct c3_isp_device *isp,
> > > +                               struct c3_isp_param_block_header *block)
> > > +{
> > > +     struct csc_cfg *csc = (struct csc_cfg *)block;
> > > +
> > > +     if (!block->enabled) {
> > > +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, false);
> > > +             return;
> > > +     }
> > > +
> > > +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, TOP_BED_CM0_EN);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST0_MASK,
> > > +                        csc->cm0_offset_inp[0]);
> > > +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST1_MASK,
> > > +                        csc->cm0_offset_inp[1] << CM0_INP_OFST1_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST2, CM0_INP_OFST2_MASK,
> > > +                        csc->cm0_offset_inp[2]);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_00_MASK,
> > > +                        csc->cm0_3x3matrix[0][0]);
> > > +     c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_01_MASK,
> > > +                        csc->cm0_3x3matrix[0][1] << CM0_MTX_01_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_02_MASK,
> > > +                        csc->cm0_3x3matrix[0][2]);
> > > +     c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_10_MASK,
> > > +                        csc->cm0_3x3matrix[1][0] << CM0_MTX_10_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_11_MASK,
> > > +                        csc->cm0_3x3matrix[1][1]);
> > > +     c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_12_MASK,
> > > +                        csc->cm0_3x3matrix[1][2] << CM0_MTX_12_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_20_MASK,
> > > +                        csc->cm0_3x3matrix[2][0]);
> > > +     c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_21_MASK,
> > > +                        csc->cm0_3x3matrix[2][1] << CM0_MTX_21_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_MTX_22_MASK,
> > > +                        csc->cm0_3x3matrix[2][2]);
> > > +     c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_OFST_OUP0_MASK,
> > > +                        csc->cm0_offset_oup[0] << CM0_OFST_OUP0_SHIFT);
> > > +
> > > +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP1_MASK,
> > > +                        csc->cm0_offset_oup[1]);
> > > +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP2_MASK,
> > > +                        csc->cm0_offset_oup[2] << CM0_OFST_OUP2_SHIFT);
> > > +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_MTX_RS_MASK,
> > > +                        csc->cm0_3x3mtrx_rs << CM0_MTX_RS_SHIFT);
> > > +}
> > > +
> > > +/* Set blc offset of each color channel */
> > > +static void c3_isp_params_cfg_blc(struct c3_isp_device *isp,
> > > +                               struct c3_isp_param_block_header *block)
> > > +{
> > > +     struct blc_cfg *blc = (struct blc_cfg *)block;
> > > +
> > > +     if (!block->enabled) {
> > > +             c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN, false);
> > > +             return;
> > > +     }
> > > +
> > > +     c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN,
> > > +                        TOP_BEO_CTRL_BLC_EN);
> > > +
> > > +     c3_isp_write(isp, ISP_FED_BL_OFST_GR, FED_BL_GR_OFST(blc->fe_bl_ofst[0]));
> > > +     c3_isp_write(isp, ISP_FED_BL_OFST_R, FED_BL_R_OFST(blc->fe_bl_ofst[1]));
> > > +     c3_isp_write(isp, ISP_FED_BL_OFST_B, FED_BL_B_OFST(blc->fe_bl_ofst[2]));
> > > +     c3_isp_write(isp, ISP_FED_BL_OFST_GB, FED_BL_GB_OFST(blc->fe_bl_ofst[3]));
> > > +     c3_isp_write(isp, ISP_FED_BL_OFST_IR, FED_BL_IR_OFST(blc->fe_bl_ofst[4]));
> > > +
> > > +     c3_isp_write(isp, ISP_LSWB_BLC_OFST0,
> > > +                  LSWB_BLC_OFST0(blc->blc_ofst[0]) | LSWB_BLC_OFST1(blc->blc_ofst[1]));
> > > +     c3_isp_write(isp, ISP_LSWB_BLC_OFST1,
> > > +                  LSWB_BLC_OFST2(blc->blc_ofst[2]) | LSWB_BLC_OFST3(blc->blc_ofst[3]));
> > > +     c3_isp_write(isp, ISP_LSWB_BLC_OFST2, LSWB_BLC_OFST4(blc->blc_ofst[4]));
> > > +}
> > > +
> > > +static const struct c3_isp_block_handler c3_isp_block_handlers[] = {
> > > +     [C3_ISP_PARAM_BLOCK_WB_CHANGE] = {
> > > +             .size = sizeof(struct wb_change_cfg),
> > > +             .handler = c3_isp_params_cfg_wb_change,
> > > +     },
> > > +     [C3_ISP_PARAM_BLOCK_WB_LUMA] = {
> > > +             .size = sizeof(struct wb_luma_cfg),
> > > +             .handler = c3_isp_params_cfg_wb_luma,
> > > +     },
> > > +     [C3_ISP_PARAM_BLOCK_WB_TRIANGLE] = {
> > > +             .size = sizeof(struct wb_triangle_cfg),
> > > +             .handler = c3_isp_params_cfg_wb_triangle,
> > > +     },
> > > +     [C3_ISP_PARAM_BLOCK_AWB_STATS] = {
> > > +             .size = sizeof(struct awb_stats_cfg),
> > > +             .handler = c3_isp_params_cfg_awb_stats,
> > > +     },
> > > +     [C3_ISP_PARAM_BLOCK_AE_STATS] = {
> > > +             .size = sizeof(struct ae_stats_cfg),
> > > +             .handler = c3_isp_params_cfg_ae_stats,
> > > +     },
> > > +     [C3_ISP_PARAM_BLOCK_AF_STATS] = {
> > > +             .size = sizeof(struct af_stats_cfg),
> > > +             .handler = c3_isp_params_cfg_af_stats,
> > > +     },
> > > +     [C3_ISP_PARAM_BLOCK_PST_GAMMA] = {
> > > +             .size = sizeof(struct pst_gamma_cfg),
> > > +             .handler = c3_isp_params_cfg_pst_gamma,
> > > +     },
> > > +     [C3_ISP_PARAM_BLOCK_DMSC] = {
> > > +             .size = sizeof(struct dmsc_cfg),
> > > +             .handler = c3_isp_params_cfg_dmsc,
> > > +     },
> > > +     [C3_ISP_PARAM_BLOCK_CCM] = {
> > > +             .size = sizeof(struct ccm_cfg),
> > > +             .handler = c3_isp_params_cfg_ccm,
> > > +     },
> > > +     [C3_ISP_PARAM_BLOCK_CSC] = {
> > > +             .size = sizeof(struct csc_cfg),
> > > +             .handler = c3_isp_params_cfg_csc,
> > > +     },
> > > +     [C3_ISP_PARAM_BLOCK_BLC] = {
> > > +             .size = sizeof(struct blc_cfg),
> > > +             .handler = c3_isp_params_cfg_blc,
> > > +     },
> > > +};
> > > +
> > > +static enum vb2_buffer_state
> > > +c3_isp_params_cfg_blocks(struct c3_isp_params *params)
> > > +{
> > > +     struct c3_isp_params_buffer *config = params->buff->vaddr;
> > > +     enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
> > > +     size_t block_offset = 0;
> > > +     size_t max_offset = 0;
> > > +
> > > +     if (config->total_size > C3_ISP_PARAMS_MAX_SIZE) {
> > > +             dev_dbg(params->isp->dev, "Invalid parameters buffer size %lu\n",
> > > +                     config->total_size);
> > > +             state = VB2_BUF_STATE_ERROR;
> > > +             goto err_return_state;
> > > +     }
> > I suggest to move validation of the parameters buffer to .buf_prepare
> > time.
> >
> > This function is called in irq context, and it's better to do all
> > validation check at buffer queuing time instead.
> >
> > You can have a look at the rkisp1-params.c module, where
> > rkisp1_params_prepare_ext_params does the validation in the
> > .buf_prepare call path
> >
>
> Will refer the rkisp1-params.c module.
>
> > > +
> > > +     /* Ensure config->data has a full struct c3_isp_param_block_header */
> > > +     max_offset = config->total_size - sizeof(struct c3_isp_param_block_header);
> > > +
> > > +     while (block_offset <= max_offset) {
> > > +             const struct c3_isp_block_handler *block_handler;
> > > +             struct c3_isp_param_block_header *block;
> > > +
> > > +             block = (struct c3_isp_param_block_header *)
> > > +                      &config->data[block_offset];
> > > +
> > > +             if (block->type >= C3_ISP_PARAM_BLOCK_SENTINEL) {
> > > +                     dev_dbg(params->isp->dev, "Invalid parameters block type\n");
> > > +                     state = VB2_BUF_STATE_ERROR;
> > > +                     goto err_return_state;
> > > +             }
> > > +
> > > +             block_handler = &c3_isp_block_handlers[block->type];
> > > +             if (block->size != block_handler->size) {
> > > +                     dev_dbg(params->isp->dev, "Invalid parameters block size\n");
> > > +                     state = VB2_BUF_STATE_ERROR;
> > > +                     goto err_return_state;
> > > +             }
> > > +
> > > +             block_handler->handler(params->isp, block);
> > > +
> > > +             block_offset += block->size;
> > > +     }
> > > +
> > > +err_return_state:
> > > +     return state;
> > > +}
> > > +
> > > +/* Initialize ISP pipeline */
> > > +static int c3_isp_params_start(struct c3_isp_params *params)
> > > +{
> > > +     enum vb2_buffer_state state;
> > > +     unsigned long flags;
> > > +
> > > +     /* Reset these controllers */
> > > +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL0, TOP_FEO_CTRL0_ALL_DIS);
> > > +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_0, TOP_FEO_CTRL1_0_ALL_DIS);
> > > +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_1, TOP_FEO_CTRL1_1_ALL_DIS);
> > > +     c3_isp_write(params->isp, ISP_TOP_FED_CTRL, TOP_FED_CTRL_ALL_DIS);
> > > +     c3_isp_write(params->isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_ALL_DIS);
> > > +     c3_isp_write(params->isp, ISP_TOP_BED_CTRL, TOP_BED_CTRL_ALL_DIS);
> > > +
> > > +     spin_lock_irqsave(&params->buff_lock, flags);
> > > +
> > > +     /* Only use the first buffer to initialize ISP */
> > > +     params->buff = list_first_entry_or_null(&params->pending,
> > > +                                             struct c3_isp_vb2_buffer, list);
> > > +     if (!params->buff) {
> > > +             spin_unlock_irqrestore(&params->buff_lock, flags);
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     state = c3_isp_params_cfg_blocks(params);
> > > +
> > > +     spin_unlock_irqrestore(&params->buff_lock, flags);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +/* V4L2 video operations */
> > > +
> > > +static void c3_isp_params_return_buffers(struct c3_isp_params *params,
> > > +                                      enum vb2_buffer_state state)
> > > +{
> > > +     unsigned long flags;
> > > +     struct c3_isp_vb2_buffer *buff;
> > > +
> > > +     spin_lock_irqsave(&params->buff_lock, flags);
> > > +
> > > +     while (!list_empty(&params->pending)) {
> > > +             buff = list_first_entry(&params->pending,
> > > +                                     struct c3_isp_vb2_buffer, list);
> > > +             list_del(&buff->list);
> > > +             vb2_buffer_done(&buff->vb.vb2_buf, state);
> > > +     }
> > > +
> > > +     spin_unlock_irqrestore(&params->buff_lock, flags);
> > > +}
> > > +
> > > +static int c3_isp_params_querycap(struct file *file, void *fh,
> > > +                               struct v4l2_capability *cap)
> > > +{
> > > +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
> > > +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_params_enum_fmt(struct file *file, void *fh,
> > > +                               struct v4l2_fmtdesc *f)
> > > +{
> > > +     if (f->index)
> > > +             return -EINVAL;
> > > +
> > > +     f->pixelformat = V4L2_META_FMT_C3ISP_PARAMS;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_params_g_fmt(struct file *file, void *fh,
> > > +                            struct v4l2_format *f)
> > > +{
> > > +     struct c3_isp_params *params = video_drvdata(file);
> > > +
> > > +     f->fmt.meta = params->vfmt.fmt.meta;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static const struct v4l2_ioctl_ops isp_params_v4l2_ioctl_ops = {
> > > +     .vidioc_querycap                = c3_isp_params_querycap,
> > > +     .vidioc_enum_fmt_meta_out       = c3_isp_params_enum_fmt,
> > > +     .vidioc_g_fmt_meta_out          = c3_isp_params_g_fmt,
> > > +     .vidioc_s_fmt_meta_out          = c3_isp_params_g_fmt,
> > > +     .vidioc_try_fmt_meta_out        = c3_isp_params_g_fmt,
> > > +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
> > > +     .vidioc_querybuf                = vb2_ioctl_querybuf,
> > > +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
> > > +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
> > > +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
> > > +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
> > > +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
> > > +     .vidioc_streamon                = vb2_ioctl_streamon,
> > > +     .vidioc_streamoff               = vb2_ioctl_streamoff,
> > > +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
> > > +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
> > > +};
> > > +
> > > +static const struct v4l2_file_operations isp_params_v4l2_fops = {
> > > +     .open = v4l2_fh_open,
> > > +     .release = vb2_fop_release,
> > > +     .poll = vb2_fop_poll,
> > > +     .unlocked_ioctl = video_ioctl2,
> > > +     .mmap = vb2_fop_mmap,
> > > +};
> > > +
> > > +static int c3_isp_params_vb2_queue_setup(struct vb2_queue *q,
> > > +                                      unsigned int *num_buffers,
> > > +                                      unsigned int *num_planes,
> > > +                                      unsigned int sizes[],
> > > +                                      struct device *alloc_devs[])
> > > +{
> > > +     if (*num_planes) {
> > > +             if (*num_planes != 1)
> > > +                     return -EINVAL;
> > > +
> > > +             if (sizes[0] < sizeof(struct c3_isp_params_buffer))
> > > +                     return -EINVAL;
> > > +     } else {
> > > +             *num_planes = 1;
> > > +             sizes[0] = sizeof(struct c3_isp_params_buffer);
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static void c3_isp_params_vb2_buf_queue(struct vb2_buffer *vb)
> > > +{
> > > +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > > +     struct c3_isp_vb2_buffer *buf =
> > > +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> > > +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
> > > +     unsigned long flags;
> > > +
> > > +     spin_lock_irqsave(&params->buff_lock, flags);
> > > +
> > > +     list_add_tail(&buf->list, &params->pending);
> > > +
> > > +     spin_unlock_irqrestore(&params->buff_lock, flags);
> > > +}
> > > +
> > > +static int c3_isp_params_vb2_buf_prepare(struct vb2_buffer *vb)
> > > +{
> > > +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
> > > +     unsigned int size = params->vfmt.fmt.meta.buffersize;
> > > +
> > > +     if (vb2_plane_size(vb, 0) < size) {
> > How does this work ?
> >
> > 'size' is
> >          params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
> >
> > if I got this right
> >
> > Now, as you're using an extensible parameters implementation,
> > userspace is allowed to submit buffers of a smaller size, with only
> > the "interesting" blocks there.
> >
> > This check instead makes sure that userspace always fill the
> > paramteres buffer with all blocks, am I wrong ?
> >
> > This defeates the purpose of extensible formats, where instead
> > userspace is allowed to only fill the buffers with a subset of the
> > configuration blocks.
> >
> > Have I missed something ?
>
>
> Didn't notice this issue and will check this issue.
>
> >
> > > +             dev_err(params->isp->dev,
> > > +                     "User buffer too small (%ld < %u)\n",
> > > +                     vb2_plane_size(vb, 0), size);
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     vb2_set_plane_payload(vb, 0, size);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_params_vb2_buf_init(struct vb2_buffer *vb)
> > > +{
> > > +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > > +     struct c3_isp_vb2_buffer *buf =
> > > +             container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> > > +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
> > > +
> > > +     buf->vaddr = vb2_plane_vaddr(vb, 0);
> > > +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
> > This is not used
> >
>
> OK, will remove this line.
>
> > > +
> > > +     memset(buf->vaddr, 0, params->vfmt.fmt.meta.buffersize);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_params_vb2_start_streaming(struct vb2_queue *q,
> > > +                                          unsigned int count)
> > > +{
> > > +     struct c3_isp_params *params = vb2_get_drv_priv(q);
> > > +     int ret;
> > > +
> > > +     guard(mutex)(&params->isp->lock);
> > > +
> > > +     ret = pm_runtime_resume_and_get(params->isp->dev);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = video_device_pipeline_start(&params->vdev, &params->isp->pipe);
> > > +     if (ret) {
> > > +             dev_err(params->isp->dev,
> > > +                     "Failed to start params pipeline: %d\n", ret);
> > > +             goto err_pm_put;
> > > +     }
> > > +
> > > +     if (c3_isp_pipeline_ready(params->isp)) {
> > I understand this counts how many video devices have been started...
> >
> Yes.
> > > +             ret = v4l2_subdev_enable_streams(&params->isp->core.sd,
> > > +                                              C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > > +                                              BIT(0));
> > ... but does it need to be called by this module ? it's not like there is a
> > parameters stream to be enabled in the ISP (see also the suggestion to
> > drop routing support from there).
> >
> >
>
> Will remove this function  from here.
>
> > > +             if (ret)
> > > +                     goto err_pipeline_stop;
> > > +     }
> > > +
> > > +     c3_isp_params_start(params);
> > Does this perform initialization and apply the first parameters from a
> > queued buffer ?
> >
> > I'm wondering if it wouldn't be better to:
> >
> > - Implement a start_streaming operation on capture nodes only
> > - Call the ISP's enable_streams
> > - The ISP driver counts how many enabled links to resizers are there
> > - If the number of enable_streams calls matches the number of enabled
> >    links:
> >    - call c3_isp_params_start() and any function that does the stats or
> >      params setup
> >    - actually start the ISP
> >    - propagate the enable_streams call to the downstream subdevs (Adap,
> >      then csi2-rx then sensor)
> >
>
> Will refer your suggestion.
>
> > > +
> > > +     return 0;
> > > +
> > > +err_pipeline_stop:
> > > +     video_device_pipeline_stop(&params->vdev);
> > > +err_pm_put:
> > > +     pm_runtime_put(params->isp->dev);
> > > +     c3_isp_params_return_buffers(params, VB2_BUF_STATE_QUEUED);
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_isp_params_vb2_stop_streaming(struct vb2_queue *q)
> > > +{
> > > +     struct c3_isp_params *params = vb2_get_drv_priv(q);
> > > +
> > > +     guard(mutex)(&params->isp->lock);
> > > +
> > > +     c3_isp_params_return_buffers(params, VB2_BUF_STATE_ERROR);
> > Even if you don't implement start_streaming as I've suggested, you
> > will need this one to return buffers to userspace.
> >
>
> OK.
>
> > > +
> > > +     if (params->isp->pipe.start_count == 1)
> > > +             v4l2_subdev_disable_streams(&params->isp->core.sd,
> > > +                                         C3_ISP_CORE_PAD_SOURCE_VIDEO,
> > > +                                         BIT(0));
> > > +
> > > +     video_device_pipeline_stop(&params->vdev);
> > > +     pm_runtime_put(params->isp->dev);
> > > +}
> > > +
> > > +static const struct vb2_ops isp_params_vb2_ops = {
> > > +     .queue_setup = c3_isp_params_vb2_queue_setup,
> > > +     .buf_queue = c3_isp_params_vb2_buf_queue,
> > > +     .buf_prepare = c3_isp_params_vb2_buf_prepare,
> > > +     .buf_init = c3_isp_params_vb2_buf_init,
> > > +     .wait_prepare = vb2_ops_wait_prepare,
> > > +     .wait_finish = vb2_ops_wait_finish,
> > > +     .start_streaming = c3_isp_params_vb2_start_streaming,
> > > +     .stop_streaming = c3_isp_params_vb2_stop_streaming,
> > > +};
> > > +
> > > +int c3_isp_params_register(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_params *params = &isp->params;
> > > +     struct video_device *vdev = &params->vdev;
> > > +     struct vb2_queue *vb2_q = &params->vb2_q;
> > > +     int ret;
> > > +
> > > +     memset(params, 0, sizeof(*params));
> > > +     params->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_PARAMS;
> > > +     params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
> > > +     params->isp = isp;
> > > +     INIT_LIST_HEAD(&params->pending);
> > > +     spin_lock_init(&params->buff_lock);
> > > +     mutex_init(&params->lock);
> > > +
> > > +     snprintf(vdev->name, sizeof(vdev->name), "isp-params");
> > Here and in all other names, I would prefix them with "c3-"
> >
> > > +     vdev->fops = &isp_params_v4l2_fops;
> > > +     vdev->ioctl_ops = &isp_params_v4l2_ioctl_ops;
> > > +     vdev->v4l2_dev = &isp->v4l2_dev;
> > > +     vdev->lock = &params->lock;
> > > +     vdev->minor = -1;
> > > +     vdev->queue = vb2_q;
> > > +     vdev->release = video_device_release_empty;
> > > +     vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING;
> > > +     vdev->vfl_dir = VFL_DIR_TX;
> > > +     video_set_drvdata(vdev, params);
> > > +
> > > +     vb2_q->drv_priv = params;
> > > +     vb2_q->mem_ops = &vb2_dma_contig_memops;
> > Do you need to use DMA ?
> >
> > This implementation seems to rather receive a buffer of parameters,
> > inspect its content and write registers according to values there.
> >
> > I don't see direct DMA transfers to a memory mapped register area, but
> > instead single writes to registers.
> >
> > Should you use vb2_vmalloc_memops instead ?
> > Make sure to change the headers inclusions and Kconfig dependencies
> > accordingly.
>
>
> Will use vb2_vmalloc_memops instead.
>
> > > +     vb2_q->ops = &isp_params_vb2_ops;
> > > +     vb2_q->type = V4L2_BUF_TYPE_META_OUTPUT;
> > > +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
> > > +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> > > +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
> > > +     vb2_q->dev = isp->dev;
> > > +     vb2_q->lock = &params->lock;
> > > +     vb2_q->min_queued_buffers = 1;
> > > +
> > > +     ret = vb2_queue_init(vb2_q);
> > > +     if (ret)
> > > +             goto err_detroy;
> > > +
> > > +     params->pad.flags = MEDIA_PAD_FL_SOURCE;
> > > +     ret = media_entity_pads_init(&vdev->entity, 1, &params->pad);
> > > +     if (ret)
> > > +             goto err_queue_release;
> > > +
> > > +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> > > +     if (ret < 0) {
> > > +             dev_err(isp->dev,
> > > +                     "Failed to register %s: %d\n", vdev->name, ret);
> > > +             goto err_entity_cleanup;
> > > +     }
> > > +
> > > +     return 0;
> > > +
> > > +err_entity_cleanup:
> > > +     media_entity_cleanup(&vdev->entity);
> > > +err_queue_release:
> > > +     vb2_queue_release(vb2_q);
> > > +err_detroy:
> > > +     mutex_destroy(&params->lock);
> > > +     return ret;
> > > +}
> > > +
> > > +void c3_isp_params_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_params *params = &isp->params;
> > > +
> > > +     vb2_queue_release(&params->vb2_q);
> > > +     media_entity_cleanup(&params->vdev.entity);
> > > +     video_unregister_device(&params->vdev);
> > > +     mutex_destroy(&params->lock);
> > > +}
> > > +
> > > +int c3_isp_params_done(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_params *params = &isp->params;
> > > +     enum vb2_buffer_state state;
> > > +     unsigned long flags;
> > > +
> > > +     spin_lock_irqsave(&params->buff_lock, flags);
> > > +
> > > +     params->buff = list_first_entry_or_null(&params->pending,
> > > +                                             struct c3_isp_vb2_buffer, list);
> > > +     if (!params->buff) {
> > > +             spin_unlock_irqrestore(&params->buff_lock, flags);
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     list_del(&params->buff->list);
> > > +
> > > +     state = c3_isp_params_cfg_blocks(params);
> > > +
> > > +     params->buff->vb.sequence = params->isp->frm_sequence;
> > > +     params->buff->vb.vb2_buf.timestamp = ktime_get();
> > > +     params->buff->vb.field = V4L2_FIELD_NONE;
> > > +     vb2_buffer_done(&params->buff->vb.vb2_buf, state);
> > > +
> > > +     spin_unlock_irqrestore(&params->buff_lock, flags);
> > > +
> > > +     return 0;
> > > +}
> > > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
> > > new file mode 100644
> > > index 000000000000..de1938f7c354
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
> > > @@ -0,0 +1,683 @@
> > > +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#ifndef __C3_ISP_REGS_H__
> > > +#define __C3_ISP_REGS_H__
> > > +
> > > +#define ISP_TOP_INPUT_SIZE                       0x0000
> > > +#define TOP_INPUT_HSIZE(x)                       (((x) & 0xffff) << 16)
> > > +#define TOP_INPUT_VSIZE(x)                       ((x) & 0xffff)
> > > +
> > > +#define ISP_TOP_FRM_SIZE                         0x0004
> > > +#define TOP_FRM_CORE_IHSIZE(x)                   (((x) & 0xffff) << 16)
> > > +#define TOP_FRM_CORE_IVSIZE(x)                   ((x) & 0xffff)
> > > +
> > > +#define ISP_TOP_HOLD_SIZE                        0x0008
> > > +#define TOP_HOLD_HSIZE_MASK                      GENMASK(31, 16)
> > > +#define TOP_HOLD_HSIZE_SHIFT                     16
> > > +
> > > +#define ISP_TOP_PATH_EN                          0x0010
> > > +#define TOP_DISP_EN(x)                           BIT((x))
> > > +#define TOP_WRMIF_EN(x)                          BIT((x) + 8)
> > > +
> > > +#define ISP_TOP_PATH_SEL                         0x0014
> > > +#define TOP_DATA_PATH_MASK                       GENMASK(18, 16)
> > > +#define TOP_PATH_MIPI_TO_CORE                    BIT(16)
> > > +
> > > +#define ISP_TOP_IRQ_EN                           0x0080
> > > +#define TOP_IRQ_FRAME_DONE                       BIT(0)
> > > +#define TOP_IRQ_STATS_ERR                        BIT(5)
> > > +
> > > +#define ISP_TOP_IRQ_CLR                          0x0084
> > > +#define ISP_TOP_IRQ_LINE_THRD                    0x0088
> > > +#define TOP_IRQ_DIN_HSYNC                        BIT(16)
> > > +
> > > +#define ISP_TOP_RO_IRQ_STAT                      0x01c4
> > > +#define ISP_TOP_MODE_CTRL                        0x0400
> > > +#define ISP_TOP_FEO_CTRL0                        0x040c
> > > +#define TOP_FEO_CTRL0_ALL_DIS                    0
> > > +#define TOP_FEO_INPUT_FMT_EN                     BIT(8)
> > > +
> > > +#define ISP_TOP_FEO_CTRL1_0                      0x0410
> > > +#define TOP_FEO_CTRL1_0_ALL_DIS                  0
> > > +
> > > +#define ISP_TOP_FEO_CTRL1_1                      0x0414
> > > +#define TOP_FEO_CTRL1_1_ALL_DIS                  0
> > > +
> > > +#define ISP_TOP_FED_CTRL                         0x0418
> > > +#define TOP_FED_CTRL_ALL_DIS                     0
> > > +
> > > +#define ISP_TOP_BEO_CTRL                         0x041c
> > > +#define TOP_BEO_CTRL_ALL_DIS                     0
> > > +#define TOP_BEO_CTRL_GTM_EN                      BIT(2)
> > > +#define TOP_BEO_CTRL_WB_EN                       BIT(6)
> > > +#define TOP_BEO_CTRL_BLC_EN                      BIT(7)
> > > +#define TOP_BEO_CTRL_IDGAIN_EN                   BIT(8)
> > > +#define TOP_BEO_CTRL_EOTF_EN                     BIT(9)
> > > +
> > > +#define ISP_TOP_BED_CTRL                         0x0420
> > > +#define TOP_BED_CTRL_ALL_DIS                     0
> > > +#define TOP_BED_CM0_EN                           BIT(14)
> > > +#define TOP_BED_GAMMA_EN                         BIT(16)
> > > +#define TOP_BED_CCM_EN                           BIT(18)
> > > +#define TOP_BED_DMSC_EN                          BIT(19)
> > > +
> > > +#define ISP_TOP_3A_STAT_CRTL                     0x0424
> > > +#define TOP_3A_AE_STAT_EN                        BIT(0)
> > > +#define TOP_3A_AWB_STAT_EN                       BIT(1)
> > > +#define TOP_3A_AF_STAT_EN                        BIT(2)
> > > +#define TOP_3A_AWB_STAT_SWITCH_MASK              GENMASK(6, 4)
> > > +#define TOP_3A_AWB_STAT_SWITCH_SHIFT             4
> > > +#define TOP_3A_AWB_STAT_SWITCH_BEFORE_WB         2
> > > +
> > > +#define TOP_3A_AE_STAT_SWITCH_MASK               GENMASK(9, 8)
> > > +#define TOP_3A_AE_STAT_SWITCH_SHIFT              8
> > > +#define TOP_3A_AE_STAT_SWITCH_FROM_MLS           1
> > > +#define TOP_3A_AF_STAT_SWITCH_MASK               GENMASK(13, 12)
> > > +#define TOP_3A_AF_STAT_SWITCH_SHIFT              12
> > > +#define TOP_3A_AF_STAT_SWITCH_FROM_SNR            0
> > > +
> > > +#define ISP_FED_BL_OFST_GR                       0x2018
> > > +#define FED_BL_GR_OFST(x)                        ((x) & 0x1fffff)
> > > +
> > > +#define ISP_FED_BL_OFST_R                        0x201c
> > > +#define FED_BL_R_OFST(x)                         ((x) & 0x1fffff)
> > > +
> > > +#define ISP_FED_BL_OFST_B                        0x2020
> > > +#define FED_BL_B_OFST(x)                         ((x) & 0x1fffff)
> > > +
> > > +#define ISP_FED_BL_OFST_GB                       0x2024
> > > +#define FED_BL_GB_OFST(x)                        ((x) & 0x1fffff)
> > > +
> > > +#define ISP_FED_BL_OFST_IR                       0x2028
> > > +#define FED_BL_IR_OFST(x)                        ((x) & 0x1fffff)
> > > +
> > > +#define ISP_LSWB_BLC_OFST0                       0x4028
> > > +#define LSWB_BLC_OFST1_MASK                      GENMASK(15, 0)
> > > +#define LSWB_BLC_OFST1(x)                        ((x) & 0xffff)
> > > +#define LSWB_BLC_OFST1_EXT(x)                    ((x) & 0xffff)
> > > +#define LSWB_BLC_OFST0_MASK                      GENMASK(31, 16)
> > > +#define LSWB_BLC_OFST0(x)                        (((x) & 0xffff) << 16)
> > > +#define LSWB_BLC_OFST0_EXT(x)                    (((x) >> 16) & 0xffff)
> > > +
> > > +#define ISP_LSWB_BLC_OFST1                       0x402c
> > > +#define LSWB_BLC_OFST3_MASK                      GENMASK(15, 0)
> > > +#define LSWB_BLC_OFST3(x)                        ((x) & 0xffff)
> > > +#define LSWB_BLC_OFST3_EXT(x)                    ((x) & 0xffff)
> > > +#define LSWB_BLC_OFST2_MASK                      GENMASK(31, 16)
> > > +#define LSWB_BLC_OFST2(x)                        (((x) & 0xffff) << 16)
> > > +#define LSWB_BLC_OFST2_EXT(x)                    (((x) >> 16) & 0xffff)
> > > +
> > > +#define ISP_LSWB_BLC_OFST2                       0x4030
> > > +#define LSWB_BLC_OFST4_MASK                      GENMASK(15, 0)
> > > +#define LSWB_BLC_OFST4(x)                        ((x) & 0xffff)
> > > +#define LSWB_BLC_OFST4_EXT(x)                    ((x) & 0xffff)
> > > +
> > > +#define ISP_LSWB_BLC_PHSOFST                     0x4034
> > > +#define LSWB_BLC_YPHS_OFST_MASK                  GENMASK(1, 0)
> > > +#define LSWB_BLC_XPHS_OFST_MASK                  GENMASK(3, 2)
> > > +#define LSWB_BLC_XPHS_OFST_SHIFT                 2
> > > +
> > > +#define ISP_LSWB_WB_GAIN0                        0x4038
> > > +#define LSWB_WB_GAIN1_MASK                       GENMASK(11, 0)
> > > +#define LSWB_WB_GAIN0_MASK                       GENMASK(27, 16)
> > > +#define LSWB_WB_GAIN0_SHIFT                      16
> > > +
> > > +#define ISP_LSWB_WB_GAIN1                        0x403c
> > > +#define LSWB_WB_GAIN3_MASK                       GENMASK(11, 0)
> > > +#define LSWB_WB_GAIN2_MASK                       GENMASK(27, 16)
> > > +#define LSWB_WB_GAIN2_SHIFT                      16
> > > +
> > > +#define ISP_LSWB_WB_GAIN2                        0x4040
> > > +#define LSWB_WB_GAIN4_MASK                       GENMASK(11, 0)
> > > +
> > > +#define ISP_LSWB_WB_LIMIT0                       0x4044
> > > +#define LSWB_WB_LIMIT1(x)                        ((x) & 0xffff)
> > > +#define LSWB_WB_LIMIT0(x)                        (((x) & 0xffff) << 16)
> > > +
> > > +#define ISP_LSWB_WB_LIMIT1                       0x4048
> > > +#define LSWB_WB_LIMIT3(x)                        ((x) & 0xffff)
> > > +#define LSWB_WB_LIMIT2(x)                        (((x) & 0xffff) << 16)
> > > +
> > > +#define ISP_LSWB_WB_LIMIT2                       0x404c
> > > +#define LSWB_WB_LIMIT4_MASK                      GENMASK(15, 0)
> > > +
> > > +#define ISP_LSWB_WB_PHSOFST                      0x4050
> > > +#define LSWB_WB_YPHS_OFST_MASK                   GENMASK(1, 0)
> > > +#define LSWB_WB_XPHS_OFST_MASK                   GENMASK(3, 2)
> > > +#define LSWB_WB_XPHS_OFST_SHIFT                  2
> > > +
> > > +#define ISP_LSWB_LNS_PHSOFST                     0x4054
> > > +#define LSWB_LNS_YPHS_OFST_MASK                  GENMASK(1, 0)
> > > +#define LSWB_LNS_XPHS_OFST_MASK                  GENMASK(3, 2)
> > > +#define LSWB_LNS_XPHS_OFST_SHIFT                 2
> > > +
> > > +#define ISP_DMS_COMMON_PARAM0                    0x5000
> > > +#define DMS_COMMON_YPHS_OFST_MASK                GENMASK(1, 0)
> > > +#define DMS_COMMON_XPHS_OFST_MASK                GENMASK(3, 2)
> > > +#define DMS_COMMON_XPHS_OFST_SHIFT               2
> > > +
> > > +#define ISP_CM0_INP_OFST01                       0x6040
> > > +#define CM0_INP_OFST0_MASK                       GENMASK(12, 0)
> > > +#define CM0_INP_OFST1_MASK                       GENMASK(28, 16)
> > > +#define CM0_INP_OFST1_SHIFT                      16
> > > +
> > > +#define ISP_CM0_INP_OFST2                        0x6044
> > > +#define CM0_INP_OFST2_MASK                       GENMASK(12, 0)
> > > +
> > > +#define ISP_CM0_COEF00_01                        0x6048
> > > +#define CM0_MTX_00_MASK                          GENMASK(12, 0)
> > > +#define CM0_MTX_01_MASK                          GENMASK(28, 16)
> > > +#define CM0_MTX_01_SHIFT                         16
> > > +
> > > +#define ISP_CM0_COEF02_10                        0x604c
> > > +#define CM0_MTX_02_MASK                          GENMASK(12, 0)
> > > +#define CM0_MTX_10_MASK                          GENMASK(28, 16)
> > > +#define CM0_MTX_10_SHIFT                         16
> > > +
> > > +#define ISP_CM0_COEF11_12                        0x6050
> > > +#define CM0_MTX_11_MASK                          GENMASK(12, 0)
> > > +#define CM0_MTX_12_MASK                          GENMASK(28, 16)
> > > +#define CM0_MTX_12_SHIFT                         16
> > > +
> > > +#define ISP_CM0_COEF20_21                        0x6054
> > > +#define CM0_MTX_20_MASK                          GENMASK(12, 0)
> > > +#define CM0_MTX_21_MASK                          GENMASK(28, 16)
> > > +#define CM0_MTX_21_SHIFT                         16
> > > +
> > > +#define ISP_CM0_COEF22_OUP_OFST0                 0x6058
> > > +#define CM0_MTX_22_MASK                          GENMASK(12, 0)
> > > +#define CM0_OFST_OUP0_MASK                       GENMASK(28, 16)
> > > +#define CM0_OFST_OUP0_SHIFT                      16
> > > +
> > > +#define ISP_CM0_OUP_OFST12_RS                    0x605c
> > > +#define CM0_OFST_OUP1_MASK                       GENMASK(12, 0)
> > > +#define CM0_OFST_OUP2_MASK                       GENMASK(28, 16)
> > > +#define CM0_OFST_OUP2_SHIFT                      16
> > > +#define CM0_MTX_RS_MASK                          GENMASK(31, 30)
> > > +#define CM0_MTX_RS_SHIFT                         30
> > > +
> > > +#define ISP_CCM_MTX_00_01                        0x6098
> > > +#define CCM_MTX_00_MASK                          GENMASK(12, 0)
> > > +#define CCM_MTX_01_MASK                          GENMASK(28, 16)
> > > +#define CCM_MTX_01_SHIFT                         16
> > > +
> > > +#define ISP_CCM_MTX_02_03                        0x609c
> > > +#define CCM_MTX_02_MASK                          GENMASK(12, 0)
> > > +#define CCM_MTX_03_MASK                          GENMASK(28, 16)
> > > +#define CCM_MTX_03_SHIFT                         16
> > > +
> > > +#define ISP_CCM_MTX_10_11                        0x60A0
> > > +#define CCM_MTX_10_MASK                          GENMASK(12, 0)
> > > +#define CCM_MTX_11_MASK                          GENMASK(28, 16)
> > > +#define CCM_MTX_11_SHIFT                         16
> > > +
> > > +#define ISP_CCM_MTX_12_13                        0x60A4
> > > +#define CCM_MTX_12_MASK                          GENMASK(12, 0)
> > > +#define CCM_MTX_13_MASK                          GENMASK(28, 16)
> > > +#define CCM_MTX_13_SHIFT                         16
> > > +
> > > +#define ISP_CCM_MTX_20_21                        0x60A8
> > > +#define CCM_MTX_20_MASK                          GENMASK(12, 0)
> > > +#define CCM_MTX_21_MASK                          GENMASK(28, 16)
> > > +#define CCM_MTX_21_SHIFT                         16
> > > +
> > > +#define ISP_CCM_MTX_22_23_RS                     0x60Ac
> > > +#define CCM_MTX_22_MASK                          GENMASK(12, 0)
> > > +#define CCM_MTX_23_MASK                          GENMASK(28, 16)
> > > +#define CCM_MTX_23_SHIFT                         16
> > > +
> > > +#define ISP_PST_GAMMA_MODE                       0x60C0
> > > +#define PST_GAMMA_MODE                           BIT(0)
> > > +#define PST_GAMMA_MODE_EXT(x)                    ((x) & 0x1)
> > > +
> > > +#define ISP_PST_GAMMA_LUT_ADDR                   0x60cc
> > > +#define PST_GAMMA_LUT_ADDR(x)                    ((x) << 7)
> > > +
> > > +#define ISP_PST_GAMMA_LUT_DATA                   0x60d0
> > > +#define PST_GAMMA_LUT_DATA0(x)                   ((x) & 0xffff)
> > > +#define PST_GAMMA_LUT_DATA1(x)                   (((x) & 0xffff) << 16)
> > > +
> > > +#define DISP0_TOP_TOP_CTRL                       0x8000
> > > +#define DISP_CRP2_EN                             BIT(5)
> > > +
> > > +#define DISP0_TOP_CRP2_START                     0x8004
> > > +#define DISP_CRP2_VSTART_MASK                    GENMASK(15, 0)
> > > +#define DISP_CRP2_VSTART(x)                      ((x) & 0xffff)
> > > +#define DISP_CRP2_HSTART_MASK                    GENMASK(31, 16)
> > > +#define DISP_CRP2_HSTART(x)                      (((x) & 0xffff) << 16)
> > > +
> > > +#define DISP0_TOP_CRP2_SIZE                      0x8008
> > > +#define DISP_CRP2_VSIZE_MASK                     GENMASK(15, 0)
> > > +#define DISP_CRP2_VSIZE(x)                       ((x) & 0xffff)
> > > +#define DISP_CRP2_HSIZE_MASK                     GENMASK(31, 16)
> > > +#define DISP_CRP2_HSIZE(x)                       (((x) & 0xffff) << 16)
> > > +
> > > +#define DISP0_TOP_OUT_SIZE                       0x800c
> > > +#define DISP_OUT_VSIZE_MASK                      GENMASK(12, 0)
> > > +#define DISP_OUT_HSIZE_MASK                      GENMASK(28, 16)
> > > +#define DISP_OUT_HSIZE_SHIFT                     16
> > > +
> > > +#define ISP_DISP0_TOP_IN_SIZE                    0x804c
> > > +#define DISP_TOP_IN_VSIZE(x)                     ((x) & 0x1fff)
> > > +#define DISP_TOP_IN_HSIZE(x)                     (((x) & 0x1fff) << 16)
> > > +
> > > +#define DISP0_PPS_SCALE_EN                       0x8200
> > > +#define PPS_VSC_TAP_NUM_MASK                     GENMASK(3, 0)
> > > +#define PPS_HSC_TAP_NUM_MASK                     GENMASK(7, 4)
> > > +#define PPS_HSC_TAP_NUM_SHIFT                    4
> > > +#define PPS_HSC_TAP_NUM_INIT                     4
> > > +#define PPS_PREVSC_FLT_NUM_MASK                  GENMASK(11, 8)
> > > +#define PPS_PREVSC_FLT_NUM_SHIFT                 8
> > > +#define PPS_PREHSC_FLT_NUM_MASK                  GENMASK(15, 12)
> > > +#define PPS_PREHSC_FLT_NUM_SHIFT                 12
> > > +#define PPS_PREHSC_FLT_NUM_INIT                  8
> > > +#define PPS_PREVSC_RATE_MASK                     GENMASK(17, 16)
> > > +#define PPS_PREVSC_RATE_SHIFT                    16
> > > +#define PPS_PREHSC_RATE_MASK                     GENMASK(19, 18)
> > > +#define PPS_PREHSC_RATE_SHIFT                    18
> > > +#define PPS_HSC_EN_MASK                          BIT(20)
> > > +#define PPS_HSC_EN_SHIFT                         20
> > > +#define PPS_VSC_EN_MASK                          BIT(21)
> > > +#define PPS_VSC_EN_SHIFT                         21
> > > +#define PPS_PREVSC_EN_MASK                       BIT(22)
> > > +#define PPS_PREVSC_EN_SHIFT                      22
> > > +#define PPS_PREHSC_EN_MASK                       BIT(23)
> > > +#define PPS_PREHSC_EN_SHIFT                      23
> > > +#define PPS_HSC_NOR_RS_BITS_MASK                 GENMASK(27, 24)
> > > +#define PPS_HSC_NOR_RS_BITS_SHIFT                24
> > > +#define PPS_HSC_NOR_RS_BITS_INIT                 9
> > > +#define PPS_VSC_NOR_RS_BITS_MASK                 GENMASK(31, 28)
> > > +#define PPS_VSC_NOR_RS_BITS_SHIFT                28
> > > +#define PPS_VSC_NOR_RS_BITS_INIT                 9
> > > +
> > > +#define DISP0_PPS_PRE_HSCALE_COEF_0              0x8204
> > > +#define PPS_PREHSC_LUMA_COEF0_MASK               GENMASK(9, 0)
> > > +#define PPS_PREHSC_LUMA_COEF0_INIT               128
> > > +#define PPS_PREHSC_LUMA_COEF1_MASK               GENMASK(25, 16)
> > > +#define PPS_PREHSC_LUMA_COEF1_SHIFT              16
> > > +#define PPS_PREHSC_LUMA_COEF1_INIT               128
> > > +
> > > +#define DISP0_PPS_PRE_HSCALE_COEF_1              0x8208
> > > +#define PPS_PREHSC_LUMA_COEF2_MASK               GENMASK(9, 0)
> > > +#define PPS_PREHSC_LUMA_COEF2_INIT               32
> > > +#define PPS_PREHSC_LUMA_COEF3_MASK               GENMASK(25, 16)
> > > +#define PPS_PREHSC_LUMA_COEF3_SHIFT              16
> > > +#define PPS_PREHSC_LUMA_COEF3_INIT               32
> > > +
> > > +#define DISP0_PPS_VSC_START_PHASE_STEP           0x8224
> > > +#define PPS_VSC_FRACTION_PART_MASK               GENMASK(23, 0)
> > > +#define PPS_VSC_INTEGER_PART_MASK                GENMASK(27, 24)
> > > +#define PPS_VSC_INTEGER_PART_SHIFT               24
> > > +
> > > +#define DISP0_PPS_HSC_START_PHASE_STEP           0x8230
> > > +#define PPS_HSC_FRACTION_PART_MASK               GENMASK(23, 0)
> > > +#define PPS_HSC_INTEGER_PART_MASK                GENMASK(27, 24)
> > > +#define PPS_HSC_INTEGER_PART_SHIFT               24
> > > +
> > > +#define DISP0_PPS_444TO422                       0x823c
> > > +#define PPS_444TO422_EN_MASK                     BIT(0)
> > > +
> > > +#define ISP_SCALE0_COEF_IDX_LUMA                 0x8240
> > > +#define SCALE_LUMA_COEF_S11_MODE                 BIT(9)
> > > +#define SCALE_LUMA_CTYPE(x)                      (((x) & 0x7) << 10)
> > > +
> > > +#define ISP_SCALE0_COEF_LUMA                     0x8244
> > > +#define SCALE_COEF_LUMA_DATA1(x)                 ((x) & 0x7ff)
> > > +#define SCALE_COEF_LUMA_DATA0(x)                 (((x) & 0x7ff) << 16)
> > > +
> > > +#define ISP_SCALE0_COEF_IDX_CHRO                 0x8248
> > > +#define SCALE_CHRO_COEF_S11_MODE                 BIT(9)
> > > +#define SCALE_CHRO_CTYPE(x)                      (((x) & 0x7) << 10)
> > > +
> > > +#define ISP_SCALE0_COEF_CHRO                     0x824c
> > > +#define SCALE_COEF_CHRO_DATA1(x)                 ((x) & 0x7ff)
> > > +#define SCALE_COEF_CHRO_DATA0(x)                 (((x) & 0x7ff) << 16)
> > > +
> > > +#define ISP_AF_ROI0_WIN01                        0xa00c
> > > +#define AF_ROI_XYWIN_00_INIT                     (50 & 0xffff)
> > > +#define AF_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
> > > +
> > > +#define ISP_AF_ROI1_WIN01                        0xa010
> > > +#define AF_ROI_XYWIN_10_INIT                     (100 & 0xffff)
> > > +#define AF_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
> > > +
> > > +#define ISP_AF_ROI0_WIN23                        0xa014
> > > +#define AF_ROI_XYWIN_02_INIT                     (80 & 0xffff)
> > > +#define AF_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
> > > +
> > > +#define ISP_AF_ROI1_WIN23                        0xa018
> > > +#define AF_ROI_XYWIN_12_INIT                     (80 & 0xffff)
> > > +#define AF_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
> > > +
> > > +#define ISP_AF_CTRL                              0xa044
> > > +#define AF_CTRL_YPHS_OFST_MASK                   GENMASK(15, 14)
> > > +#define AF_CTRL_YPHS_OFST_SHIFT                  14
> > > +#define AF_CTRL_XPHS_OFST_MASK                   GENMASK(17, 16)
> > > +#define AF_CTRL_XPHS_OFST_SHIFT                  16
> > > +
> > > +#define ISP_AF_HV_SIZE                           0xa04c
> > > +#define AF_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
> > > +#define AF_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
> > > +
> > > +#define ISP_AF_HV_BLKNUM                         0xa050
> > > +#define AF_HV_STAT_VBLK_NUM_MASK                 GENMASK(5, 0)
> > > +#define AF_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x3f)
> > > +#define AF_HV_STAT_HBLK_NUM_MASK                 GENMASK(21, 16)
> > > +#define AF_HV_STAT_HBLK_NUM_SHIFT                16
> > > +
> > > +#define ISP_AF_EN_CTRL                           0xa054
> > > +#define AF_STAT_SELECT                           BIT(21)
> > > +#define AF_STAT_SELECT_SHIFT                     21
> > > +
> > > +#define ISP_RO_AF_GLB_STAT_PCK0                  0xa0e0
> > > +#define ISP_RO_AF_GLB_STAT_PCK1                  0xa0e4
> > > +#define ISP_RO_AF_GLB_STAT_PCK2                  0xa0e8
> > > +#define ISP_RO_AF_GLB_STAT_PCK3                  0xa0eC
> > > +#define ISP_AF_IDX_ADDR                          0xa1c0
> > > +#define ISP_AF_IDX_DATA                          0xa1c4
> > > +#define AF_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
> > > +#define AF_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
> > > +
> > > +#define ISP_AE_ROI0_WIN01                        0xa40c
> > > +#define AE_ROI_XYWIN_00_INIT                     (50 & 0xffff)
> > > +#define AE_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
> > > +
> > > +#define ISP_AE_ROI1_WIN01                        0xa410
> > > +#define AE_ROI_XYWIN_10_INIT                     (100 & 0xffff)
> > > +#define AE_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
> > > +
> > > +#define ISP_AE_ROI0_WIN23                        0xa414
> > > +#define AE_ROI_XYWIN_02_INIT                     (80 & 0xffff)
> > > +#define AE_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
> > > +
> > > +#define ISP_AE_ROI1_WIN23                        0xa418
> > > +#define AE_ROI_XYWIN_12_INIT                     (80 & 0xffff)
> > > +#define AE_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
> > > +
> > > +#define ISP_RO_AE_ROI_STAT_PCK0_0                0xa424
> > > +#define ISP_RO_AE_ROI_STAT_PCK1_0                0xa428
> > > +#define ISP_RO_AE_ROI_STAT_PCK0_1                0xa42c
> > > +#define ISP_RO_AE_ROI_STAT_PCK1_1                0xa430
> > > +#define ISP_AE_CTRL                              0xa448
> > > +#define AE_CTRL_AE_STAT_LOCAL_MODE               GENMASK(3, 2)
> > > +#define AE_CTRL_AE_STAT_LOCAL_MODE_EXT(x)        (((x) >> 2) & 0x3)
> > > +#define AE_CTRL_INPUT_2LINE_TOGETHER             BIT(7)
> > > +#define AE_CTRL_LUMA_MODE_MASK                   GENMASK(9, 8)
> > > +#define AE_CTRL_LUMA_MODE_SHIFT                  8
> > > +#define AE_CTRL_LUMA_MODE_FILTER                 2
> > > +#define AE_CTRL_YPHS_OFST_MASK                   GENMASK(25, 24)
> > > +#define AE_CTRL_YPHS_OFST_SHIFT                  24
> > > +#define AE_CTRL_XPHS_OFST_MASK                   GENMASK(27, 26)
> > > +#define AE_CTRL_XPHS_OFST_SHIFT                  26
> > > +
> > > +#define ISP_AE_CRTL2_0                           0xa44c
> > > +#define AE_CRTL2_BL12_GRBGI0_MASK                GENMASK(11, 0)
> > > +#define AE_CRTL2_GAIN_GRBGI0_MASK                GENMASK(23, 16)
> > > +#define AE_CRTL2_GAIN_GRBGI0_SHIFT               16
> > > +
> > > +#define ISP_AE_CRTL2_1                           0xa450
> > > +#define AE_CRTL2_BL12_GRBGI1_MASK                GENMASK(11, 0)
> > > +#define AE_CRTL2_GAIN_GRBGI1_MASK                GENMASK(23, 16)
> > > +#define AE_CRTL2_GAIN_GRBGI1_SHIFT               16
> > > +
> > > +#define ISP_AE_CRTL2_2                           0xa454
> > > +#define AE_CRTL2_BL12_GRBGI2_MASK                GENMASK(11, 0)
> > > +#define AE_CRTL2_GAIN_GRBGI2_MASK                GENMASK(23, 16)
> > > +#define AE_CRTL2_GAIN_GRBGI2_SHIFT               16
> > > +
> > > +#define ISP_AE_CRTL2_3                           0xa458
> > > +#define AE_CRTL2_BL12_GRBGI3_MASK                GENMASK(11, 0)
> > > +#define AE_CRTL2_GAIN_GRBGI3_MASK                GENMASK(23, 16)
> > > +#define AE_CRTL2_GAIN_GRBGI3_SHIFT               16
> > > +
> > > +#define ISP_AE_CRTL2_4                           0xa45C
> > > +#define AE_CRTL2_BL12_GRBGI4_MASK                GENMASK(11, 0)
> > > +#define AE_CRTL2_GAIN_GRBGI4_MASK                GENMASK(23, 16)
> > > +#define AE_CRTL2_GAIN_GRBGI4_SHIFT               16
> > > +
> > > +#define ISP_AE_HV_SIZE                           0xa464
> > > +#define AE_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
> > > +#define AE_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
> > > +
> > > +#define ISP_AE_HV_BLKNUM                         0xa468
> > > +#define AE_HV_STAT_VBLK_NUM_MASK                 GENMASK(6, 0)
> > > +#define AE_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x7f)
> > > +#define AE_HV_STAT_HBLK_NUM_MASK                 GENMASK(22, 16)
> > > +#define AE_HV_STAT_HBLK_NUM_SHIFT                16
> > > +
> > > +#define ISP_AE_STAT_THD01                        0xa46c
> > > +#define AE_STAT_THRD0_INIT                       (13107 & 0xffff)
> > > +#define AE_STAT_THRD1_INIT                       ((26214 & 0xffff) << 16)
> > > +
> > > +#define ISP_AE_STAT_THD23                        0xa470
> > > +#define AE_STAT_THRD2_INIT                       (39321 & 0xffff)
> > > +#define AE_STAT_THRD3_INIT                       ((52428 & 0xffff) << 16)
> > > +
> > > +#define ISP_RO_AE_STAT_GPNUM                     0xa49c
> > > +#define ISP_AE_IDX_ADDR                          0xa600
> > > +#define ISP_AE_IDX_DATA                          0xa604
> > > +#define AE_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
> > > +#define AE_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
> > > +
> > > +#define ISP_AE_BLK_WT_ADDR                       0xa608
> > > +#define ISP_AE_BLK_WT_DATA                       0xa60c
> > > +#define AE_BLK_WT_DATA0(x)                       ((x) & 0xf)
> > > +#define AE_BLK_WT_DATA1(x)                       (((x) & 0xf) << 4)
> > > +#define AE_BLK_WT_DATA2(x)                       (((x) & 0xf) << 8)
> > > +#define AE_BLK_WT_DATA3(x)                       (((x) & 0xf) << 12)
> > > +#define AE_BLK_WT_DATA4(x)                       (((x) & 0xf) << 16)
> > > +#define AE_BLK_WT_DATA5(x)                       (((x) & 0xf) << 20)
> > > +#define AE_BLK_WT_DATA6(x)                       (((x) & 0xf) << 24)
> > > +#define AE_BLK_WT_DATA7(x)                       (((x) & 0xf) << 28)
> > > +
> > > +#define ISP_AWB_CTRL                             0xa834
> > > +#define AWB_CTRL_YPHS_OFST_MASK                  GENMASK(1, 0)
> > > +#define AWB_CTRL_XPHS_OFST_MASK                  GENMASK(3, 2)
> > > +#define AWB_CTRL_XPHS_OFST_SHIFT                 2
> > > +#define AWB_CTRL_STAT_RATIO_MODE                 BIT(4)
> > > +
> > > +#define ISP_AWB_HV_SIZE                          0xa83c
> > > +#define AWB_HV_STAT_VSIZE(x)                     ((x) & 0xffff)
> > > +#define AWB_HV_STAT_HSIZE(x)                     (((x) & 0xffff) << 16)
> > > +
> > > +#define ISP_AWB_STAT_ROI0_WIN01                  0xa80c
> > > +#define AWB_ROI_XYWIN_00_INIT                    (32 & 0xffff)
> > > +#define AWB_ROI_XYWIN_01_INIT                    ((62 & 0xffff) << 16)
> > > +
> > > +#define ISP_AWB_STAT_ROI1_WIN01                  0xa810
> > > +#define AWB_ROI_XYWIN_10_INIT                    (32 & 0xffff)
> > > +#define AWB_ROI_XYWIN_11_INIT                    ((82 & 0xffff) << 16)
> > > +
> > > +#define ISP_AWB_STAT_ROI0_WIN23                  0xa814
> > > +#define AWB_ROI_XYWIN_02_INIT                    (32 & 0xffff)
> > > +#define AWB_ROI_XYWIN_03_INIT                    ((42 & 0xffff) << 16)
> > > +
> > > +#define ISP_AWB_STAT_ROI1_WIN23                  0xa818
> > > +#define AWB_ROI_XYWIN_12_INIT                    (64 & 0xffff)
> > > +#define AWB_ROI_XYWIN_13_INIT                    ((52 & 0xffff) << 16)
> > > +
> > > +#define ISP_AWB_HV_BLKNUM                        0xa840
> > > +#define AWB_HV_STAT_VBLK_NUM_MASK                GENMASK(5, 0)
> > > +#define AWB_HV_STAT_VBLK_NUM_EXT(x)              ((x) & 0x3f)
> > > +#define AWB_HV_STAT_HBLK_NUM_MASK                GENMASK(21, 16)
> > > +#define AWB_HV_STAT_HBLK_NUM_SHIFT               16
> > > +
> > > +#define ISP_AWB_STAT_RG                          0xa848
> > > +#define AWB_STAT_RG_MIN_MASK                     GENMASK(11, 0)
> > > +#define AWB_STAT_RG_MAX_MASK                     GENMASK(27, 16)
> > > +#define AWB_STAT_RG_MAX_SHIFT                    16
> > > +
> > > +#define ISP_AWB_STAT_BG                          0xa84c
> > > +#define AWB_STAT_BG_MIN_MASK                     GENMASK(11, 0)
> > > +#define AWB_STAT_BG_MAX_MASK                     GENMASK(27, 16)
> > > +#define AWB_STAT_BG_MAX_SHIFT                    16
> > > +
> > > +#define ISP_AWB_STAT_RG_HL                       0xa850
> > > +#define AWB_STAT_RG_LOW_MASK                     GENMASK(11, 0)
> > > +#define AWB_STAT_RG_HIGH_MASK                    GENMASK(27, 16)
> > > +#define AWB_STAT_RG_HIGH_SHIFT                   16
> > > +
> > > +#define ISP_AWB_STAT_BG_HL                       0xa854
> > > +#define AWB_STAT_BG_LOW_MASK                     GENMASK(11, 0)
> > > +#define AWB_STAT_BG_HIGH_MASK                    GENMASK(27, 16)
> > > +#define AWB_STAT_BG_HIGH_SHIFT                   16
> > > +
> > > +#define ISP_AWB_STAT_CTRL2                       0xa858
> > > +#define AWB_STAT_SATUR_VALID_MASK                BIT(0)
> > > +#define AWB_STAT_LOCAL_MODE                      BIT(2)
> > > +#define AWB_STAT_LOCAL_MODE_EXT(x)               (((x) >> 2) & 0x1)
> > > +#define AWB_STAT_LUMA_DIV_MODE                   GENMASK(4, 3)
> > > +#define AWB_STAT_LUMA_DIV_MODE_EXT(x)            (((x) >> 3) & 0x3)
> > > +
> > > +#define ISP_AWB_STAT_BLC20_0                     0xa85c
> > > +#define AWB_STAT_BLC20_GR_MASK                   GENMASK(19, 0)
> > > +
> > > +#define ISP_AWB_STAT_BLC20_1                     0xa860
> > > +#define AWB_STAT_BLC20_R_MASK                    GENMASK(19, 0)
> > > +
> > > +#define ISP_AWB_STAT_BLC20_2                     0xa864
> > > +#define AWB_STAT_BLC20_B_MASK                    GENMASK(19, 0)
> > > +
> > > +#define ISP_AWB_STAT_BLC20_3                     0xa868
> > > +#define AWB_STAT_BLC20_GB_MASK                   GENMASK(19, 0)
> > > +
> > > +#define ISP_AWB_STAT_GAIN10_0                    0xa86c
> > > +#define AWB_STAT_GAIN10_GR_MASK                  GENMASK(9, 0)
> > > +
> > > +#define ISP_AWB_STAT_GAIN10_1                    0xa870
> > > +#define AWB_STAT_GAIN10_R_MASK                   GENMASK(9, 0)
> > > +
> > > +#define ISP_AWB_STAT_GAIN10_2                    0xa874
> > > +#define AWB_STAT_GAIN10_B_MASK                   GENMASK(9, 0)
> > > +
> > > +#define ISP_AWB_STAT_GAIN10_3                    0xa878
> > > +#define AWB_STAT_GAIN10_GB_MASK                  GENMASK(9, 0)
> > > +
> > > +#define ISP_AWB_STAT_SATUR_CTRL                  0xa884
> > > +#define AWB_STAT_SATUR_LOW(x)                    ((x) & 0xffff)
> > > +#define AWB_STAT_SATUR_HIGH(x)                   (((x) & 0xffff) << 16)
> > > +
> > > +#define ISP_AWB_IDX_ADDR                         0xaa00
> > > +#define ISP_AWB_IDX_DATA                         0xaa04
> > > +#define AWB_IDX_VIDX_DATA(x)                     ((x) & 0xffff)
> > > +#define AWB_IDX_HIDX_DATA(x)                     (((x) & 0xffff) << 16)
> > > +
> > > +#define ISP_AWB_BLK_WT_ADDR                      0xaa08
> > > +#define ISP_AWB_BLK_WT_DATA                      0xaa0c
> > > +#define AWB_BLK_WT_DATA0(x)                      ((x) & 0xf)
> > > +#define AWB_BLK_WT_DATA1(x)                      (((x) & 0xf) << 4)
> > > +#define AWB_BLK_WT_DATA2(x)                      (((x) & 0xf) << 8)
> > > +#define AWB_BLK_WT_DATA3(x)                      (((x) & 0xf) << 12)
> > > +#define AWB_BLK_WT_DATA4(x)                      (((x) & 0xf) << 16)
> > > +#define AWB_BLK_WT_DATA5(x)                      (((x) & 0xf) << 20)
> > > +#define AWB_BLK_WT_DATA6(x)                      (((x) & 0xf) << 24)
> > > +#define AWB_BLK_WT_DATA7(x)                      (((x) & 0xf) << 28)
> > > +
> > > +#define ISP_WRMIFX3_0_CH0_CTRL0                  0xc400
> > > +#define WRMIFX3_CH0_STRIDE_MASK                  GENMASK(28, 16)
> > > +#define WRMIFX3_CH0_STRIDE_SHIFT                 16
> > > +
> > > +#define ISP_WRMIFX3_0_CH0_CTRL1                  0xc404
> > > +#define WRMIFX3_CH0_PIX_BITS_MODE_MASK           GENMASK(30, 27)
> > > +#define WRMIFX3_CH0_PIX_BITS_MODE_SHIFT          27
> > > +#define WRMIFX3_CH0_PIX_BITS_8BITS               1
> > > +#define WRMIFX3_CH0_PIX_BITS_16BITS              2
> > > +
> > > +#define ISP_WRMIFX3_0_CH1_CTRL0                  0xc408
> > > +#define WRMIFX3_CH1_STRIDE_MASK                  GENMASK(28, 16)
> > > +#define WRMIFX3_CH1_STRIDE_SHIFT                 16
> > > +
> > > +#define ISP_WRMIFX3_0_CH1_CTRL1                  0xc40c
> > > +#define WRMIFX3_CH1_PIX_BITS_MODE_MASK           GENMASK(30, 27)
> > > +#define WRMIFX3_CH1_PIX_BITS_MODE_SHIFT          27
> > > +#define WRMIFX3_CH1_PIX_BITS_16BITS              2
> > > +#define WRMIFX3_CH1_PIX_BITS_32BITS              3
> > > +
> > > +#define ISP_WRMIFX3_0_CH0_BADDR                  0xc440
> > > +/* WRMIF base address need 16 bits alignment */
> > > +#define WRMIFX3_CH0_BADDR(x)                     (((x) >> 4) & 0xffffffff)
> > > +
> > > +#define ISP_WRMIFX3_0_CH1_BADDR                  0xc444
> > > +#define WRMIFX3_CH1_BADDR(x)                     (((x) >> 4) & 0xffffffff)
> > > +
> > > +#define ISP_WRMIFX3_0_FMT_SIZE                   0xc464
> > > +#define WRMIFX3_FMT_HSIZE(x)                     ((x) & 0xffff)
> > > +#define WRMIFX3_FMT_VSIZE(x)                     (((x) & 0xffff) << 16)
> > > +
> > > +#define ISP_WRMIFX3_0_FMT_CTRL                   0xc468
> > > +#define WRMIFX3_FMT_MTX_IBITS_MASK               GENMASK(1, 0)
> > > +#define WRMIFX3_FMT_MTX_IBITS_8BIT               0
> > > +#define WRMIFX3_FMT_MTX_IBITS_10BIT              1
> > > +#define WRMIFX3_FMT_MTX_IBITS_12BIT              2
> > > +#define WRMIFX3_FMT_MTX_IBITS_16BIT              3
> > > +#define WRMIFX3_FMT_MTX_UV_SWAP_MASK             BIT(2)
> > > +#define WRMIFX3_FMT_MTX_UV_SWAP_SHIFT            2
> > > +#define WRMIFX3_FMT_MTX_UV_SWAP_VU               0
> > > +#define WRMIFX3_FMT_MTX_UV_SWAP_UV               1
> > > +#define WRMIFX3_FMT_MTX_PLANE_MASK               GENMASK(5, 4)
> > > +#define WRMIFX3_FMT_MTX_PLANE_SHIFT              4
> > > +#define WRMIFX3_FMT_MTX_PLANE_X1                 0
> > > +#define WRMIFX3_FMT_MTX_PLANE_X2                 1
> > > +#define WRMIFX3_FMT_MODE_OUT_MASK                GENMASK(18, 16)
> > > +#define WRMIFX3_FMT_MODE_OUT_SHIFT               16
> > > +#define WRMIFX3_FMT_MODE_OUT_YUV422              1
> > > +#define WRMIFX3_FMT_MODE_OUT_YUV420              2
> > > +#define WRMIFX3_FMT_MODE_OUT_Y_ONLY              3
> > > +
> > > +#define ISP_WRMIFX3_0_WIN_LUMA_H                 0xc420
> > > +#define WRMIFX3_WIN_LUMA_HEND_MASK               GENMASK(28, 16)
> > > +#define WRMIFX3_WIN_LUMA_HEND_SHIFT              16
> > > +#define WRMIFX3_WIN_LUMA_HEND(x)                 ((x) - 1)
> > > +
> > > +#define ISP_WRMIFX3_0_WIN_LUMA_V                 0xc424
> > > +#define WRMIFX3_WIN_LUMA_VEND_MASK               GENMASK(28, 16)
> > > +#define WRMIFX3_WIN_LUMA_VEND_SHIFT              16
> > > +#define WRMIFX3_WIN_LUMA_VEND(x)                 ((x) - 1)
> > > +
> > > +#define ISP_WRMIFX3_0_WIN_CHROM_H                0xc428
> > > +#define WRMIFX3_WIN_CHROM_HEND_MASK              GENMASK(28, 16)
> > > +#define WRMIFX3_WIN_CHROM_HEND_SHIFT             16
> > > +#define WRMIFX3_WIN_CHROM_HEND(x)                (((x) >> 1) - 1)
> > > +
> > > +#define ISP_WRMIFX3_0_WIN_CHROM_V                0xc42c
> > > +#define WRMIFX3_WIN_CHROM_VEND_MASK              GENMASK(28, 16)
> > > +#define WRMIFX3_WIN_CHROM_VEND_SHIFT             16
> > > +#define WRMIFX3_WIN_CHROM_VEND(x)                (((x) >> 1) - 1)
> > > +
> > > +#define ISP_WRMIFX3_0_CRP_HSIZE                  0xc48c
> > > +#define WRMIFX3_CROP_HEND_MASK                   GENMASK(31, 16)
> > > +#define WRMIFX3_CROP_HEND_SHIFT                  16
> > > +#define WRMIFX3_CROP_HEND(x)                     ((x) - 1)
> > > +
> > > +#define ISP_WRMIFX3_0_CRP_VSIZE                  0xc490
> > > +#define WRMIFX3_CROP_VEND_MASK                   GENMASK(31, 16)
> > > +#define WRMIFX3_CROP_VEND_SHIFT                  16
> > > +#define WRMIFX3_CROP_VEND(x)                     ((x) - 1)
> > > +
> > > +#define VIU_DMAWR_BADDR0                         0xc840
> > > +#define VIU_DMAWR_AF_BADDR_MASK                  GENMASK(27, 0)
> > > +/* AF base address need 16 bits alignment */
> > > +#define VIU_DMAWR_AF_BADDR(x)                    ((x) >> 4)
> > > +
> > > +#define VIU_DMAWR_BADDR1                         0xc844
> > > +#define VIU_DMAWR_AWB_BADDR_MASK                  GENMASK(27, 0)
> > > +/* AWB base address need 16 bits alignment */
> > > +#define VIU_DMAWR_AWB_BADDR(x)                    ((x) >> 4)
> > > +
> > > +#define VIU_DMAWR_BADDR2                         0xc848
> > > +#define VIU_DMAWR_AE_BADDR_MASK                  GENMASK(27, 0)
> > > +/* AE base address need 16 bits alignment */
> > > +#define VIU_DMAWR_AE_BADDR(x)                    ((x) >> 4)
> > > +
> > > +#define VIU_DMAWR_SIZE0                          0xc854
> > > +#define VIU_DMAWR_SIZE_AF_MASK                   GENMASK(15, 0)
> > > +#define VIU_DMAWR_SIZE_AWB_MASK                  GENMASK(31, 16)
> > > +#define VIU_DMAWR_SIZE_AWB_SHIFT                 16
> > > +
> > > +#define VIU_DMAWR_SIZE1                          0xc858
> > > +#define VIU_DMAWR_SIZE_AE_MASK                   GENMASK(15, 0)
> > > +
> > > +#endif
> > > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
> > > new file mode 100644
> > > index 000000000000..01d99b66cb32
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
> > > @@ -0,0 +1,768 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#include <linux/pm_runtime.h>
> > > +
> > > +#include "c3-isp-common.h"
> > > +#include "c3-isp-regs.h"
> > > +
> > > +static const struct c3_isp_mbus_format_info c3_isp_rsz_mbus_formats[] = {
> > > +     /* YUV formats */
> > > +     {
> > > +             .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
> > > +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
> > > +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_1_5X8,
> > > +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
> > > +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +     }, {
> > > +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_2X8,
> > These mbus_codes come from the ISP, I presume after debayering.
> > Is the different samples number (5X8, 2X8) a representation of the
> > format on the internal bus between the ISP and the resizers ?
> >
>
> No,  there is no internal bus.
>
> > > +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
> > > +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
> > > +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
> > > +     },
> > > +};
> > > +
> > > +/* The normal parameters of pps module */
> > > +static const int pps_lut_tap4_s11[C3_PPS_TAP4_S11_H_NUM][4] =  {
> > > +     {  0, 511,   0,   0}, { -5, 511,   5,   0}, {-10, 511,  11,   0},
> > > +     {-14, 510,  17,  -1}, {-18, 508,  23,  -1}, {-22, 506,  29,  -1},
> > > +     {-25, 503,  36,  -2}, {-28, 500,  43,  -3}, {-32, 496,  51,  -3},
> > > +     {-34, 491,  59,  -4}, {-37, 487,  67,  -5}, {-39, 482,  75,  -6},
> > > +     {-41, 476,  84,  -7}, {-42, 470,  92,  -8}, {-44, 463, 102,  -9},
> > > +     {-45, 456, 111, -10}, {-45, 449, 120, -12}, {-47, 442, 130, -13},
> > > +     {-47, 434, 140, -15}, {-47, 425, 151, -17}, {-47, 416, 161, -18},
> > > +     {-47, 407, 172, -20}, {-47, 398, 182, -21}, {-47, 389, 193, -23},
> > > +     {-46, 379, 204, -25}, {-45, 369, 215, -27}, {-44, 358, 226, -28},
> > > +     {-43, 348, 237, -30}, {-43, 337, 249, -31}, {-41, 326, 260, -33},
> > > +     {-40, 316, 271, -35}, {-39, 305, 282, -36}, {-37, 293, 293, -37}
> > > +};
> > > +
> > > +static const struct c3_isp_mbus_format_info
> > > +*rsz_find_format_by_code(u32 code, u32 pad)
> > > +{
> > > +     int i;
> > unsigned
>
>
> Will use unsigned int.
>
> > > +
> > > +     for (i = 0; i < ARRAY_SIZE(c3_isp_rsz_mbus_formats); i++) {
> > > +             const struct c3_isp_mbus_format_info *info =
> > > +                     &c3_isp_rsz_mbus_formats[i];
> > > +
> > > +             if (info->mbus_code == code && info->pads & BIT(pad))
> > > +                     return info;
> > > +     }
> > > +
> > > +     return NULL;
> > > +}
> > > +
> > > +static void c3_isp_rsz_cfg_fmt(struct c3_isp_resizer *rsz,
> > > +                            struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_mbus_framefmt *fmt;
> > > +
> > > +     fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> > > +
> > > +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_DISP0_TOP_IN_SIZE, rsz->id),
> > > +                  DISP_TOP_IN_HSIZE(fmt->width) | DISP_TOP_IN_VSIZE(fmt->height));
> > > +}
> > > +
> > > +static void c3_isp_rsz_crop_enable(struct c3_isp_resizer *rsz,
> > > +                                struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_rect *crop;
> > > +
> > > +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> > > +
> > > +     c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_START, rsz->id),
> > > +                  DISP_CRP2_VSTART(crop->top) | DISP_CRP2_HSTART(crop->left));
> > > +     c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_SIZE, rsz->id),
> > > +                  DISP_CRP2_VSIZE(crop->height) | DISP_CRP2_HSIZE(crop->width));
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
> > > +                        DISP_CRP2_EN, DISP_CRP2_EN);
> > > +}
> > > +
> > > +static void c3_isp_rsz_pps_size(struct c3_isp_resizer *rsz,
> > > +                             struct c3_isp_pps_io_size *io_size)
> > > +{
> > > +     int thsize = io_size->thsize;
> > > +     int tvsize = io_size->tvsize;
> > > +     u32 ohsize = io_size->ohsize;
> > > +     u32 ovsize = io_size->ovsize;
> > > +     u32 ihsize = io_size->ihsize;
> > > +     u32 max_hsize = io_size->max_hsize;
> > > +     int step_h_integer, step_v_integer;
> > > +     int step_h_fraction, step_v_fraction;
> > > +     int yuv444to422_en;
> > > +
> > > +     /* Calculate the integer part of horizonal scaler step */
> > > +     step_h_integer = thsize / ohsize;
> > > +
> > > +     /* Calculate the vertical part of horizonal scaler step */
> > > +     step_v_integer = tvsize / ovsize;
> > > +
> > > +     /*
> > > +      * Calculate the fraction part of horizonal scaler step.
> > > +      * step_h_fraction = (source / dest) * 2^24,
> > > +      * so step_h_fraction = ((source << 12) / dest) << 12.
> > > +      */
> > > +     step_h_fraction = ((thsize << 12) / ohsize) << 12;
> > > +
> > > +     /*
> > > +      * Calculate the fraction part of vertical scaler step
> > > +      * step_v_fraction = (source / dest) * 2^24,
> > > +      * so step_v_fraction = ((source << 12) / dest) << 12.
> > > +      */
> > > +     step_v_fraction = ((tvsize << 12) / ovsize) << 12;
> > > +
> > > +     yuv444to422_en = ihsize > (max_hsize / 2) ? 1 : 0;
> > > +
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_444TO422, rsz->id),
> > > +                        PPS_444TO422_EN_MASK, yuv444to422_en);
> > > +
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
> > > +                        PPS_VSC_FRACTION_PART_MASK, step_v_fraction);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
> > > +                        PPS_VSC_INTEGER_PART_MASK,
> > > +                        step_v_integer << PPS_VSC_INTEGER_PART_SHIFT);
> > > +
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
> > > +                        PPS_HSC_FRACTION_PART_MASK, step_h_fraction);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
> > > +                        PPS_HSC_INTEGER_PART_MASK,
> > > +                        step_h_integer << PPS_HSC_INTEGER_PART_SHIFT);
> > > +
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
> > > +                        PPS_PREHSC_LUMA_COEF0_MASK, PPS_PREHSC_LUMA_COEF0_INIT);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
> > > +                        PPS_PREHSC_LUMA_COEF1_MASK,
> > > +                        PPS_PREHSC_LUMA_COEF1_INIT << PPS_PREHSC_LUMA_COEF1_SHIFT);
> > > +
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
> > > +                        PPS_PREHSC_LUMA_COEF2_MASK, PPS_PREHSC_LUMA_COEF2_INIT);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
> > > +                        PPS_PREHSC_LUMA_COEF3_MASK,
> > > +                        PPS_PREHSC_LUMA_COEF3_INIT << PPS_PREHSC_LUMA_COEF3_SHIFT);
> > > +}
> > > +
> > > +static void c3_isp_rsz_pps_lut(struct c3_isp_resizer *rsz, u32 ctype)
> > > +{
> > > +     int i;
> > unsigned
>
>
> Will use unsigned int.
>
> > > +
> > > +     /*
> > > +      * Default value of this register is 0,
> > > +      * so only need to set SCALE_LUMA_COEF_S11_MODE
> > > +      * and SCALE_LUMA_CTYPE.
> > You can fit this in 2 lines ?
>
>
> OK, will fit this in 2 lines.
>
> > > +      * This register needs to be written in one time.
> > > +      */
> > > +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_LUMA, rsz->id),
> > > +                  SCALE_LUMA_COEF_S11_MODE | SCALE_LUMA_CTYPE(ctype));
> > > +
> > > +     for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
> > > +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
> > > +                          SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
> > > +                          SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
> > > +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
> > > +                          SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
> > > +                          SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][3]));
> > > +     }
> > > +
> > > +     /*
> > > +      * Default value of this register is 0,
> > > +      * so only need to set SCALE_CHRO_COEF_S11_MODE
> > > +      * and SCALE_CHRO_CTYPE.
> > same
> >
>
> OK, will fit this in 2 lines.
>
> > > +      * This register needs to be written in one time.
> > > +      */
> > > +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_CHRO, rsz->id),
> > > +                  SCALE_CHRO_COEF_S11_MODE | SCALE_CHRO_CTYPE(ctype));
> > > +
> > > +     for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
> > > +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
> > > +                          SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
> > > +                          SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
> > > +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
> > > +                          SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
> > > +                          SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][3]));
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_rsz_pps_disable(struct c3_isp_resizer *rsz)
> > > +{
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_HSC_EN_MASK, 0);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_VSC_EN_MASK, 0);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_PREVSC_EN_MASK, 0);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_PREHSC_EN_MASK, 0);
> > > +}
> > > +
> > > +static int c3_isp_rsz_pps_enable(struct c3_isp_resizer *rsz,
> > > +                              struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_rect *crop;
> > > +     struct v4l2_rect *cmps;
> > > +     int max_hsize;
> > > +     int hsc_en, vsc_en;
> > > +     int preh_en, prev_en;
> > > +     u32 reg_prehsc_rate;
> > > +     u32 reg_prevsc_flt_num;
> > > +     int pre_vscale_max_hsize;
> > > +     u32 ihsize_after_pre_hsc;
> > > +     u32 ihsize_after_pre_hsc_alt;
> > > +     u32 reg_vsc_tap_num_alt;
> > > +     u32 ihsize;
> > > +     u32 ivsize;
> > > +     struct c3_isp_pps_io_size io_size;
> > > +
> > > +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> > > +     cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> > > +
> > > +     ihsize = crop->width;
> > > +     ivsize = crop->height;
> > > +
> > > +     hsc_en = (ihsize == cmps->width) ? C3_SCALE_DIS : C3_SCALE_EN;
> > > +     vsc_en = (ivsize == cmps->height) ? C3_SCALE_DIS : C3_SCALE_EN;
> > > +
> > > +     /* Disable pps when there no need to use pps */
> > > +     if (!hsc_en && !vsc_en) {
> > > +             c3_isp_rsz_pps_disable(rsz);
> > > +             return 0;
> > > +     }
> > > +
> > > +     /*
> > > +      * Pre-scale needs to be enable
> > > +      * if the down scaling factor exceeds 4.
> > > +      */
> > > +     preh_en = (ihsize > cmps->width * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
> > > +     prev_en = (ivsize > cmps->height * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
> > > +
> > > +     if (rsz->id == C3_ISP_RSZ_2) {
> > > +             max_hsize = C3_ISP_MAX_WIDTH;
> > > +             /*
> > > +              * Set vertical tap number and
> > > +              * the max hsize of pre-vertical scale.
> > > +              */
> > > +             reg_prevsc_flt_num = 4;
> > > +             pre_vscale_max_hsize = max_hsize / 2;
> > > +     } else {
> > > +             max_hsize = C3_ISP_DEFAULT_WIDTH;
> > > +             preh_en  = (ihsize > max_hsize) ? C3_SCALE_EN : C3_SCALE_DIS;
> > > +             /*
> > > +              * Set vertical tap number and
> > > +              * the max hsize of pre-vertical scale.
> > > +              */
> > > +             if (ihsize > (max_hsize / 2) &&
> > > +                 ihsize <= max_hsize && prev_en) {
> > > +                     reg_prevsc_flt_num = 2;
> > > +                     pre_vscale_max_hsize = max_hsize;
> > > +             } else {
> > > +                     reg_prevsc_flt_num = 4;
> > > +                     pre_vscale_max_hsize = max_hsize / 2;
> > > +             }
> > > +     }
> > > +
> > > +     /*
> > > +      * Set pre-horizonal scale rate and
> > > +      * the hsize of after pre-horizonal scale.
> > > +      */
> > > +     if (preh_en) {
> > > +             reg_prehsc_rate = 1;
> > > +             ihsize_after_pre_hsc = DIV_ROUND_UP(ihsize, 2);
> > > +     } else {
> > > +             reg_prehsc_rate = 0;
> > > +             ihsize_after_pre_hsc = ihsize;
> > > +     }
> > > +
> > > +     /* Change pre-horizonal scale rate */
> > > +     if (prev_en && ihsize_after_pre_hsc >= pre_vscale_max_hsize)
> > > +             reg_prehsc_rate += 1;
> > > +
> > > +     /* Set the actual hsize of after pre-horizonal scale */
> > > +     if (preh_en)
> > > +             ihsize_after_pre_hsc_alt =
> > > +                     DIV_ROUND_UP(ihsize, 1 << reg_prehsc_rate);
> > > +     else
> > > +             ihsize_after_pre_hsc_alt = ihsize;
> > > +
> > > +     /* Set vertical scaler bank length */
> > > +     if (ihsize_after_pre_hsc_alt <= (max_hsize / 2))
> > > +             reg_vsc_tap_num_alt = 4;
> > > +     else if (ihsize_after_pre_hsc_alt <= max_hsize)
> > > +             reg_vsc_tap_num_alt = prev_en ? 2 : 4;
> > > +     else
> > > +             reg_vsc_tap_num_alt = prev_en ? 4 : 2;
> > > +
> > > +     io_size.thsize = ihsize_after_pre_hsc_alt;
> > > +     io_size.tvsize = prev_en ? DIV_ROUND_UP(ivsize, 2) : ivsize;
> > > +     io_size.ohsize = cmps->width;
> > > +     io_size.ovsize = cmps->height;
> > > +     io_size.ihsize = ihsize;
> > > +     io_size.max_hsize = max_hsize;
> > > +
> > > +     c3_isp_rsz_pps_size(rsz, &io_size);
> > > +     c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_0);
> > > +     c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_2);
> > > +
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_VSC_TAP_NUM_MASK, reg_vsc_tap_num_alt);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_HSC_TAP_NUM_MASK,
> > > +                        PPS_HSC_TAP_NUM_INIT << PPS_HSC_TAP_NUM_SHIFT);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_PREVSC_FLT_NUM_MASK,
> > > +                        reg_prevsc_flt_num << PPS_PREVSC_FLT_NUM_SHIFT);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_PREHSC_FLT_NUM_MASK,
> > > +                        PPS_PREHSC_FLT_NUM_INIT << PPS_PREHSC_FLT_NUM_SHIFT);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_PREVSC_RATE_MASK, prev_en << PPS_PREVSC_RATE_SHIFT);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_PREHSC_RATE_MASK, reg_prehsc_rate << PPS_PREHSC_RATE_SHIFT);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_HSC_EN_MASK, hsc_en << PPS_HSC_EN_SHIFT);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_VSC_EN_MASK, vsc_en << PPS_VSC_EN_SHIFT);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_PREVSC_EN_MASK, prev_en << PPS_PREVSC_EN_SHIFT);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_PREHSC_EN_MASK, preh_en << PPS_PREHSC_EN_SHIFT);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_HSC_NOR_RS_BITS_MASK,
> > > +                        PPS_HSC_NOR_RS_BITS_INIT << PPS_HSC_NOR_RS_BITS_SHIFT);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
> > > +                        PPS_VSC_NOR_RS_BITS_MASK,
> > > +                        PPS_VSC_NOR_RS_BITS_INIT << PPS_VSC_NOR_RS_BITS_SHIFT);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +void c3_isp_rsz_start(struct c3_isp_resizer *rsz)
> > > +{
> > > +     struct v4l2_subdev_state *state;
> > > +
> > > +     state = v4l2_subdev_lock_and_get_active_state(&rsz->sd);
> > > +
> > > +     c3_isp_rsz_cfg_fmt(rsz, state);
> > > +     c3_isp_rsz_crop_enable(rsz, state);
> > > +     c3_isp_rsz_pps_enable(rsz, state);
> > > +
> > > +     c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN,
> > > +                        TOP_DISP_EN(rsz->id), TOP_DISP_EN(rsz->id));
> > > +
> > > +     v4l2_subdev_unlock_state(state);
> > > +}
> > > +
> > > +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
> > > +{
> > > +     c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, TOP_DISP_EN(rsz->id), 0);
> > > +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
> > > +                        DISP_CRP2_EN, 0x0);
> > > +
> > > +     c3_isp_rsz_pps_disable(rsz);
> > > +}
> > > +
> > > +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
> > > +                               struct v4l2_subdev_state *state,
> > > +                               struct v4l2_subdev_krouting *routing)
> > > +{
> > > +     static const struct v4l2_mbus_framefmt format = {
> > > +             .width = C3_ISP_DEFAULT_WIDTH,
> > > +             .height = C3_ISP_DEFAULT_HEIGHT,
> > > +             .code = C3_ISP_RSZ_DEF_PAD_FMT,
> > > +             .field = V4L2_FIELD_NONE,
> > > +             .colorspace = V4L2_COLORSPACE_SRGB,
> > > +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
> > > +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
> > > +             .xfer_func = V4L2_XFER_FUNC_SRGB,
> > > +     };
> > > +     int ret;
> > > +
> > > +     ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
> > > +                                struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_subdev_route routes;
> > > +     struct v4l2_subdev_krouting routing;
> > > +
> > > +     routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
> > > +     routes.sink_stream = 0;
> > > +     routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
> > > +     routes.source_stream = 0;
> > > +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> > > +
> > > +     routing.num_routes = 1;
> > > +     routing.routes = &routes;
> > > +
> > > +     return c3_isp_rsz_cfg_routing(sd, state, &routing);
> > > +}
> > > +
> > > +static int c3_isp_rsz_set_routing(struct v4l2_subdev *sd,
> > > +                               struct v4l2_subdev_state *state,
> > > +                               enum v4l2_subdev_format_whence which,
> > > +                               struct v4l2_subdev_krouting *routing)
> > > +{
> > > +     bool is_streaming = v4l2_subdev_is_streaming(sd);
> > > +
> > > +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
> > > +             return -EBUSY;
> > > +
> > > +     return c3_isp_rsz_cfg_routing(sd, state, routing);
> > > +}
> > Unless there are reasons I missed, I would drop routing support from
> > the resizers
> >
>
>
> Will remove this interface.
>
> > > +
> > > +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
> > > +                                  struct v4l2_subdev_state *state,
> > > +                                  struct v4l2_subdev_mbus_code_enum *code)
> > > +{
> > > +     if (code->index >= ARRAY_SIZE(c3_isp_rsz_mbus_formats))
> > > +             return -EINVAL;
> > > +
> > > +     code->code = c3_isp_rsz_mbus_formats[code->index].mbus_code;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static void c3_isp_rsz_set_sink_fmt(struct v4l2_subdev_state *state,
> > > +                                 struct v4l2_subdev_format *format)
> > > +{
> > > +     struct v4l2_mbus_framefmt *sink_fmt;
> > > +     const struct c3_isp_mbus_format_info *isp_fmt;
> > > +     struct v4l2_rect *sink_crop;
> > > +     struct v4l2_rect *sink_cmps;
> > > +
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
> > > +     sink_crop = v4l2_subdev_state_get_crop(state, format->pad);
> > > +     sink_cmps = v4l2_subdev_state_get_compose(state, format->pad);
> > > +
> > > +     isp_fmt = rsz_find_format_by_code(format->format.code, format->pad);
> > > +     if (!isp_fmt)
> > > +             sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> > > +     else
> > > +             sink_fmt->code = format->format.code;
> > > +
> > > +     sink_fmt->width = clamp_t(u32, format->format.width,
> > > +                               C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
> > > +     sink_fmt->height = clamp_t(u32, format->format.height,
> > > +                                C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
> > > +
> > > +     sink_crop->width = sink_fmt->width;
> > > +     sink_crop->height = sink_fmt->height;
> > > +     sink_crop->left = 0;
> > > +     sink_crop->top = 0;
> > > +
> > > +     sink_cmps->width = sink_crop->width;
> > > +     sink_cmps->height = sink_crop->height;
> > > +     sink_cmps->left = 0;
> > > +     sink_cmps->top = 0;
> > > +
> > > +     format->format = *sink_fmt;
> > > +}
> > > +
> > > +static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state,
> > > +                                   struct v4l2_subdev_format *format)
> > > +{
> > > +     const struct c3_isp_mbus_format_info *rsz_fmt;
> > > +     struct v4l2_mbus_framefmt *src_fmt;
> > > +     struct v4l2_rect *sink_crop;
> > > +     struct v4l2_rect *sink_cmps;
> > > +
> > > +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
> > > +     sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> > > +     sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> > > +
> > > +     rsz_fmt = rsz_find_format_by_code(format->format.code, format->pad);
> > > +     if (!rsz_fmt)
> > > +             src_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> > > +     else
> > > +             src_fmt->code = format->format.code;
> > > +
> > > +     src_fmt->width = clamp_t(u32, format->format.width,
> > > +                              C3_ISP_MIN_WIDTH, sink_crop->width);
> > > +     src_fmt->height = clamp_t(u32, format->format.height,
> > > +                               C3_ISP_MIN_HEIGHT, sink_crop->height);
> > > +
> > > +     /* The sink compose size must be same with the source size. */
> > > +     sink_cmps->width = src_fmt->width;
> > > +     sink_cmps->height = src_fmt->height;
> > Shouldn't it be the other way around ? The source sizes should always
> > match the sink compose rectangle sizes ?
>
>
> OK, will check and test this issue.
>
> > > +
> > > +     format->format = *src_fmt;
> > > +}
> > > +
> > > +static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd,
> > > +                           struct v4l2_subdev_state *state,
> > > +                           struct v4l2_subdev_format *format)
> > > +{
> > > +     if (format->pad == C3_ISP_RESIZER_PAD_SINK) {
> > > +             c3_isp_rsz_set_sink_fmt(state, format);
> > > +     } else if (format->pad == C3_ISP_RESIZER_PAD_SOURCE) {
> > > +             c3_isp_rsz_set_source_fmt(state, format);
> > > +     } else {
> > This can't happen
>
>
> Will remove this branch.
>
> > > +             dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
> > > +             return -ENOTTY;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd,
> > > +                                 struct v4l2_subdev_state *state,
> > > +                                 struct v4l2_subdev_selection *sel)
> > > +{
> > > +     struct v4l2_mbus_framefmt *fmt;
> > > +     struct v4l2_rect *crop;
> > > +     struct v4l2_rect *cmps;
> > > +
> > > +     if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
> > > +             return -EINVAL;
> > > +
> > > +     switch (sel->target) {
> > > +     case V4L2_SEL_TGT_CROP_BOUNDS:
> > > +             fmt = v4l2_subdev_state_get_format(state, sel->pad);
> > > +             sel->r.width = fmt->width;
> > > +             sel->r.height = fmt->height;
> > > +             sel->r.left = 0;
> > > +             sel->r.top = 0;
> > > +             break;
> > > +     case V4L2_SEL_TGT_COMPOSE_BOUNDS:
> > > +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
> > > +             sel->r.width = crop->width;
> > > +             sel->r.height = crop->height;
> > > +             sel->r.left = 0;
> > > +             sel->r.top = 0;
> > > +             break;
> > > +     case V4L2_SEL_TGT_CROP:
> > > +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
> > > +             sel->r = *crop;
> > > +             break;
> > > +     case V4L2_SEL_TGT_COMPOSE:
> > > +             cmps = v4l2_subdev_state_get_compose(state, sel->pad);
> > > +             sel->r = *cmps;
> > > +             break;
> > > +     default:
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd,
> > > +                                 struct v4l2_subdev_state *state,
> > > +                                 struct v4l2_subdev_selection *sel)
> > > +{
> > > +     struct v4l2_mbus_framefmt *fmt;
> > > +     struct v4l2_rect *crop;
> > > +     struct v4l2_rect *cmps;
> > > +
> > > +     if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
> > > +             return -EINVAL;
> > > +
> > > +     switch (sel->target) {
> > > +     case V4L2_SEL_TGT_CROP:
> > > +             fmt = v4l2_subdev_state_get_format(state, sel->pad);
> > > +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
> > > +
> > > +             sel->r.left = clamp_t(s32, sel->r.left, 0, fmt->width - 1);
> > > +             sel->r.top = clamp_t(s32, sel->r.top, 0, fmt->height - 1);
> > > +             sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH,
> > > +                                  fmt->width - sel->r.left);
> > > +             sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT,
> > > +                                   fmt->height - sel->r.top);
> > > +
> > > +             crop->width = ALIGN(sel->r.width, 2);
> > > +             crop->height = ALIGN(sel->r.height, 2);
> > > +             crop->left = sel->r.left;
> > > +             crop->top = sel->r.top;
> > > +
> > > +             sel->r = *crop;
> > > +             break;
> > > +     case V4L2_SEL_TGT_COMPOSE:
> > > +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
> > > +             cmps = v4l2_subdev_state_get_compose(state, sel->pad);
> > > +
> > > +             sel->r.left = 0;
> > > +             sel->r.top = 0;
> > > +             sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH, crop->width);
> > > +             sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT, crop->height);
> > > +
> > > +             cmps->width = ALIGN(sel->r.width, 2);
> > > +             cmps->height = ALIGN(sel->r.height, 2);
> > > +             cmps->left = sel->r.left;
> > > +             cmps->top = sel->r.top;
> > > +
> > > +             sel->r = *cmps;
> > > +
> > > +             fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
> > > +             fmt->width = cmps->width;
> > > +             fmt->height = cmps->height;
> > > +             break;
> > > +     default:
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_rsz_init_state(struct v4l2_subdev *sd,
> > > +                              struct v4l2_subdev_state *state)
> > > +{
> > > +     struct v4l2_mbus_framefmt *sink_fmt;
> > > +     struct v4l2_mbus_framefmt *src_fmt;
> > > +     struct v4l2_rect *crop;
> > > +     struct v4l2_rect *cmps;
> > > +
> > > +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
> > > +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
> > > +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
> > > +     sink_fmt->field = V4L2_FIELD_NONE;
> > > +     sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
> > > +     sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> > > +     sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
> > > +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> > > +     sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
> > > +
> > > +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
> > > +     crop->width = C3_ISP_DEFAULT_WIDTH;
> > > +     crop->height = C3_ISP_DEFAULT_HEIGHT;
> > > +     crop->left = 0;
> > > +     crop->top = 0;
> > > +
> > > +     cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
> > > +     cmps->width = C3_ISP_DEFAULT_WIDTH;
> > > +     cmps->height = C3_ISP_DEFAULT_HEIGHT;
> > > +     cmps->left = 0;
> > > +     cmps->top = 0;
> > > +
> > > +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
> > > +     *src_fmt = *sink_fmt;
> > > +
> > > +     return c3_isp_rsz_init_routing(sd, state);
> > > +}
> > > +
> > > +static const struct v4l2_subdev_pad_ops c3_isp_rsz_pad_ops = {
> > > +     .enum_mbus_code = c3_isp_rsz_enum_mbus_code,
> > > +     .get_fmt = v4l2_subdev_get_fmt,
> > > +     .set_fmt = c3_isp_rsz_set_fmt,
> > > +     .get_selection = c3_isp_rsz_get_selection,
> > > +     .set_selection = c3_isp_rsz_set_selection,
> > > +     .set_routing = c3_isp_rsz_set_routing,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_ops c3_isp_rsz_subdev_ops = {
> > > +     .pad = &c3_isp_rsz_pad_ops,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_internal_ops c3_isp_rsz_internal_ops = {
> > > +     .init_state = c3_isp_rsz_init_state,
> > > +};
> > > +
> > > +/* Media entity operations */
> > > +static const struct media_entity_operations c3_isp_rsz_entity_ops = {
> > > +     .link_validate = v4l2_subdev_link_validate,
> > > +};
> > > +
> > > +static int c3_isp_rsz_register(struct c3_isp_resizer *rsz)
> > > +{
> > > +     struct v4l2_subdev *sd = &rsz->sd;
> > > +     int ret;
> > > +
> > > +     v4l2_subdev_init(sd, &c3_isp_rsz_subdev_ops);
> > > +     sd->owner = THIS_MODULE;
> > > +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > > +     sd->internal_ops = &c3_isp_rsz_internal_ops;
> > > +     snprintf(sd->name, sizeof(sd->name), "isp-resizer%u", rsz->id);
> > maybe "c3-isp_resizer%u"
> >
>
> OK, will use "c3-isp_resizer%u".
>
> > > +
> > > +     sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
> > > +     sd->entity.ops = &c3_isp_rsz_entity_ops;
> > > +
> > > +     sd->dev = rsz->isp->dev;
> > > +     v4l2_set_subdevdata(sd, rsz);
> > > +
> > > +     rsz->pads[C3_ISP_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> > > +     rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> > > +     ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_PAD_MAX, rsz->pads);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = v4l2_subdev_init_finalize(sd);
> > > +     if (ret)
> > > +             goto err_entity_cleanup;
> > > +
> > > +     ret = v4l2_device_register_subdev(&rsz->isp->v4l2_dev, sd);
> > > +     if (ret)
> > > +             goto err_subdev_cleanup;
> > > +
> > > +     return 0;
> > > +
> > > +err_subdev_cleanup:
> > > +     v4l2_subdev_cleanup(sd);
> > > +err_entity_cleanup:
> > > +     media_entity_cleanup(&sd->entity);
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_isp_rsz_unregister(struct c3_isp_resizer *rsz)
> > > +{
> > > +     struct v4l2_subdev *sd = &rsz->sd;
> > > +
> > > +     v4l2_device_unregister_subdev(sd);
> > > +     v4l2_subdev_cleanup(sd);
> > > +     media_entity_cleanup(&sd->entity);
> > > +}
> > > +
> > > +int c3_isp_resizers_register(struct c3_isp_device *isp)
> > > +{
> > > +     u32 i;
> > > +     int ret;
> > > +
> > > +     for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
> > > +             struct c3_isp_resizer *rsz = &isp->resizers[i];
> > > +
> > > +             rsz->id = i;
> > > +             rsz->isp = isp;
> > > +
> > > +             if (rsz->id == C3_ISP_RSZ_0)
> > > +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_0];
> > > +             else if (rsz->id == C3_ISP_RSZ_1)
> > > +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_1];
> > > +             else
> > > +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_2];
> > > +
> > > +             ret = c3_isp_rsz_register(rsz);
> > > +             if (ret) {
> > > +                     rsz->isp = NULL;
> > > +                     c3_isp_resizers_unregister(isp);
> > > +                     return ret;
> > > +             }
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +void c3_isp_resizers_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     u32 i;
> > > +
> > > +     for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
> > > +             struct c3_isp_resizer *rsz = &isp->resizers[i];
> > > +
> > > +             if (rsz->isp)
> > > +                     c3_isp_rsz_unregister(rsz);
> > > +     };
> > > +}
> > > diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
> > > new file mode 100644
> > > index 000000000000..72024442d48f
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
> > > @@ -0,0 +1,488 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#include <linux/cleanup.h>
> > > +#include <linux/pm_runtime.h>
> > > +
> > > +#include <media/v4l2-ctrls.h>
> > > +#include <media/v4l2-event.h>
> > > +#include <media/v4l2-ioctl.h>
> > > +#include <media/v4l2-mc.h>
> > > +#include <media/videobuf2-dma-contig.h>
> > > +
> > > +#include "c3-isp-common.h"
> > > +#include "c3-isp-regs.h"
> > > +#include "include/uapi/c3-isp-config.h"
> > > +
> > > +/* Hardware configuration */
> > > +
> > > +static void c3_isp_stats_af_init(struct c3_isp_stats *stats)
> > > +{
> > > +     c3_isp_write(stats->isp, ISP_AF_ROI0_WIN01,
> > > +                  AF_ROI_XYWIN_00_INIT | AF_ROI_XYWIN_01_INIT);
> > > +
> > > +     c3_isp_write(stats->isp, ISP_AF_ROI0_WIN23,
> > > +                  AF_ROI_XYWIN_02_INIT | AF_ROI_XYWIN_03_INIT);
> > > +
> > > +     c3_isp_write(stats->isp, ISP_AF_ROI1_WIN01,
> > > +                  AF_ROI_XYWIN_10_INIT | AF_ROI_XYWIN_11_INIT);
> > > +
> > > +     c3_isp_write(stats->isp, ISP_AF_ROI1_WIN23,
> > > +                  AF_ROI_XYWIN_12_INIT | AF_ROI_XYWIN_13_INIT);
> > > +
> > > +     /* 0: old statistics output, 1: new statistics output. */
> > > +     c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL,
> > > +                        AF_STAT_SELECT, 0x1 << AF_STAT_SELECT_SHIFT);
> > > +}
> > > +
> > > +static void c3_isp_stats_ae_init(struct c3_isp_stats *stats)
> > > +{
> > > +     c3_isp_write(stats->isp, ISP_AE_ROI0_WIN01,
> > > +                  AE_ROI_XYWIN_00_INIT | AE_ROI_XYWIN_01_INIT);
> > > +
> > > +     c3_isp_write(stats->isp, ISP_AE_ROI0_WIN23,
> > > +                  AE_ROI_XYWIN_02_INIT | AE_ROI_XYWIN_03_INIT);
> > > +
> > > +     c3_isp_write(stats->isp, ISP_AE_ROI1_WIN01,
> > > +                  AE_ROI_XYWIN_10_INIT | AE_ROI_XYWIN_11_INIT);
> > > +
> > > +     c3_isp_write(stats->isp, ISP_AE_ROI1_WIN23,
> > > +                  AE_ROI_XYWIN_12_INIT | AE_ROI_XYWIN_13_INIT);
> > > +
> > > +     c3_isp_write(stats->isp, ISP_AE_STAT_THD01,
> > > +                  AE_STAT_THRD0_INIT | AE_STAT_THRD1_INIT);
> > > +
> > > +     c3_isp_write(stats->isp, ISP_AE_STAT_THD23,
> > > +                  AE_STAT_THRD2_INIT | AE_STAT_THRD3_INIT);
> > > +
> > > +     /* Set 0 when ae_stat_switch is not 0 */
> > > +     c3_isp_update_bits(stats->isp, ISP_AE_CTRL,
> > > +                        AE_CTRL_INPUT_2LINE_TOGETHER, 0);
> > > +
> > > +     /* Configure ae luma mode */
> > > +     c3_isp_update_bits(stats->isp, ISP_AE_CTRL, AE_CTRL_LUMA_MODE_MASK,
> > > +                        AE_CTRL_LUMA_MODE_FILTER << AE_CTRL_LUMA_MODE_SHIFT);
> > > +}
> > > +
> > > +static void c3_isp_stats_awb_init(struct c3_isp_stats *stats)
> > > +{
> > > +     /* Initialize the awb statistics rectangle of image */
> > > +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN01,
> > > +                  AWB_ROI_XYWIN_00_INIT | AWB_ROI_XYWIN_01_INIT);
> > > +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN23,
> > > +                  AWB_ROI_XYWIN_02_INIT | AWB_ROI_XYWIN_03_INIT);
> > > +
> > > +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN01,
> > > +                  AWB_ROI_XYWIN_10_INIT | AWB_ROI_XYWIN_11_INIT);
> > > +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN23,
> > > +                  AWB_ROI_XYWIN_12_INIT | AWB_ROI_XYWIN_13_INIT);
> > > +}
> > > +
> > > +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
> > > +{
> > > +     struct c3_isp_device *isp = stats->isp;
> > > +     struct c3_isp_stats_info *stats_info = stats->buff->vaddr;
> > > +     u32 awb_dma_size = sizeof(stats_info->awb_stats);
> > > +     u32 ae_dma_size = sizeof(stats_info->ae_stats);
> > > +     u32 awb_dma_addr = stats->buff->paddr;
> > > +     u32 af_dma_addr;
> > > +     u32 ae_dma_addr;
> > > +
> > > +     ae_dma_addr = awb_dma_addr + awb_dma_size;
> > > +     af_dma_addr = ae_dma_addr + ae_dma_size;
> > > +
> > > +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR0, VIU_DMAWR_AF_BADDR_MASK,
> > > +                        VIU_DMAWR_AF_BADDR(af_dma_addr));
> > > +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR1, VIU_DMAWR_AWB_BADDR_MASK,
> > > +                        VIU_DMAWR_AWB_BADDR(awb_dma_addr));
> > > +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR2, VIU_DMAWR_AE_BADDR_MASK,
> > > +                        VIU_DMAWR_AE_BADDR(ae_dma_addr));
> > > +}
> > > +
> > > +static void c3_isp_stats_enable(struct c3_isp_stats *stats)
> > > +{
> > > +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> > > +                        TOP_3A_AE_STAT_EN, TOP_3A_AE_STAT_EN);
> > > +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> > > +                        TOP_3A_AWB_STAT_EN, TOP_3A_AWB_STAT_EN);
> > > +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> > > +                        TOP_3A_AF_STAT_EN, TOP_3A_AF_STAT_EN);
> > > +}
> > > +
> > > +static void c3_isp_stats_disable(struct c3_isp_stats *stats)
> > > +{
> > > +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> > > +                        TOP_3A_AE_STAT_EN, 0);
> > > +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> > > +                        TOP_3A_AWB_STAT_EN, 0);
> > > +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
> > > +                        TOP_3A_AF_STAT_EN, 0);
> > > +}
> > > +
> > > +/* The unit of dma_size is 16 bytes */
> > > +static void c3_isp_stats_cfg_dmawr_size(struct c3_isp_stats *stats)
> > > +{
> > > +     u32 dma_size;
> > > +
> > > +     dma_size = sizeof(struct af_stats_info) / 16;
> > > +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0,
> > > +                        VIU_DMAWR_SIZE_AF_MASK, dma_size);
> > > +
> > > +     dma_size = sizeof(struct awb_stats_info) / 16;
> > > +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE_AWB_MASK,
> > > +                        dma_size << VIU_DMAWR_SIZE_AWB_SHIFT);
> > > +
> > > +     dma_size = sizeof(struct ae_stats_info) / 16;
> > > +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1,
> > > +                        VIU_DMAWR_SIZE_AE_MASK, dma_size);
> > > +}
> > > +
> > > +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
> > > +{
> > > +     stats->buff = list_first_entry_or_null(&stats->pending,
> > > +                                            struct c3_isp_vb2_buffer, list);
> > > +     if (stats->buff) {
> > > +             c3_isp_stats_cfg_dmawr_addr(stats);
> > > +             list_del(&stats->buff->list);
> > > +     }
> > > +}
> > > +
> > > +static void c3_isp_stats_start(struct c3_isp_stats *stats)
> > > +{
> > > +     c3_isp_stats_af_init(stats);
> > > +     c3_isp_stats_ae_init(stats);
> > > +     c3_isp_stats_awb_init(stats);
> > > +
> > > +     c3_isp_stats_cfg_dmawr_size(stats);
> > > +     c3_isp_stats_cfg_buff(stats);
> > > +     c3_isp_stats_enable(stats);
> > > +
> > > +     stats->is_streaming = true;
> > > +}
> > > +
> > > +static void c3_isp_stats_stop(struct c3_isp_stats *stats)
> > > +{
> > > +     stats->is_streaming = false;
> > > +
> > > +     c3_isp_stats_disable(stats);
> > > +}
> > > +
> > > +static void c3_isp_stats_return_buffers(struct c3_isp_stats *stats,
> > > +                                     enum vb2_buffer_state state)
> > > +{
> > > +     unsigned long flags;
> > > +     struct c3_isp_vb2_buffer *buff;
> > > +
> > > +     spin_lock_irqsave(&stats->buff_lock, flags);
> > > +
> > > +     if (stats->buff) {
> > > +             vb2_buffer_done(&stats->buff->vb.vb2_buf, state);
> > > +             stats->buff = NULL;
> > > +     }
> > > +
> > > +     while (!list_empty(&stats->pending)) {
> > > +             buff = list_first_entry(&stats->pending,
> > > +                                     struct c3_isp_vb2_buffer, list);
> > > +             list_del(&buff->list);
> > > +             vb2_buffer_done(&buff->vb.vb2_buf, state);
> > > +     }
> > > +
> > > +     spin_unlock_irqrestore(&stats->buff_lock, flags);
> > > +}
> > > +
> > > +static int c3_isp_stats_querycap(struct file *file, void *fh,
> > > +                              struct v4l2_capability *cap)
> > > +{
> > > +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
> > > +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_stats_enum_fmt(struct file *file, void *fh,
> > > +                              struct v4l2_fmtdesc *f)
> > > +{
> > > +     struct c3_isp_stats *stats = video_drvdata(file);
> > > +
> > > +     if (f->index > 0 || f->type != stats->vb2_q.type)
> > > +             return -EINVAL;
> > > +
> > > +     f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_stats_g_fmt(struct file *file, void *fh,
> > > +                           struct v4l2_format *f)
> > > +{
> > > +     struct c3_isp_stats *stats = video_drvdata(file);
> > > +
> > > +     f->fmt.meta = stats->vfmt.fmt.meta;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
> > > +     .vidioc_querycap                = c3_isp_stats_querycap,
> > > +     .vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
> > > +     .vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
> > > +     .vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
> > > +     .vidioc_try_fmt_meta_cap        = c3_isp_stats_g_fmt,
> > > +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
> > > +     .vidioc_querybuf                = vb2_ioctl_querybuf,
> > > +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
> > > +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
> > > +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
> > > +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
> > > +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
> > > +     .vidioc_streamon                = vb2_ioctl_streamon,
> > > +     .vidioc_streamoff               = vb2_ioctl_streamoff,
> > > +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
> > > +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
> > > +};
> > > +
> > > +static const struct v4l2_file_operations isp_stats_v4l2_fops = {
> > > +     .open = v4l2_fh_open,
> > > +     .release = vb2_fop_release,
> > > +     .poll = vb2_fop_poll,
> > > +     .unlocked_ioctl = video_ioctl2,
> > > +     .mmap = vb2_fop_mmap,
> > > +};
> > > +
> > > +static int c3_isp_stats_vb2_queue_setup(struct vb2_queue *q,
> > > +                                     unsigned int *num_buffers,
> > > +                                     unsigned int *num_planes,
> > > +                                     unsigned int sizes[],
> > > +                                     struct device *alloc_devs[])
> > > +{
> > > +     if (*num_planes) {
> > > +             if (*num_planes != 1)
> > > +                     return -EINVAL;
> > > +
> > > +             if (sizes[0] < sizeof(struct c3_isp_stats_info))
> > > +                     return -EINVAL;
> > > +     } else {
> > > +             *num_planes = 1;
> > > +             sizes[0] = sizeof(struct c3_isp_stats_info);
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
> > > +{
> > > +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > > +     struct c3_isp_vb2_buffer *buf =
> > > +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> > > +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> > > +     unsigned long flags;
> > > +
> > > +     spin_lock_irqsave(&stats->buff_lock, flags);
> > > +
> > > +     list_add_tail(&buf->list, &stats->pending);
> > > +
> > > +     spin_unlock_irqrestore(&stats->buff_lock, flags);
> > > +}
> > > +
> > > +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
> > > +{
> > > +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> > > +     unsigned int size = stats->vfmt.fmt.meta.buffersize;
> > > +
> > > +     if (vb2_plane_size(vb, 0) < size) {
> > > +             dev_err(stats->isp->dev,
> > > +                     "User buffer too small (%ld < %u)\n",
> > > +                     vb2_plane_size(vb, 0), size);
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     vb2_set_plane_payload(vb, 0, size);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
> > > +{
> > > +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> > > +     struct c3_isp_vb2_buffer *buf =
> > > +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
> > > +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
> > > +
> > > +     buf->vaddr = vb2_plane_vaddr(vb, 0);
> > > +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
> > > +
> > > +     memset(buf->vaddr, 0, stats->vfmt.fmt.meta.buffersize);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int c3_isp_stats_vb2_start_streaming(struct vb2_queue *q,
> > > +                                         unsigned int count)
> > > +{
> > > +     struct c3_isp_stats *stats = vb2_get_drv_priv(q);
> > > +     int ret;
> > > +
> > > +     guard(mutex)(&stats->isp->lock);
> > > +
> > > +     ret = pm_runtime_resume_and_get(stats->isp->dev);
> > > +     if (ret)
> > > +             return ret;
> > > +
> > > +     ret = video_device_pipeline_start(&stats->vdev, &stats->isp->pipe);
> > > +     if (ret) {
> > > +             dev_err(stats->isp->dev,
> > > +                     "Failed to start stats pipeline: %d\n", ret);
> > > +             goto err_pm_put;
> > > +     }
> > > +
> > > +     if (c3_isp_pipeline_ready(stats->isp)) {
> > > +             ret = v4l2_subdev_enable_streams(&stats->isp->core.sd,
> > > +                                              C3_ISP_CORE_PAD_SOURCE_STATS,
> > > +                                              BIT(0));
> > > +             if (ret)
> > > +                     goto err_pipeline_stop;
> > > +     }
> > > +
> > > +     c3_isp_stats_start(stats);
> > > +
> > > +     return 0;
> > > +
> > > +err_pipeline_stop:
> > > +     video_device_pipeline_stop(&stats->vdev);
> > > +err_pm_put:
> > > +     pm_runtime_put(stats->isp->dev);
> > > +     c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_QUEUED);
> > > +     return ret;
> > > +}
> > > +
> > > +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
> > > +{
> > > +     struct c3_isp_stats *stats = vb2_get_drv_priv(q);
> > > +
> > > +     guard(mutex)(&stats->isp->lock);
> > > +
> > > +     c3_isp_stats_stop(stats);
> > > +     c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_ERROR);
> > > +
> > > +     if (stats->isp->pipe.start_count == 1)
> > > +             v4l2_subdev_disable_streams(&stats->isp->core.sd,
> > > +                                         C3_ISP_CORE_PAD_SOURCE_STATS,
> > > +                                         BIT(0));
> > > +
> > > +     video_device_pipeline_stop(&stats->vdev);
> > > +     pm_runtime_put(stats->isp->dev);
> > > +}
> > > +
> > > +static const struct vb2_ops isp_stats_vb2_ops = {
> > > +     .queue_setup = c3_isp_stats_vb2_queue_setup,
> > > +     .buf_queue = c3_isp_stats_vb2_buf_queue,
> > > +     .buf_prepare = c3_isp_stats_vb2_buf_prepare,
> > > +     .buf_init = c3_isp_stats_vb2_buf_init,
> > > +     .wait_prepare = vb2_ops_wait_prepare,
> > > +     .wait_finish = vb2_ops_wait_finish,
> > > +     .start_streaming = c3_isp_stats_vb2_start_streaming,
> > > +     .stop_streaming = c3_isp_stats_vb2_stop_streaming,
> > > +};
> > > +
> > > +int c3_isp_stats_register(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_stats *stats = &isp->stats;
> > > +     struct video_device *vdev = &stats->vdev;
> > > +     struct vb2_queue *vb2_q = &stats->vb2_q;
> > > +     int ret;
> > > +
> > > +     memset(stats, 0, sizeof(*stats));
> > > +     stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
> > > +     stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_info);
> > > +     stats->isp = isp;
> > > +     INIT_LIST_HEAD(&stats->pending);
> > > +     spin_lock_init(&stats->buff_lock);
> > > +
> > > +     mutex_init(&stats->lock);
> > > +
> > > +     snprintf(vdev->name, sizeof(vdev->name), "isp-stats");
> > > +     vdev->fops = &isp_stats_v4l2_fops;
> > > +     vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
> > > +     vdev->v4l2_dev = &isp->v4l2_dev;
> > > +     vdev->lock = &stats->lock;
> > > +     vdev->minor = -1;
> > > +     vdev->queue = vb2_q;
> > > +     vdev->release = video_device_release_empty;
> > > +     vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
> > > +     vdev->vfl_dir = VFL_DIR_RX;
> > > +     video_set_drvdata(vdev, stats);
> > > +
> > > +     vb2_q->drv_priv = stats;
> > > +     vb2_q->mem_ops = &vb2_dma_contig_memops;
> > > +     vb2_q->ops = &isp_stats_vb2_ops;
> > > +     vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
> > > +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
> > > +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> > > +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
> > > +     vb2_q->dev = isp->dev;
> > > +     vb2_q->lock = &stats->lock;
> > > +     vb2_q->min_queued_buffers = 2;
> > > +
> > > +     ret = vb2_queue_init(vb2_q);
> > > +     if (ret)
> > > +             goto err_destroy;
> > > +
> > > +     stats->pad.flags = MEDIA_PAD_FL_SINK;
> > > +     ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
> > > +     if (ret)
> > > +             goto err_queue_release;
> > > +
> > > +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> > > +     if (ret) {
> > > +             dev_err(isp->dev,
> > > +                     "Failed to register %s: %d\n", vdev->name, ret);
> > > +             goto err_entity_cleanup;
> > > +     }
> > > +
> > > +     return 0;
> > > +
> > > +err_entity_cleanup:
> > > +     media_entity_cleanup(&vdev->entity);
> > > +err_queue_release:
> > > +     vb2_queue_release(vb2_q);
> > > +err_destroy:
> > > +     mutex_destroy(&stats->lock);
> > > +     return ret;
> > > +}
> > > +
> > > +void c3_isp_stats_unregister(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_stats *stats = &isp->stats;
> > > +
> > > +     vb2_queue_release(&stats->vb2_q);
> > > +     media_entity_cleanup(&stats->vdev.entity);
> > > +     video_unregister_device(&stats->vdev);
> > > +     mutex_destroy(&stats->lock);
> > > +}
> > > +
> > > +int c3_isp_stats_done(struct c3_isp_device *isp)
> > > +{
> > > +     struct c3_isp_stats *stats = &isp->stats;
> > > +     struct c3_isp_vb2_buffer *buff = stats->buff;
> > > +     unsigned long flags;
> > > +
> > > +     if (!stats->is_streaming)
> > > +             return -EINVAL;
> > > +
> > > +     spin_lock_irqsave(&stats->buff_lock, flags);
> > > +
> > > +     if (buff) {
> > > +             buff->vb.sequence = stats->isp->frm_sequence;
> > > +             buff->vb.vb2_buf.timestamp = ktime_get();
> > > +             buff->vb.field = V4L2_FIELD_NONE;
> > > +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
> > > +     }
> > > +
> > > +     c3_isp_stats_cfg_buff(stats);
> > > +
> > > +     spin_unlock_irqrestore(&stats->buff_lock, flags);
> > > +
> > > +     return 0;
> > > +}
> > > diff --git a/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
> > > new file mode 100644
> > > index 000000000000..84ff5741357a
> > > --- /dev/null
> > > +++ b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
> > > @@ -0,0 +1,537 @@
> > > +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
> > > +/*
> > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > + */
> > > +
> > > +#ifndef __C3_ISP_CONFIG_H__
> > > +#define __C3_ISP_CONFIG_H__
> > When moving to include/uapi/linux/media/amlogic prefix this with
> > _UAPI_
> >
>
> OK, will add "_UAPI_"
>
> > > +
> > > +#define AF_STAT_BLKH_NUM             17
> > > +#define AF_STAT_BLKV_NUM             15
> > > +#define AF_STAT_BLK_NUM              (AF_STAT_BLKH_NUM * AF_STAT_BLKV_NUM)
> > > +/* AF stats block size need to be aligned with 2 */
> > > +#define AF_STAT_BLK_SIZE             ALIGN(AF_STAT_BLK_NUM, 2)
> > > +#define AE_HISTOGRAM_SIZE         1024
> > > +#define AE_STAT_BLKH_NUM             17
> > > +#define AE_STAT_BLKV_NUM             15
> > > +#define AE_STAT_BLK_NUM              (AE_STAT_BLKH_NUM * AE_STAT_BLKV_NUM)
> > > +/* AE stats block size need to be aligned with 2 */
> > > +#define AE_STAT_BLK_SIZE             ALIGN(AE_STAT_BLK_NUM, 2)
> > > +#define AE_BLOCK_WT_NUM              255
> > > +#define AE_BLK_WT_DATA_NUM_OF_GROUP  8
> > > +#define AWB_STAT_BLKH_NUM            32
> > > +#define AWB_STAT_BLKV_NUM            24
> > > +#define AWB_STAT_BLK_NUM             (AWB_STAT_BLKH_NUM * AWB_STAT_BLKV_NUM)
> > > +/* AWB stats block size need to be aligned with 2 */
> > > +#define AWB_STAT_BLK_SIZE            ALIGN(AWB_STAT_BLK_NUM, 2)
> > > +#define AWB_BLOCK_WT_NUM             768
> > > +#define AWB_BLK_WT_DATA_NUM_OF_GROUP 8
> > > +#define AWB_STAT_BLC20_NUM           4
> > > +#define AWB_STAT_GAIN10_NUM          4
> > > +#define BLC_OFFSET_NUM               5
> > > +#define GAMMA_LUT_GROUP_NUM          4
> > > +#define GAMMA_LUT_POINT_NUM          129
> > > +#define GAMMA_LUT_DATA_NUM_OF_GROUP  2
> > > +
> > > +/**
> > > + * struct awb_zone_stats - AWB statistics of a block
> > > + *
> > > + * AWB zone stats is aligned with 8 bytes
> > > + *
> > > + * @rg: the ratio of R / G in a zone
> > > + * @bg: the ratio of B / G in a zone
> > > + * @pixel_sum: the total number of pixels used in a zone
> > > + */
> > > +struct awb_zone_stats {
> > > +     u16 rg;
> > > +     u16 bg;
> > > +     u32 pixel_sum;
> > > +};
> > > +
> > > +/**
> > > + * struct awb_stats_info - Auto white balance statistics information.
> > > + *
> > > + * AWB statistical information of all blocks.
> > > + *
> > > + * @awb_stats: array of auto white balance statistics
> > > + */
> > > +struct awb_stats_info {
> > > +     struct awb_zone_stats awb_stats[AWB_STAT_BLK_SIZE];
> > > +};
> > > +
> > > +/**
> > > + * struct ae_zone_stats - AE statistics of a block
> > > + *
> > > + * AE zone stats is aligned with 8 bytes.
> > > + * This is a 5-bin histogram and the total sum is
> > > + * normalized to 0xffff.
> > > + * So hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4)
> > > + *
> > > + * @hist0: the global normalized pixel count for bin 0
> > > + * @hist1: the global normalized pixel count for bin 1
> > > + * @hist3: the global normalized pixel count for bin 3
> > > + * @hist4: the global normalized pixel count for bin 4
> > > + */
> > > +struct ae_zone_stats {
> > > +     u16 hist0;
> > Should you include  <linux/types.h> and use types prefixed with __
> > (__u16) ?
> >
>
> OK, will include <linux/types.h>  and use prefix "__".
>
> > > +     u16 hist1;
> > > +     u16 hist3;
> > > +     u16 hist4;
> > > +};
> > > +
> > > +/**
> > > + * struct ae_stats_info - Exposure statistics information
> > > + *
> > > + * AE statistical information consists of
> > > + * all blocks information and a 1024-bin histogram.
> > > + *
> > > + * @ae_stats: array of auto exposure block statistics
> > > + * @hist: a 1024-bin histogram for the entire image
> > > + */
> > > +struct ae_stats_info {
> > > +     struct ae_zone_stats ae_stats[AE_STAT_BLK_SIZE];
> > > +     u32 hist[AE_HISTOGRAM_SIZE];
> > > +};
> > > +
> > > +/**
> > > + * struct af_zone_stats - AF statistics of a block
> > > + *
> > > + * AF block stats is aligned with 8 bytes.
> > > + * The zonal accumulated contrast metrics are stored
> > > + * in floating point format with 16 bits mantissa and
> > > + * 5 or 6 bits exponent.
> > > + * Apart from contrast metrics we accumulate squared image and
> > > + * quartic image data over the zone.
> > > + *
> > > + * @i2_mat: the mantissa of zonal squared image pixel sum
> > > + * @i4_mat: the mantissa of zonal quartic image pixel sum
> > > + * @e4_mat: the mantissa of zonal multi-directional quartic edge sum
> > > + * @i2_exp: the exponent of zonal squared image pixel sum
> > > + * @i4_exp: the exponent of zonal quartic image pixel sum
> > > + * @e4_exp: the exponent of zonal multi-directional quartic edge sum
> > > + */
> > > +struct af_zone_stats {
> > > +     u16 i2_mat;
> > > +     u16 i4_mat;
> > > +     u16 e4_mat;
> > > +     u16 i2_exp: 5;
> > > +     u16 i4_exp: 6;
> > > +     u16 e4_exp: 5;
> > > +};
> > > +
> > > +/**
> > > + * struct af_stats_info - Auto Focus statistics information
> > > + *
> > > + * AF statistical information of each block
> > > + *
> > > + * @af_stats: array of auto focus block statistics
> > > + */
> > > +struct af_stats_info {
> > > +     struct af_zone_stats af_stats[AF_STAT_BLK_SIZE];
> > > +};
> > > +
> > > +/**
> > > + * struct c3_isp_stats_info - V4L2_META_FMT_C3ISP_STATS
> > > + *
> > > + * Contains ISP statistics
> > > + *
> > > + * @awb_stats: auto white balance stats
> > > + * @ae_stats: auto exposure stats
> > > + * @af_stats: auto focus stats
> > > + */
> > > +struct c3_isp_stats_info {
> > > +     struct awb_stats_info awb_stats;
> > > +     struct ae_stats_info ae_stats;
> > > +     struct af_stats_info af_stats;
> > > +};
> > > +
> > > +/**
> > > + * enum c3_isp_param_buffer_version -  C3 ISP parameters block versioning
> > > + *
> > > + * @C3_ISP_PARAM_BUFFER_V0: First version of C3 ISP parameters block
> > > + */
> > > +enum c3_isp_param_buffer_version {
> > > +     C3_ISP_PARAM_BUFFER_V0,
> > > +};
> > > +
> > > +/**
> > > + * enum c3_isp_param_block_type - Enumeration of C3 ISP parameter blocks
> > > + *
> > > + * Each block configures a specific processing block of the C3 ISP.
> > > + * The block type allows the driver to correctly interpret
> > > + * the parameters block data.
> > > + *
> > > + * @C3_ISP_PARAM_BLOCK_WB_CHANGE: White balance change parameters
> > > + * @C3_ISP_PARAM_BLOCK_WB_LUMA: White balance Luma-based parameters
> > > + * @C3_ISP_PARAM_BLOCK_WB_TRIANGLE: White balance triangle parameters
> > > + * @C3_ISP_PARAM_BLOCK_AWB_STATS: AWB statistics parameters
> > > + * @C3_ISP_PARAM_BLOCK_AE_STATS: AE statistics parameters
> > > + * @C3_ISP_PARAM_BLOCK_AF_STATS: AF statistics parameters
> > > + * @C3_ISP_PARAM_BLOCK_PST_GAMMA: post gamma parameters
> > > + * @C3_ISP_PARAM_BLOCK_DMSC: demosaic parameters
> > > + * @C3_ISP_PARAM_BLOCK_CCM: Color correction matrix parameters
> > > + * @C3_ISP_PARAM_BLOCK_CSC: Color space conversion parameters
> > > + * @C3_ISP_PARAM_BLOCK_BLC: Black level correction parameters
> > > + * @C3_ISP_PARAM_BLOCK_SENTINEL: First non-valid block index
> > > + */
> > > +enum c3_isp_param_block_type {
> > > +     C3_ISP_PARAM_BLOCK_WB_CHANGE,
> > > +     C3_ISP_PARAM_BLOCK_WB_LUMA,
> > > +     C3_ISP_PARAM_BLOCK_WB_TRIANGLE,
> > > +     C3_ISP_PARAM_BLOCK_AWB_STATS,
> > > +     C3_ISP_PARAM_BLOCK_AE_STATS,
> > > +     C3_ISP_PARAM_BLOCK_AF_STATS,
> > > +     C3_ISP_PARAM_BLOCK_PST_GAMMA,
> > > +     C3_ISP_PARAM_BLOCK_DMSC,
> > > +     C3_ISP_PARAM_BLOCK_CCM,
> > > +     C3_ISP_PARAM_BLOCK_CSC,
> > > +     C3_ISP_PARAM_BLOCK_BLC,
> > > +     C3_ISP_PARAM_BLOCK_SENTINEL
> > > +};
> > > +
> > > +/**
> > > + * struct c3_isp_param_block_header - C3 ISP parameter block header
> > > + *
> > > + * This structure represents the common part of all the ISP configuration
> > > + * blocks. Each parameters block shall embed an instance of this structure type
> > > + * as its first member, followed by the block-specific configuration data. The
> > > + * driver inspects this common header to discern the block type and its size and
> > > + * properly handle the block content by casting it to the correct block-specific
> > > + * type.
> > > + *
> > > + * @type: The parameters block type (enum c3_isp_param_block_type)
> > > + * @enabled: Block enabled/disabled flag
> > > + * @size: Size (in bytes) of parameters block
> > > + */
> > > +
> > > +struct c3_isp_param_block_header {
> > > +     enum c3_isp_param_block_type type;
> > > +     bool enabled;
> > > +     size_t size;
> > > +};
> > What is the size of this structure ? Is it aligned or does the
> > compiler inserts padding bytes ? In general, I would try to align
> > everything to 8 bytes to avoid the compiler inserting padding bytes.
> >
> > A tool that can help you identifies holes is pahole. Just write a
> > small userspace program that includes types from this header and
> > declare a variable of each type of defined in this header. Pass the
> > executable to pahole and it will show the memory layout of each
> > member.
> >
>
> Will check this issue.
>
> > > +
> > > +/**
> > > + * struct wb_change_cfg - White Balance configuration
> > > + *
> > > + * @header: The C3 ISP parameters block header
> > > + * @wb_gain: white balance gain of each color
> > > + *   wb_gain[0]: Gr gain, range 0~0xfff
> > > + *   wb_gain[1]: R gain, range 0~0xfff
> > > + *   wb_gain[2]: B gain, range 0~0xfff
> > > + *   wb_gain[3]: Gb gain, range 0~0xfff
> > > + *   wb_gain[4]: Ir gain, range 0~0xfff
> > > + * @wb_limit: white balance limit of each color
> > > + *   wb_limit[0]: Gr limit, 16 bits float
> > > + *   wb_limit[1]: R limit, 16 bits float
> > > + *   wb_limit[2]: B limit, 16 bits float
> > > + *   wb_limit[3]: Gb limit, 16 bits float
> > > + *   wb_limit[4]: Ir limit, 16 bits float
> > > + * @ae_gain_grbgi: Gain of each color before blending to luma
> > > + *   ae_gain_grbgi[0]: Gr gain, range 0~255
> > > + *   ae_gain_grbgi[1]: R gain, range 0~255
> > > + *   ae_gain_grbgi[2]: B gain, range 0~255
> > > + *   ae_gain_grbgi[3]: Gb gain, range 0~255
> > > + *   ae_gain_grbgi[4]: Ir gain, range 0~255
> > > + * @ae_bl12_grbgi: Offset of each color before ae_gain_grbgi
> > > + *   ae_bl12_grbgi[0]: Gr offset, range 0~4095
> > > + *   ae_bl12_grbgi[1]: R offset, range 0~4095
> > > + *   ae_bl12_grbgi[2]: B offset, range 0~4095
> > > + *   ae_bl12_grbgi[3]: Gb offset, range 0~4095
> > > + *   ae_bl12_grbgi[4]: Ir offset, range 0~4095
> > > + */
> > > +struct wb_change_cfg {
> > > +     struct c3_isp_param_block_header header;
> > > +     u32 wb_gain[5];
> > > +     u32 wb_limit[5];
> > > +     u32 ae_gain_grbgi[5];
> > > +     u32 ae_bl12_grbgi[5];
> > > +};
> > > +
> > > +/**
> > > + * struct wb_luma_cfg - White Balance Luma-based configuration
> > > + *
> > > + * @header: The C3 ISP parameters block header
> > > + * @awb_stat_blc20: BLC in AWB statistic
> > > + *   awb_stat_blc20[0]: Gr blc, range 0~0xfffff
> > > + *   awb_stat_blc20[1]: R blc, range 0~0xfffff
> > > + *   awb_stat_blc20[2]: B blc, range 0~0xfffff
> > > + *   awb_stat_blc20[3]: Gb blc, range 0~0xfffff
> > > + * @awb_stat_gain10: Gain in AWB statistic
> > > + *   awb_stat_gain10[0]: Gr gain, range 0~1023
> > > + *   awb_stat_gain10[1]: R gain, range 0~1023
> > > + *   awb_stat_gain10[2]: B gain, range 0~1023
> > > + *   awb_stat_gain10[3]: Gb gain, range 0~1023
> > > + * @awb_stat_satur_low: AWB statistic under-saturation threshold
> > > + *   value: range 0~65535
> > > + * @awb_stat_satur_high: AWB statistic over-saturation threshold
> > > + *   value: range 0~65535
> > > + */
> > > +struct wb_luma_cfg {
> > > +     struct c3_isp_param_block_header header;
> > > +     u32 awb_stat_blc20[AWB_STAT_BLC20_NUM];
> > > +     u32 awb_stat_gain10[AWB_STAT_GAIN10_NUM];
> > > +     u32 awb_stat_satur_low;
> > > +     u32 awb_stat_satur_high;
> > > +};
> > > +
> > > +/**
> > > + * struct wb_triangle_cfg - White Balance Triangle
> > > + *
> > > + * @header: The C3 ISP parameters block header
> > > + * @awb_stat_satur_vald: AWB statistic over saturation control
> > > + *   value: 0: disable, 1: enable
> > > + * @awb_stat_rg_min: min value of r/g
> > > + *   value: 0~4095
> > > + * @awb_stat_rg_max: max value of r/g
> > > + *   value: 0~4095
> > > + * @awb_stat_bg_min: min value of b/g
> > > + *   value: 0~4095
> > > + * @awb_stat_bg_max: max value of b/g
> > > + *   value: 0~4095
> > > + * @awb_stat_rg_low: low value of r/g
> > > + *   value: 0~4095
> > > + * @awb_stat_rg_high: high value of r/g
> > > + *   value: 0~4095
> > > + * @awb_stat_bg_low: low value of b/g
> > > + *   value: 0~4095
> > > + * @awb_stat_bg_high: high value of b/g
> > > + *   value: 0~4095
> > > + */
> > > +struct wb_triangle_cfg {
> > > +     struct c3_isp_param_block_header header;
> > > +     u32 awb_stat_satur_vald;
> > > +     u32 awb_stat_rg_min;
> > > +     u32 awb_stat_rg_max;
> > > +     u32 awb_stat_bg_min;
> > > +     u32 awb_stat_bg_max;
> > > +     u32 awb_stat_rg_low;
> > > +     u32 awb_stat_rg_high;
> > > +     u32 awb_stat_bg_low;
> > > +     u32 awb_stat_bg_high;
> > > +};
> > > +
> > > +/**
> > > + * struct awb_stats_cfg - AWB statistics configuration
> > > + *
> > > + * This structure contains AWB statistics control information.
> > > + *
> > > + * @header: The C3 ISP parameters block header
> > > + * @awb_stat_switch: the switch of AWB statistics
> > > + *   value: 0~7
> > > + * @awb_stat_blk_weight: Array of weights for AWB statistics blocks
> > > + *   value: 0~15
> > > + */
> > > +struct awb_stats_cfg {
> > > +     struct c3_isp_param_block_header header;
> > > +     u8 awb_stat_switch;
> > > +     u32 awb_stat_blk_weight[AWB_BLOCK_WT_NUM];
> > > +};
> > > +
> > > +/**
> > > + * struct ae_stats_cfg - AE statistics configuration
> > > + *
> > > + * This structure contains AE statistics control information.
> > > + *
> > > + * @header: The C3 ISP parameters block header
> > > + * @ae_stat_switch: the switch of AE statistics
> > > + *   value: 0~3
> > > + * @ae_stat_blk_weight: Array of weights for AE statistics blocks
> > > + *   value: 0~15
> > > + */
> > > +struct ae_stats_cfg {
> > > +     struct c3_isp_param_block_header header;
> > > +     u8 ae_stat_switch;
> > > +     u32 ae_stat_blk_weight[AE_BLOCK_WT_NUM];
> > > +};
> > > +
> > > +/**
> > > + * struct af_stats_cfg - AF statistics configuration
> > > + *
> > > + * This structure contains AF statistics control information.
> > > + *
> > > + * @header: The C3 ISP parameters block header
> > > + * @af_stat_switch: the switch of AF statistics
> > > + *   value: 0~3
> > > + */
> > > +struct af_stats_cfg {
> > > +     struct c3_isp_param_block_header header;
> > > +     u8 af_stat_switch;
> > > +};
> > > +
> > > +/**
> > > + * struct pst_gamma_cfg - Post gamma configuration
> > > + *
> > > + * This structure contains post gamma parameters
> > > + *
> > > + * @header: The C3 ISP parameters block header
> > > + * @pst_gamma_lut: LUT for P-Stitch gamma
> > > + *   value: 0~65535
> > > + */
> > > +struct pst_gamma_cfg {
> > > +     struct c3_isp_param_block_header header;
> > > +     u32 pst_gamma_lut[GAMMA_LUT_GROUP_NUM][GAMMA_LUT_POINT_NUM];
> > > +};
> > > +
> > > +/**
> > > + * struct dmsc_cfg - Demosaic configuration
> > > + *
> > > + * This structure contains demosaic parameters
> > > + *
> > > + * @header: The C3 ISP parameters block header
> > > + */
> > > +struct dmsc_cfg {
> > > +     struct c3_isp_param_block_header header;
> > > +};
> > > +
> > > +/**
> > > + * struct ccm_cfg - ISP CCM configuration
> > > + *
> > > + * This structure holds the parameters for configuring the CCM,
> > > + * which is used for color correction.
> > > + *
> > > + * @header: The C3 ISP parameters block header
> > > + * @ccm_4x3matrix: A 3x4 matrix used for color correction
> > > + *   value: 0~8191
> > > + */
> > > +struct ccm_cfg {
> > > +     struct c3_isp_param_block_header header;
> > > +     u32 ccm_4x3matrix[3][4];
> > > +};
> > > +
> > > +/**
> > > + * struct csc_cfg - ISP Color Space Conversion configuration
> > > + *
> > > + * This structure contains settings for color space conversion.
> > > + *
> > > + * @header: The C3 ISP parameters block header
> > > + * @cm0_offset_inp: Input offset values for the 0-order color matrix
> > > + *   value: 0~8191
> > > + * @cm0_offset_oup: Output offset values for the 0-order color matrix
> > > + *   value: 0~8191
> > > + * @cm0_3x3mtrx_rs: matrix right shift for cm0
> > > + *   value: 0~3
> > > + * @cm0_3x3matrix: A 3x3 matrix used for the color matrix
> > > + *   value: 0~8191
> > > + */
> > > +struct csc_cfg {
> > > +     struct c3_isp_param_block_header header;
> > > +     u32 cm0_offset_inp[3];
> > > +     u32 cm0_offset_oup[3];
> > > +     u32 cm0_3x3mtrx_rs;
> > > +     u32 cm0_3x3matrix[3][3];
> > > +};
> > > +
> > > +/**
> > > + * struct blc_cfg - ISP Black Level Correction (BLC) configuration
> > > + *
> > > + * This structure holds the parameters for BLC in image processing.
> > > + *
> > > + * @header: The C3 ISP parameters block header
> > > + * @fe_bl_ofst: Array of front-end BLC offsets for each color each channels
> > > + *   fe_bl_ofst[0]: Gr blc offset, range 0~0x1fffff
> > > + *   fe_bl_ofst[1]: R blc offset, range 0~0x1fffff
> > > + *   fe_bl_ofst[2]: B blc offset, range 0~0x1fffff
> > > + *   fe_bl_ofst[3]: Gb blc offset, range 0~0x1fffff
> > > + *   fe_bl_ofst[4]: Ir blc offset, range 0~0x1fffff
> > > + * @blc_ofst: Array of LSWB BLC offsets
> > > + *   blc_ofst[0]: Gr blc offset, 16 bits float
> > > + *   blc_ofst[1]: R blc offset, 16 bits float
> > > + *   blc_ofst[2]: B blc offset, 16 bits float
> > > + *   blc_ofst[3]: Gb blc offset, 16 bits float
> > > + *   blc_ofst[4]: Ir blc offset, 16 bits float
> > > + */
> > > +struct blc_cfg {
> > > +     struct c3_isp_param_block_header header;
> > > +     u32 fe_bl_ofst[BLC_OFFSET_NUM];
> > > +     u32 blc_ofst[BLC_OFFSET_NUM];
> > > +};
> > > +
> > > +/**
> > > + * define C3_ISP_PARAMS_MAX_SIZE - Maximum size of all C3 ISP Parameters
> > > + *
> > > + * Though the parameters for the C3 ISP are passed as optional blocks, the
> > > + * driver still needs to know the absolute maximum size so that it can allocate
> > > + * a buffer sized appropriately to accommodate userspace attempting to set all
> > > + * possible parameters in a single frame.
> > > + */
> > > +#define C3_ISP_PARAMS_MAX_SIZE                 \
> > > +     (sizeof(struct wb_change_cfg) +        \
> > > +     sizeof(struct wb_luma_cfg)   +         \
> > > +     sizeof(struct wb_triangle_cfg) +       \
> > > +     sizeof(struct awb_stats_cfg) +          \
> > > +     sizeof(struct ae_stats_cfg) +           \
> > > +     sizeof(struct af_stats_cfg) +           \
> > > +     sizeof(struct pst_gamma_cfg) +         \
> > > +     sizeof(struct dmsc_cfg) +              \
> > > +     sizeof(struct ccm_cfg) +               \
> > > +     sizeof(struct csc_cfg) +               \
> > > +     sizeof(struct blc_cfg))
> > > +
> > > +/**
> > > + * struct c3_isp_params_buffer - C3 ISP configuration parameters
> > > + *
> > > + * This struct contains the configuration parameters of the C3 ISP
> > > + * algorithms, serialized by userspace into an opaque data buffer. Each
> > > + * configuration parameter block is represented by a block-specific structure
> > > + * which contains a :c:type:`c3_isp_param_block_header` entry as first
> > > + * member. Userspace populates the @data buffer with configuration parameters
> > > + * for the blocks that it intends to configure. As a consequence, the data
> > > + * buffer effective size changes according to the number of ISP blocks that
> > > + * userspace intends to configure.
> > > + *
> > > + * The parameters buffer is versioned by the @version field to allow modifying
> > > + * and extending its definition. Userspace should populate the @version field to
> > > + * inform the driver about the version it intends to use. The driver will parse
> > > + * and handle the @data buffer according to the data layout specific to the
> > > + * indicated revision and return an error if the desired revision is not
> > > + * supported.
> > > + *
> > > + * For each ISP block that userspace wants to configure, a block-specific
> > > + * structure is appended to the @data buffer, one after the other without gaps
> > > + * in between nor overlaps. Userspace shall populate the @total_size field with
> > > + * the effective size, in bytes, of the @data buffer.
> > > + *
> > > + * The expected memory layout of the parameters buffer is::
> > > + *
> > > + *   +-------------------- struct c3_isp_params_buffer ------------------+
> > > + *   | version = C3_ISP_PARAM_BUFFER_V0;                                   |
> > > + *   | total_size = sizeof(sizeof(struct wb_change_cfg))                   |
> > > + *   |              sizeof(sizeof(struct wb_luma_cfg));                    |
> > > + *   | +------------------------- data  ---------------------------------+ |
> > > + *   | | +------------------ struct wb_change_cfg) --------------------+ | |
> > > + *   | | | +---------  struct c3_isp_param_block_header header  -----+ | | |
> > > + *   | | | | type = C3_ISP_PARAM_BLOCK_WB_CHANGE;                    | | | |
> > > + *   | | | | enabled = true;                                            | | | |
> > > + *   | | | | size =                                                  | | | |
> > > + *   | | | |    sizeof(struct c3_isp_param_block_header header);     | | | |
> > > + *   | | | +---------------------------------------------------------+ | | |
> > > + *   | | | wb_gain[5] = ...;                                           | | |
> > > + *   | | | wb_limit[5] = ...;                                          | | |
> > > + *   | | | ae_gain_grbgi[5] = ...;                                     | | |
> > > + *   | | | ae_bl12_grbgi[5] = ...;                                     | | |
> > > + *   | | +------------------ struct wb_luma_cfg -----------------------+ | |
> > > + *   | | | +---------- struct c3_isp_param_block_header header ------+ | | |
> > > + *   | | | | type = C3_ISP_PARAM_BLOCK_WB_LUMA;                      | | | |
> > > + *   | | | | enabled = true;                                            | | | |
> > > + *   | | | | size = sizeof(struct wb_luma_cfg);                      | | | |
> > > + *   | | | +---------------------------------------------------------+ | | |
> > > + *   | | | awb_stat_blc20[4] = ...;                                    | | |
> > > + *   | | | awb_stat_gain10[4] = ...;                                   | | |
> > > + *   | | | awb_stat_satur_low = ...;                                   | | |
> > > + *   | | | awb_stat_satur_high = ...;                                  | | |
> > > + *   | | +-------------------------------------------------------------+ | |
> > > + *   | +-----------------------------------------------------------------+ |
> > > + *   +---------------------------------------------------------------------+
> > > + *
> > > + * @version: The C3 ISP parameters buffer version
> > > + * @total_size: The C3 ISP configuration data effective size,
> > > + *           excluding this header
> > > + * @data: The C3 ISP configuration blocks data
> > > + */
> > > +struct c3_isp_params_buffer {
> > > +     enum c3_isp_param_buffer_version version;
> > > +     size_t total_size;
> > > +     u8 data[C3_ISP_PARAMS_MAX_SIZE];
> > > +};
> > > +
> > > +#endif
> > >
> > > --
> > > 2.46.1
> > >
> > >
> > >

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

* Re: [PATCH v3 7/9] media: platform: Add c3 ISP driver
  2024-11-08 14:04       ` Jacopo Mondi
@ 2024-11-11  3:00         ` Keke Li
  2024-11-11  7:50           ` Jacopo Mondi
  0 siblings, 1 reply; 37+ messages in thread
From: Keke Li @ 2024-11-11  3:00 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-media, devicetree, linux-kernel,
	kieran.bingham, laurent.pinchart, dan.scally

Hi Jacopo

Thanks very much for your reply.

On 2024/11/8 22:04, Jacopo Mondi wrote:
> [ EXTERNAL EMAIL ]
>
> Hi Keke
>
> On Fri, Nov 08, 2024 at 08:34:41PM +0800, Keke Li wrote:
>> Hi Jacopo
>>
>> Thanks very much for your reply.
>>
>> On 2024/11/8 00:03, Jacopo Mondi wrote:
>>> [ EXTERNAL EMAIL ]
>>>
>>> Hi Keke
>>>
>>>      a first pass of review without going into details about the
>>> ISP parameters and stats but mostly on architecture.
>>>
>>> On Wed, Sep 18, 2024 at 02:07:18PM +0800, Keke Li via B4 Relay wrote:
>>>> From: Keke Li <keke.li@amlogic.com>
>>>>
>>>> The C3 ISP supports multi-camera and muti-exposure
>>>> high dynamic range (HDR). It brings together some
>>>> advanced imaging technologies to provide good image quality.
>>>> This driver mainly responsible for driving ISP pipeline
>>>> to process raw image.
>>>>
>>>> Signed-off-by: Keke Li <keke.li@amlogic.com>
>>>> ---
>>>>    drivers/media/platform/amlogic/Kconfig             |   1 +
>>>>    drivers/media/platform/amlogic/Makefile            |   1 +
>>>>    drivers/media/platform/amlogic/c3-isp/Kconfig      |  17 +
>>>>    drivers/media/platform/amlogic/c3-isp/Makefile     |  10 +
>>>>    .../media/platform/amlogic/c3-isp/c3-isp-capture.c | 759 ++++++++++++++++++
>>>>    .../media/platform/amlogic/c3-isp/c3-isp-common.h  | 327 ++++++++
>>>>    .../media/platform/amlogic/c3-isp/c3-isp-core.c    | 675 ++++++++++++++++
>>>>    drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c | 486 ++++++++++++
>>>>    .../media/platform/amlogic/c3-isp/c3-isp-params.c  | 857 +++++++++++++++++++++
>>>>    .../media/platform/amlogic/c3-isp/c3-isp-regs.h    | 683 ++++++++++++++++
>>>>    .../media/platform/amlogic/c3-isp/c3-isp-resizer.c | 768 ++++++++++++++++++
>>>>    .../media/platform/amlogic/c3-isp/c3-isp-stats.c   | 488 ++++++++++++
>>>>    .../amlogic/c3-isp/include/uapi/c3-isp-config.h    | 537 +++++++++++++
>>> This really should be in include/uapi/linux/media/amlogic
>>> so that it is made available to userspace.
>>
>> OK. will move c3-isp-config.h to include/uapi/linux/media/amlogic
>>
>>>>    13 files changed, 5609 insertions(+)
>>>>
>>>> diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
>>>> index df09717b28d0..ebda6b7edc2d 100644
>>>> --- a/drivers/media/platform/amlogic/Kconfig
>>>> +++ b/drivers/media/platform/amlogic/Kconfig
>>>> @@ -2,6 +2,7 @@
>>>>
>>>>    comment "Amlogic media platform drivers"
>>>>
>>>> +source "drivers/media/platform/amlogic/c3-isp/Kconfig"
>>>>    source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
>>>>    source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>>> I start wondering if this shouldn't rather be organized in an
>>> drivers/media/platform/amlogic/c3/ folder with an
>>> drivers/media/platform/amlogic/c3/isp/ subfolder
>>>
>>> Do you know if any of the csi-2-rx, adapter or ISP can
>>> be used in other SoCs ?
>>>
>> Yes, csi-2-rx, adapter and ISP will be used in T7 SoC in the future.
>>
>> Is there need to create "drivers/media/platform/amlogic/c3/" in this case?
>>
>> How to handle this case?
>>
> I see... well, you have the c3- prefix already in each of these
> drivers, so having a c3/ folder isn't a problem even if all three
> components will be used on another SoC.
>
> It would be different if, in example, the CSI-2_RX and Adapt are used
> in C3 only, but the ISP is used in multiple modules.
>
> At this point is up to you. If you keep the c3- prefix in all modules
> I would rather move them to a c3/ subfolder to avoid conflicts when
> you will add a different generation to the media/platform/amlogic
> folder.
>
> Up to you ;)
>

OK, will create "c3" directory in "drivers/media/platform/amlogic/" and 
drop the "c3-" prefix.

>>>>    source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
>>>> diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
>>>> index b370154b090c..d0d9363d4d8d 100644
>>>> --- a/drivers/media/platform/amlogic/Makefile
>>>> +++ b/drivers/media/platform/amlogic/Makefile
>>>> @@ -1,5 +1,6 @@
>>>>    # SPDX-License-Identifier: GPL-2.0-only
>>>>
>>>> +obj-y += c3-isp/
>>>>    obj-y += c3-mipi-adapter/
>>>>    obj-y += c3-mipi-csi2/
>>>>    obj-y += meson-ge2d/
>>>> diff --git a/drivers/media/platform/amlogic/c3-isp/Kconfig b/drivers/media/platform/amlogic/c3-isp/Kconfig
>>>> new file mode 100644
>>>> index 000000000000..e317c1e81750
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-isp/Kconfig
>>>> @@ -0,0 +1,17 @@
>>>> +# SPDX-License-Identifier: GPL-2.0-only
>>>> +
>>>> +config VIDEO_C3_ISP
>>>> +     tristate "Amlogic C3 Image Signal Processor (ISP) driver"
>>>> +     depends on ARCH_MESON || COMPILE_TEST
>>>> +     depends on VIDEO_DEV
>>>> +     depends on OF
>>>> +     select MEDIA_CONTROLLER
>>>> +     select V4L2_FWNODE
>>>> +     select VIDEO_V4L2_SUBDEV_API
>>>> +     select VIDEOBUF2_DMA_CONTIG
>>>> +     help
>>>> +       Video4Linux2 driver for Amlogic C3 ISP pipeline.
>>>> +       C3 ISP pipeline mainly for processing raw image
>>>             The C3 ISP is used for processing raw images
>>>> +       and output result to memory.
>>> Or something similar, but "C3 ISP pipeline mainly for processing"
>>> doesn't sound right to be
>>
>> OK, I see
>>
>>>> +
>>>> +       To compile this driver as a module choose m here.
>>>> diff --git a/drivers/media/platform/amlogic/c3-isp/Makefile b/drivers/media/platform/amlogic/c3-isp/Makefile
>>>> new file mode 100644
>>>> index 000000000000..b1b064170b57
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-isp/Makefile
>>>> @@ -0,0 +1,10 @@
>>>> +# SPDX-License-Identifier: GPL-2.0-only
>>>> +
>>>> +c3-isp-objs := c3-isp-dev.o \
>>>> +            c3-isp-params.o \
>>>> +            c3-isp-stats.o \
>>>> +            c3-isp-capture.o \
>>>> +            c3-isp-core.o \
>>>> +            c3-isp-resizer.o
>>>> +
>>>> +obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
>>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
>>>> new file mode 100644
>>>> index 000000000000..ee9a7a17a203
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
>>>> @@ -0,0 +1,759 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#include <linux/cleanup.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +
>>>> +#include <media/v4l2-ctrls.h>
>>>> +#include <media/v4l2-event.h>
>>>> +#include <media/v4l2-ioctl.h>
>>>> +#include <media/v4l2-mc.h>
>>>> +#include <media/videobuf2-dma-contig.h>
>>>> +
>>>> +#include "c3-isp-common.h"
>>>> +#include "c3-isp-regs.h"
>>>> +
>>>> +static const struct c3_isp_capture_format cap_formats[] = {
>>>> +     {
>>>> +             .mbus_code = MEDIA_BUS_FMT_Y8_1X8,
>>>> +             .fourcc = V4L2_PIX_FMT_GREY,
>>>> +             .depth = 8,
>>>> +     },
>>>> +     {
>>>> +             .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
>>> Does the 5X8 version represents the format on the internal bus between
>>> the resizers and the capture device ?
>>>
>>> How does format propagation work from the ISP to the resizers and the
>>> capture devices ? I mean, is there an internal bus where the number of
>>> samples (5X8 vs 2X8) changes depending on the output format ?
>>>
>> There is no internal bus between the resizer and the capture device.
>>
> I presume there is at least a data path, maybe as internal memory
> buffers, between the different components of the ISP though.


Yes, there is a data path between  resizer and capture node.

>> The output format should be only configured in capture device.
>>
> Ok, what I'm after here is finding out the reason why you have
> different mbus codes associated with different output formats. Same
> thing as per the below question about the mbus codes used between the
> ISP and the resizers.
>
> The mbus code should describe the format as it actually is in the data
> path between the different ISP components. In example, if your
> demosaicing block outputs data in a specific format with a specific
> bit-depth (in example RGB data with a 30 bits wide bus) you should use
> MEDIA_BUS_FMT_RGB101010_1X30.
>
> However, if this information is not available, or not relevant as it
> doesn't influence the behaviour of any of the ISP blocks, I think
> using MEDIA_BUS_FMT_FIXED should be fine.
>
> Otherwise, if there are reasons to use MEDIA_BUS_FMT_YUYV8_1_5X8 over
> MEDIA_BUS_FMT_YUYV8_2X8 it's fine, but please explain them :)
>
> Thanks
>    j


I have checked the ISP datasheet, there is no need to use mbus code.

So decided to use MEDIA_BUS_FMT_FIXED.

>>>> +             .fourcc = V4L2_PIX_FMT_NV12,
>>>> +             .depth = 12,
>>>> +     }, {
>>>> +             .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
>>>> +             .fourcc = V4L2_PIX_FMT_NV21,
>>>> +             .depth = 12,
>>>> +     }, {
>>>> +             .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
>>>> +             .fourcc = V4L2_PIX_FMT_NV16,
>>>> +             .depth = 16,
>>>> +     }, {
>>>> +             .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
>>>> +             .fourcc = V4L2_PIX_FMT_NV61,
>>>> +             .depth = 16,
>>>> +     },
>>>> +};
>>>> +
>>>> +/* Hardware configuration */
>>>> +
>>>> +/* Set the address of wrmifx3(write memory interface) */
>>>> +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
>>>> +{
>>>> +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>>>> +     struct c3_isp_vb2_buffer *buff = cap->buff;
>>>> +     u32 offset;
>>>> +
>>>> +     c3_isp_write(cap->isp,
>>>> +                  C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
>>>> +                  WRMIFX3_CH0_BADDR(buff->paddr));
>>>> +
>>>> +     if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
>>>> +         pix->pixelformat == V4L2_PIX_FMT_NV21 ||
>>>> +         pix->pixelformat == V4L2_PIX_FMT_NV16 ||
>>>> +         pix->pixelformat == V4L2_PIX_FMT_NV61) {
>>>> +             offset = pix->width * pix->height;
>>>> +             c3_isp_write(cap->isp,
>>>> +                          C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
>>>> +                          WRMIFX3_CH1_BADDR(buff->paddr + offset));
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_output_size(struct c3_isp_capture *cap)
>>>> +{
>>>> +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
>>>> +                        DISP_OUT_VSIZE_MASK, pix->height);
>>>> +     c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id),
>>>> +                        DISP_OUT_HSIZE_MASK, pix->width << DISP_OUT_HSIZE_SHIFT);
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
>>>> +{
>>>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>>>> +     u32 stride;
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
>>>> +
>>>> +     /* Grey has 1 plane*/
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        WRMIFX3_FMT_MTX_PLANE_MASK,
>>>> +                        WRMIFX3_FMT_MTX_PLANE_X1 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
>>>> +
>>>> +     /* Set Y only as output format */
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        WRMIFX3_FMT_MODE_OUT_MASK,
>>>> +                        WRMIFX3_FMT_MODE_OUT_Y_ONLY << WRMIFX3_FMT_MODE_OUT_SHIFT);
>>>> +
>>>> +     /* The unit of stride is 128 bits */
>>>> +     stride = DIV_ROUND_UP(fmt->width * 8, 128);
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>>>> +                        WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>>>> +                        WRMIFX3_CH0_PIX_BITS_MODE_MASK,
>>>> +                        WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, u32 swap_uv)
>>>> +{
>>>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>>>> +     u32 stride;
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT);
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        WRMIFX3_FMT_MTX_UV_SWAP_MASK,
>>>> +                        swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
>>>> +
>>>> +     /* NV12 or NV21 has 2 planes*/
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        WRMIFX3_FMT_MTX_PLANE_MASK,
>>>> +                        WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
>>>> +
>>>> +     /* Set YUV420 as output format */
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        WRMIFX3_FMT_MODE_OUT_MASK,
>>>> +                        WRMIFX3_FMT_MODE_OUT_YUV420 << WRMIFX3_FMT_MODE_OUT_SHIFT);
>>>> +
>>>> +     /* The unit of stride is 128 bits */
>>>> +     stride = DIV_ROUND_UP(fmt->width * 8, 128);
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>>>> +                        WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
>>>> +                        WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>>>> +                        WRMIFX3_CH0_PIX_BITS_MODE_MASK,
>>>> +                        WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
>>>> +                        WRMIFX3_CH1_PIX_BITS_MODE_MASK,
>>>> +                        WRMIFX3_CH1_PIX_BITS_16BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, u32 swap_uv)
>>>> +{
>>>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>>>> +     u32 stride;
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_16BIT);
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        WRMIFX3_FMT_MTX_UV_SWAP_MASK,
>>>> +                        swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
>>>> +
>>>> +     /* NV16 or NV61 has 2 planes*/
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        WRMIFX3_FMT_MTX_PLANE_MASK,
>>>> +                        WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT);
>>>> +
>>>> +     /* Set YUV422 as output format */
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>>>> +                        WRMIFX3_FMT_MODE_OUT_MASK,
>>>> +                        WRMIFX3_FMT_MODE_OUT_YUV422 << WRMIFX3_FMT_MODE_OUT_SHIFT);
>>>> +
>>>> +     /* The unit of stride is 128 bits */
>>>> +     stride = DIV_ROUND_UP(fmt->width * 16, 128);
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>>>> +                        WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT);
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
>>>> +                        WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>>>> +                        WRMIFX3_CH0_PIX_BITS_MODE_MASK,
>>>> +                        WRMIFX3_CH0_PIX_BITS_16BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
>>>> +                        WRMIFX3_CH1_PIX_BITS_MODE_MASK,
>>>> +                        WRMIFX3_CH1_PIX_BITS_32BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
>>>> +{
>>>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>>>> +
>>>> +     if (fmt->pixelformat == V4L2_PIX_FMT_GREY) {
>>>> +             c3_isp_cap_wrmifx3_grey(cap);
>>>> +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV12) {
>>>> +             c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
>>>> +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV21) {
>>>> +             c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
>>>> +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV16) {
>>>> +             c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV);
>>>> +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV61) {
>>>> +             c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU);
>>>> +     } else {
>>>> +             dev_err(cap->isp->dev, "Invalid pixel format: 0x%x\n", fmt->pixelformat);
>>>> +             return;
>>>> +     }
>>>> +
>>>> +     c3_isp_write(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id),
>>>> +                  WRMIFX3_FMT_HSIZE(fmt->width) | WRMIFX3_FMT_VSIZE(fmt->height));
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
>>>> +                        WRMIFX3_WIN_LUMA_HEND_MASK,
>>>> +                        WRMIFX3_WIN_LUMA_HEND(fmt->width) << WRMIFX3_WIN_LUMA_HEND_SHIFT);
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
>>>> +                        WRMIFX3_WIN_LUMA_VEND_MASK,
>>>> +                        WRMIFX3_WIN_LUMA_VEND(fmt->height) << WRMIFX3_WIN_LUMA_VEND_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_HSIZE, cap->id),
>>>> +                        WRMIFX3_CROP_HEND_MASK,
>>>> +                        WRMIFX3_CROP_HEND(fmt->width) << WRMIFX3_CROP_HEND_SHIFT);
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_VSIZE, cap->id),
>>>> +                        WRMIFX3_CROP_VEND_MASK,
>>>> +                        WRMIFX3_CROP_VEND(fmt->height) << WRMIFX3_CROP_VEND_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
>>>> +                        WRMIFX3_WIN_CHROM_HEND_MASK,
>>>> +                        WRMIFX3_WIN_CHROM_HEND(fmt->width) << WRMIFX3_WIN_CHROM_HEND_SHIFT);
>>>> +     c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
>>>> +                        WRMIFX3_WIN_CHROM_VEND_MASK,
>>>> +                        WRMIFX3_WIN_CHROM_VEND(fmt->height) << WRMIFX3_WIN_CHROM_VEND_SHIFT);
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)
>>> This function is called from two locations, one with the buff_lock
>>> held, the other one without, if I'm not mistaken.
>>>
>> Will add the buff_lock in c3_isp_cap_start().
>>
>>>> +{
>>>> +     cap->buff = list_first_entry_or_null(&cap->pending,
>>>> +                                          struct c3_isp_vb2_buffer, list);
>>>> +     if (cap->buff) {
>>>> +             c3_isp_cap_wrmifx3_buff(cap);
>>>> +             list_del(&cap->buff->list);
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_start(struct c3_isp_capture *cap)
>>>> +{
>>>> +     c3_isp_cap_cfg_buff(cap);
>>>> +
>>>> +     c3_isp_cap_output_size(cap);
>>>> +     c3_isp_cap_wrmifx3_size(cap);
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN,
>>>> +                        TOP_WRMIF_EN(cap->id), TOP_WRMIF_EN(cap->id));
>>>> +
>>>> +     cap->is_streaming = true;
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_stop(struct c3_isp_capture *cap)
>>>> +{
>>>> +     cap->is_streaming = false;
>>>> +
>>>> +     c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, TOP_WRMIF_EN(cap->id), 0);
>>>> +}
>>>> +
>>>> +static int c3_isp_cap_done(struct c3_isp_capture *cap)
>>>> +{
>>>> +     struct c3_isp_vb2_buffer *buff = cap->buff;
>>>> +     unsigned long flags;
>>>> +
>>>> +     if (!cap->is_streaming)
>>>> +             return -EINVAL;
>>> Is this an error condition or 0 should be returnes ?
>>>
>> If  return 0,  there is no need to check the return value of
>> c3_isp_cap_done().
>>
>> So will modify this function from "int c3_isp_cap_done()" to
>>
>> "void c3_isp_cap_done()".
>>
>>>> +
>>>> +     spin_lock_irqsave(&cap->buff_lock, flags);
>>>> +
>>>> +     if (buff) {
>>>> +             buff->vb.sequence = cap->isp->frm_sequence;
>>>> +             buff->vb.vb2_buf.timestamp = ktime_get();
>>>> +             buff->vb.field = V4L2_FIELD_NONE;
>>>> +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
>>>> +     }
>>>> +
>>>> +     c3_isp_cap_cfg_buff(cap);
>>>> +
>>>> +     spin_unlock_irqrestore(&cap->buff_lock, flags);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +/* V4L2 video operations */
>>>> +
>>>> +static const struct c3_isp_capture_format
>>>> +*c3_cap_find_fmt(u32 fourcc)
>>>> +{
>>>> +     unsigned int i;
>>>> +
>>>> +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
>>>> +             if (cap_formats[i].fourcc == fourcc)
>>>> +                     return &cap_formats[i];
>>>> +     }
>>>> +
>>>> +     return NULL;
>>>> +}
>>>> +
>>>> +static void c3_cap_try_fmt(struct c3_isp_capture *cap,
>>>> +                        struct v4l2_pix_format *pix)
>>>> +{
>>>> +     const struct c3_isp_capture_format *fmt;
>>>> +
>>>> +     fmt = c3_cap_find_fmt(pix->pixelformat);
>>>> +     if (!fmt)
>>>> +             fmt = &cap_formats[0];
>>>> +
>>>> +     pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>>>> +     pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>>>> +     pix->pixelformat = fmt->fourcc;
>>>> +     pix->field = V4L2_FIELD_NONE;
>>>> +     pix->colorspace = V4L2_COLORSPACE_SRGB;
>>>> +     pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>>> +     pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>>>> +
>>>> +     /* ISP hardware requires 16 bytes alignment */
>>>> +     pix->bytesperline = ALIGN(pix->width, 16);
>>>> +     pix->sizeimage = pix->bytesperline * pix->height * fmt->depth / 8;
>>>> +}
>>>> +
>>>> +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
>>>> +                                   enum vb2_buffer_state state)
>>>> +{
>>>> +     unsigned long flags;
>>>> +     struct c3_isp_vb2_buffer *buff;
>>>> +
>>>> +     spin_lock_irqsave(&cap->buff_lock, flags);
>>>> +
>>>> +     if (cap->buff) {
>>>> +             vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
>>>> +             cap->buff = NULL;
>>>> +     }
>>>> +
>>>> +     while (!list_empty(&cap->pending)) {
>>>> +             buff = list_first_entry(&cap->pending,
>>>> +                                     struct c3_isp_vb2_buffer, list);
>>>> +             list_del(&buff->list);
>>>> +             vb2_buffer_done(&buff->vb.vb2_buf, state);
>>>> +     }
>>>> +
>>>> +     spin_unlock_irqrestore(&cap->buff_lock, flags);
>>>> +}
>>>> +
>>>> +static int c3_isp_cap_querycap(struct file *file, void *fh,
>>>> +                            struct v4l2_capability *cap)
>>>> +{
>>>> +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>>>> +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_cap_enum_fmt(struct file *file, void *fh,
>>>> +                            struct v4l2_fmtdesc *f)
>>>> +{
>>>> +     const struct c3_isp_capture_format *fmt;
>>>> +     unsigned int index = 0;
>>>> +     unsigned int i;
>>>> +
>>>> +     if (!f->mbus_code) {
>>>> +             if (f->index >= ARRAY_SIZE(cap_formats))
>>>> +                     return -EINVAL;
>>>> +
>>>> +             fmt = &cap_formats[f->index];
>>>> +             f->pixelformat = fmt->fourcc;
>>>> +             return 0;
>>>> +     }
>>>> +
>>>> +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
>>>> +             fmt = &cap_formats[i];
>>>> +             if (f->mbus_code != fmt->mbus_code)
>>>> +                     continue;
>>>> +
>>>> +             if (index++ == f->index) {
>>>> +                     f->pixelformat = cap_formats[i].fourcc;
>>>> +                     return 0;
>>>> +             }
>>>> +     }
>>>> +
>>>> +     return -EINVAL;
>>>> +}
>>>> +
>>>> +static int c3_isp_cap_g_fmt(struct file *file, void *fh,
>>>> +                         struct v4l2_format *f)
>>>> +{
>>>> +     struct c3_isp_capture *cap = video_drvdata(file);
>>>> +
>>>> +     f->fmt.pix = cap->vfmt.fmt.pix;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_cap_s_fmt(struct file *file, void *fh,
>>>> +                         struct v4l2_format *f)
>>>> +{
>>>> +     struct c3_isp_capture *cap = video_drvdata(file);
>>>> +
>>>> +     c3_cap_try_fmt(cap, &f->fmt.pix);
>>>> +     cap->vfmt = *f;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_cap_try_fmt(struct file *file, void *fh,
>>>> +                           struct v4l2_format *f)
>>>> +{
>>>> +     struct c3_isp_capture *cap = video_drvdata(file);
>>>> +
>>>> +     c3_cap_try_fmt(cap, &f->fmt.pix);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
>>>> +                                struct v4l2_frmsizeenum *fsize)
>>>> +{
>>>> +     const struct c3_isp_capture_format *fmt;
>>>> +
>>>> +     if (fsize->index)
>>>> +             return -EINVAL;
>>>> +
>>>> +     fmt = c3_cap_find_fmt(fsize->pixel_format);
>>>> +     if (!fmt)
>>>> +             return -EINVAL;
>>>> +
>>>> +     fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
>>>> +     fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
>>>> +     fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
>>>> +     fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
>>>> +     fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
>>>> +     fsize->stepwise.step_width = 2;
>>>> +     fsize->stepwise.step_height = 2;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
>>>> +     .vidioc_querycap                = c3_isp_cap_querycap,
>>>> +     .vidioc_enum_fmt_vid_cap        = c3_isp_cap_enum_fmt,
>>>> +     .vidioc_g_fmt_vid_cap           = c3_isp_cap_g_fmt,
>>>> +     .vidioc_s_fmt_vid_cap           = c3_isp_cap_s_fmt,
>>>> +     .vidioc_try_fmt_vid_cap         = c3_isp_cap_try_fmt,
>>>> +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>>>> +     .vidioc_querybuf                = vb2_ioctl_querybuf,
>>>> +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
>>>> +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
>>>> +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>>>> +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>>>> +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>>>> +     .vidioc_streamon                = vb2_ioctl_streamon,
>>>> +     .vidioc_streamoff               = vb2_ioctl_streamoff,
>>>> +     .vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
>>>> +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>>>> +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>>>> +};
>>>> +
>>>> +static const struct v4l2_file_operations isp_cap_v4l2_fops = {
>>>> +     .open = v4l2_fh_open,
>>>> +     .release = vb2_fop_release,
>>>> +     .poll = vb2_fop_poll,
>>>> +     .unlocked_ioctl = video_ioctl2,
>>>> +     .mmap = vb2_fop_mmap,
>>>> +};
>>>> +
>>>> +static int c3_isp_cap_link_validate(struct media_link *link)
>>>> +{
>>>> +     struct video_device *vdev =
>>>> +             media_entity_to_video_device(link->sink->entity);
>>>> +     struct v4l2_subdev *sd =
>>>> +             media_entity_to_v4l2_subdev(link->source->entity);
>>>> +     struct c3_isp_capture *cap = video_get_drvdata(vdev);
>>>> +     struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
>>>> +     struct v4l2_subdev_format src_fmt = {
>>>> +             .which = V4L2_SUBDEV_FORMAT_ACTIVE,
>>>> +             .pad = link->source->index,
>>>> +     };
>>>> +     const struct c3_isp_capture_format *cap_fmt =
>>>> +                             c3_cap_find_fmt(pix_fmt->pixelformat);
>>>> +     int ret;
>>>> +
>>>> +     ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     if (src_fmt.format.width != pix_fmt->width ||
>>>> +         src_fmt.format.height != pix_fmt->height ||
>>>> +         src_fmt.format.code != cap_fmt->mbus_code) {
>>>> +             dev_err(cap->isp->dev,
>>>> +                     "link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n",
>>>> +                     link->source->entity->name, link->source->index,
>>>> +                     link->sink->entity->name, link->sink->index,
>>>> +                     src_fmt.format.code, src_fmt.format.width,
>>>> +                     src_fmt.format.height, cap_fmt->mbus_code,
>>>> +                     pix_fmt->width, pix_fmt->height);
>>>> +
>>>> +             return -EPIPE;
>>>> +     };
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static const struct media_entity_operations isp_cap_entity_ops = {
>>>> +     .link_validate = c3_isp_cap_link_validate,
>>>> +};
>>>> +
>>>> +static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
>>>> +                               unsigned int *num_buffers,
>>>> +                               unsigned int *num_planes,
>>>> +                               unsigned int sizes[],
>>>> +                               struct device *alloc_devs[])
>>>> +{
>>>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>>>> +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>>>> +
>>>> +     if (*num_planes) {
>>>> +             if (*num_planes != 1)
>>>> +                     return -EINVAL;
>>>> +
>>>> +             if (sizes[0] < pix->sizeimage)
>>>> +                     return -EINVAL;
>>>> +     } else {
>>>> +             *num_planes = 1;
>>>> +             sizes[0] = pix->sizeimage;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>>> +     struct c3_isp_vb2_buffer *buf =
>>>> +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>>>> +     unsigned long flags;
>>>> +
>>>> +     spin_lock_irqsave(&cap->buff_lock, flags);
>>>> +
>>>> +     list_add_tail(&buf->list, &cap->pending);
>>>> +
>>>> +     spin_unlock_irqrestore(&cap->buff_lock, flags);
>>>> +}
>>>> +
>>>> +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>>>> +     unsigned int size = cap->vfmt.fmt.pix.sizeimage;
>>>> +
>>>> +     if (vb2_plane_size(vb, 0) < size) {
>>>> +             dev_err(cap->isp->dev,
>>>> +                     "User buffer too small (%ld < %u)\n",
>>>> +                     vb2_plane_size(vb, 0), size);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     vb2_set_plane_payload(vb, 0, size);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>>> +     struct c3_isp_vb2_buffer *buf =
>>>> +             container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>>>> +
>>>> +     buf->vaddr = vb2_plane_vaddr(vb, 0);
>>>> +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>>>> +
>>>> +     memset(buf->vaddr, 0, cap->vfmt.fmt.pix.sizeimage);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
>>>> +                                   unsigned int count)
>>>> +{
>>>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>>>> +     int ret;
>>>> +
>>>> +     guard(mutex)(&cap->isp->lock);
>>>> +
>>>> +     ret = pm_runtime_resume_and_get(cap->isp->dev);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
>>>> +     if (ret) {
>>>> +             dev_err(cap->isp->dev,
>>>> +                     "Failed to start cap%u pipeline: %d\n", cap->id, ret);
>>>> +             goto err_pm_put;
>>>> +     }
>>>> +
>>>> +     if (c3_isp_pipeline_ready(cap->isp)) {
>>>> +             ret = v4l2_subdev_enable_streams(&cap->isp->core.sd,
>>>> +                                              C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>>> +                                              BIT(0));
>>>> +             if (ret)
>>>> +                     goto err_pipeline_stop;
>>>> +     }
>>>> +
>>>> +     c3_isp_rsz_start(cap->rsz);
>>>> +     c3_isp_cap_start(cap);
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_pipeline_stop:
>>>> +     video_device_pipeline_stop(&cap->vdev);
>>>> +err_pm_put:
>>>> +     pm_runtime_put(cap->isp->dev);
>>>> +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
>>>> +{
>>>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>>>> +
>>>> +     guard(mutex)(&cap->isp->lock);
>>>> +
>>>> +     c3_isp_cap_stop(cap);
>>>> +     c3_isp_rsz_stop(cap->rsz);
>>>> +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
>>>> +
>>>> +     if (cap->isp->pipe.start_count == 1)
>>>> +             v4l2_subdev_disable_streams(&cap->isp->core.sd,
>>>> +                                         C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>>> +                                         BIT(0));
>>>> +
>>>> +     video_device_pipeline_stop(&cap->vdev);
>>>> +     pm_runtime_put(cap->isp->dev);
>>>> +}
>>>> +
>>>> +static const struct vb2_ops isp_video_vb2_ops = {
>>>> +     .queue_setup = c3_isp_vb2_queue_setup,
>>>> +     .buf_queue = c3_isp_vb2_buf_queue,
>>>> +     .buf_prepare = c3_isp_vb2_buf_prepare,
>>>> +     .buf_init = c3_isp_vb2_buf_init,
>>>> +     .wait_prepare = vb2_ops_wait_prepare,
>>>> +     .wait_finish = vb2_ops_wait_finish,
>>>> +     .start_streaming = c3_isp_vb2_start_streaming,
>>>> +     .stop_streaming = c3_isp_vb2_stop_streaming,
>>>> +};
>>>> +
>>>> +static int c3_isp_register_capture(struct c3_isp_capture *cap)
>>>> +{
>>>> +     struct video_device *vdev = &cap->vdev;
>>>> +     struct vb2_queue *vb2_q = &cap->vb2_q;
>>>> +     int ret;
>>>> +
>>>> +     snprintf(vdev->name, sizeof(vdev->name), "isp-cap%u", cap->id);
>>>> +     vdev->fops = &isp_cap_v4l2_fops;
>>>> +     vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
>>>> +     vdev->v4l2_dev = &cap->isp->v4l2_dev;
>>>> +     vdev->entity.ops = &isp_cap_entity_ops;
>>>> +     vdev->lock = &cap->lock;
>>>> +     vdev->minor = -1;
>>>> +     vdev->queue = vb2_q;
>>>> +     vdev->release = video_device_release_empty;
>>>> +     vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
>>>> +     vdev->vfl_dir = VFL_DIR_RX;
>>>> +     video_set_drvdata(vdev, cap);
>>>> +
>>>> +     vb2_q->drv_priv = cap;
>>>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>>>> +     vb2_q->ops = &isp_video_vb2_ops;
>>>> +     vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
>>>> +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>>>> +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>>>> +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>>>> +     vb2_q->dev = cap->isp->dev;
>>>> +     vb2_q->lock = &cap->lock;
>>>> +     vb2_q->min_queued_buffers = 2;
>>> I'm not sure what your plans regarding libcamera are, but be aware
>>> we're going to get stricter about this value. Ideally, we aim to have
>>> ISP driver set this value to 0 so that the ISP operates (produce
>>> statistics) even if no buffer is queued to the capture devices. The
>>> reason is that we want algorithms to run even if there are no capture
>>> buffers provided.
>>>
>>> In order to allow the driver to operate with no buffers, you could
>>> probably allocate a scratch buffer in the driver and use it whenever
>>> you receive a frame completed IRQ and have no buffers available in the
>>> cap->provided queue.
>>>
>>> See rkisp1_dummy_buf_create() in the RkISP1 driver and how dummy_buf
>>> is used there.
>>>
>> OK,  will refer rkisp1 driver  and add dummy buffer.
>>
>>>> +
>>>> +     ret = vb2_queue_init(vb2_q);
>>>> +     if (ret < 0)
>>>> +             goto err_destroy;
>>>> +
>>>> +     cap->pad.flags = MEDIA_PAD_FL_SINK;
>>>> +     ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
>>>> +     if (ret < 0)
>>>> +             goto err_queue_release;
>>>> +
>>>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>>>> +     if (ret < 0) {
>>>> +             dev_err(cap->isp->dev,
>>>> +                     "Failed to register %s: %d\n", vdev->name, ret);
>>>> +             goto err_entity_cleanup;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_entity_cleanup:
>>>> +     media_entity_cleanup(&vdev->entity);
>>>> +err_queue_release:
>>>> +     vb2_queue_release(vb2_q);
>>>> +err_destroy:
>>>> +     mutex_destroy(&cap->lock);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +int c3_isp_captures_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     int ret;
>>>> +     unsigned int i;
>>>> +     struct c3_isp_capture *cap;
>>>> +
>>>> +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>>>> +             cap = &isp->caps[i];
>>>> +             memset(cap, 0, sizeof(*cap));
>>>> +
>>>> +             cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
>>>> +             cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
>>>> +             cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
>>>> +             cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
>>>> +             cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
>>>> +
>>>> +             c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
>>>> +
>>>> +             cap->id = i;
>>>> +             if (cap->id == C3_ISP_CAP_DEV_0)
>>>> +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_0];
>>>> +             else if (cap->id == C3_ISP_CAP_DEV_1)
>>>> +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_1];
>>>> +             else
>>>> +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_2];
>>>> +
>>>> +             cap->isp = isp;
>>>> +             INIT_LIST_HEAD(&cap->pending);
>>>> +             spin_lock_init(&cap->buff_lock);
>>>> +             mutex_init(&cap->lock);
>>>> +
>>>> +             ret = c3_isp_register_capture(cap);
>>>> +             if (ret) {
>>>> +                     cap->isp = NULL;
>>>> +                     mutex_destroy(&cap->lock);
>>>> +                     c3_isp_captures_unregister(isp);
>>>> +                     return ret;
>>>> +             }
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +void c3_isp_captures_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     unsigned int i;
>>>> +     struct c3_isp_capture *cap;
>>>> +
>>>> +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>>>> +             cap = &isp->caps[i];
>>>> +
>>>> +             if (!cap->isp)
>>>> +                     continue;
>>>> +             vb2_queue_release(&cap->vb2_q);
>>>> +             media_entity_cleanup(&cap->vdev.entity);
>>>> +             video_unregister_device(&cap->vdev);
>>>> +             mutex_destroy(&cap->lock);
>>>> +     }
>>>> +}
>>>> +
>>>> +void c3_isp_captures_done(struct c3_isp_device *isp)
>>>> +{
>>>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
>>>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
>>>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
>>>> +}
>>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
>>>> new file mode 100644
>>>> index 000000000000..19f2a3bc29c9
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
>>>> @@ -0,0 +1,327 @@
>>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#ifndef __C3_ISP_COMMON_H__
>>>> +#define __C3_ISP_COMMON_H__
>>>> +
>>>> +#include <linux/clk.h>
>>>> +
>>>> +#include <media/media-device.h>
>>>> +#include <media/videobuf2-core.h>
>>>> +#include <media/v4l2-device.h>
>>>> +#include <media/v4l2-subdev.h>
>>>> +#include <media/videobuf2-v4l2.h>
>>>> +
>>>> +#define C3_ISP_DRIVER_NAME            "c3-isp"
>>>> +#define C3_ISP_CLOCK_NUM_MAX          3
>>>> +
>>>> +#define C3_ISP_CORE_DEF_SINK_PAD_FMT  MEDIA_BUS_FMT_SRGGB10_1X10
>>>> +#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUYV8_1_5X8
>>>> +#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUYV8_1_5X8
>>>> +#define C3_ISP_DEFAULT_WIDTH          1920
>>>> +#define C3_ISP_DEFAULT_HEIGHT         1080
>>>> +#define C3_ISP_MAX_WIDTH              2888
>>>> +#define C3_ISP_MAX_HEIGHT             2240
>>>> +#define C3_ISP_MIN_WIDTH              160
>>>> +#define C3_ISP_MIN_HEIGHT             120
>>>> +
>>>> +#define C3_DISP_INTER                 0x400
>>>> +#define C3_DISP_REG(base, id)         ((base) + (id) * C3_DISP_INTER)
>>>> +#define C3_WRMIFX3_INTER              0x100
>>>> +#define C3_WRMIFX3_REG(base, id)      ((base) + (id) * C3_WRMIFX3_INTER)
>>>> +#define C3_PPS_TAP4_S11_H_NUM         33
>>>> +#define C3_PPS_LUT_CTYPE_0            0
>>>> +#define C3_PPS_LUT_CTYPE_2            2
>>>> +#define C3_SCALE_EN                   1
>>>> +#define C3_SCALE_DIS                  0
>>>> +
>>>> +#define C3_ISP_PHASE_OFFSET_0         0
>>>> +#define C3_ISP_PHASE_OFFSET_1         1
>>>> +#define C3_ISP_PHASE_OFFSET_NONE      0xff
>>>> +
>>>> +enum c3_isp_core_pads {
>>>> +     C3_ISP_CORE_PAD_SINK_VIDEO,
>>>> +     C3_ISP_CORE_PAD_SINK_PARAMS,
>>>> +     C3_ISP_CORE_PAD_SOURCE_STATS,
>>>> +     C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>>> +     C3_ISP_CORE_PAD_MAX
>>>> +};
>>>> +
>>>> +enum c3_isp_resizer_ids {
>>>> +     C3_ISP_RSZ_0,
>>>> +     C3_ISP_RSZ_1,
>>>> +     C3_ISP_RSZ_2,
>>>> +     C3_ISP_NUM_RSZ
>>>> +};
>>>> +
>>>> +enum c3_isp_resizer_pads {
>>>> +     C3_ISP_RESIZER_PAD_SINK,
>>>> +     C3_ISP_RESIZER_PAD_SOURCE,
>>>> +     C3_ISP_RESIZER_PAD_MAX
>>>> +};
>>>> +
>>>> +enum c3_isp_cap_devs {
>>>> +     C3_ISP_CAP_DEV_0,
>>>> +     C3_ISP_CAP_DEV_1,
>>>> +     C3_ISP_CAP_DEV_2,
>>>> +     C3_ISP_NUM_CAP_DEVS
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct c3_isp_pps_io_size - isp scaler input and output size
>>>> + *
>>>> + * @thsize: input horizontal size of after preprocessing
>>>> + * @tvsize: input vertical size of after preprocessing
>>>> + * @ohsize: output horizontal size
>>>> + * @ovsize: output vertical size
>>>> + * @ihsize: input horizontal size
>>>> + * @max_hsize: maximum horizontal size
>>>> + */
>>>> +struct c3_isp_pps_io_size {
>>>> +     u32 thsize;
>>>> +     u32 tvsize;
>>>> +     u32 ohsize;
>>>> +     u32 ovsize;
>>>> +     u32 ihsize;
>>>> +     u32 max_hsize;
>>>> +};
>>>> +
>>>> +/**
>>>> + * @mbus_code: the mbus code
>>>> + * @pads: save the pad flag of this mbus_code
>>>> + * @xofst: horizontal phase offset of hardware
>>>> + * @yofst: vertical phase offset of hardware
>>>> + */
>>>> +struct c3_isp_mbus_format_info {
>>>> +     u32 mbus_code;
>>>> +     u32 pads;
>>>> +     u8 xofst;
>>>> +     u8 yofst;
>>>> +};
>>>> +
>>>> +/**
>>>> + * @mbus_code: the mbus code
>>>> + * @fourcc: pixel format
>>>> + * @depth: pixel width
>>>> + */
>>>> +struct c3_isp_capture_format {
>>>> +     u32 mbus_code;
>>>> +     u32 fourcc;
>>>> +     u8 depth;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct c3_isp_vb2_buffer - A container of vb2 buffer
>>>> + *
>>>> + * @vb: vb2 buffer
>>>> + * @vaddr: buffer virtual address
>>>> + * @paddr: buffer physical address
>>>> + * @list: entry of the buffer in the queue
>>>> + */
>>>> +struct c3_isp_vb2_buffer {
>>>> +     struct vb2_v4l2_buffer vb;
>>>> +     void *vaddr;
>>>> +     dma_addr_t paddr;
>>>> +     struct list_head list;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct c3_isp_core - ISP core subdev
>>>> + *
>>>> + * @sd: ISP sub-device
>>>> + * @pads: ISP sub-device pads
>>>> + * @src_sd: source sub-device
>>>> + * @isp: pointer to c3_isp_device
>>>> + * @src_sd_pad: source sub-device pad
>>>> + */
>>>> +struct c3_isp_core {
>>>> +     struct v4l2_subdev sd;
>>>> +     struct media_pad pads[C3_ISP_CORE_PAD_MAX];
>>>> +     struct v4l2_subdev *src_sd;
>>>> +     u16 src_sd_pad;
>>>> +     struct c3_isp_device *isp;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct c3_isp_resizer - ISP resizer subdev
>>>> + *
>>>> + * @id: resizer id
>>>> + * @sd: resizer sub-device
>>>> + * @pads: resizer sub-device pads
>>>> + * @isp: pointer to c3_isp_device
>>>> + * @cap: pointer to c3_isp_capture
>>>> + */
>>>> +struct c3_isp_resizer {
>>>> +     enum c3_isp_resizer_ids id;
>>>> +     struct v4l2_subdev sd;
>>>> +     struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
>>>> +     struct c3_isp_device *isp;
>>>> +     struct c3_isp_capture *cap;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct c3_isp_stats - ISP statistics device
>>>> + *
>>>> + * @vb2_q: vb2 buffer queue
>>>> + * @vdev: video node
>>>> + * @vfmt: v4l2_format of the metadata format
>>>> + * @pad: media pad
>>>> + * @lock: protects vb2_q, vdev
>>>> + * @is_streaming: stats status
>>>> + * @isp: pointer to c3_isp_device
>>>> + * @buff: in use buffer
>>>> + * @buff_lock: protects stats buffer
>>>> + * @pending: stats buffer list head
>>>> + */
>>>> +struct c3_isp_stats {
>>>> +     struct vb2_queue vb2_q;
>>>> +     struct video_device vdev;
>>>> +     struct v4l2_format vfmt;
>>>> +     struct media_pad pad;
>>>> +
>>>> +     struct mutex lock; /* Protects vb2_q, vdev */
>>>> +     bool is_streaming;
>>>> +     struct c3_isp_device *isp;
>>>> +
>>>> +     struct c3_isp_vb2_buffer *buff;
>>>> +     spinlock_t buff_lock; /* Protects stream buffer */
>>>> +     struct list_head pending;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct c3_isp_params - ISP parameters device
>>>> + *
>>>> + * @vb2_q: vb2 buffer queue
>>>> + * @vdev: video node
>>>> + * @vfmt: v4l2_format of the metadata format
>>>> + * @pad: media pad
>>>> + * @lock: protects vb2_q, vdev
>>>> + * @isp: pointer to c3_isp_device
>>>> + * @buff: in use buffer
>>>> + * @buff_lock: protects stats buffer
>>>> + * @pending: stats buffer list head
>>>> + */
>>>> +struct c3_isp_params {
>>>> +     struct vb2_queue vb2_q;
>>>> +     struct video_device vdev;
>>>> +     struct v4l2_format vfmt;
>>>> +     struct media_pad pad;
>>>> +
>>>> +     struct mutex lock; /* Protects vb2_q, vdev */
>>>> +     struct c3_isp_device *isp;
>>>> +
>>>> +     struct c3_isp_vb2_buffer *buff;
>>>> +     spinlock_t buff_lock; /* Protects stream buffer */
>>>> +     struct list_head pending;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct c3_isp_capture - ISP capture device
>>>> + *
>>>> + * @id: capture device ID
>>>> + * @vb2_q: vb2 buffer queue
>>>> + * @vdev: video node
>>>> + * @vfmt: v4l2_format of the capture format
>>>> + * @pad: media pad
>>>> + * @lock: protects vb2_q, vdev
>>>> + * @is_streaming: capture device status
>>>> + * @isp: pointer to c3_isp_device
>>>> + * @rsz: pointer to c3_isp_resizer
>>>> + * @buff: in use buffer
>>>> + * @buff_lock: protects capture buffer
>>>> + * @pending: capture buffer list head
>>>> + */
>>>> +struct c3_isp_capture {
>>>> +     enum c3_isp_cap_devs id;
>>>> +     struct vb2_queue vb2_q;
>>>> +     struct video_device vdev;
>>>> +     struct v4l2_format vfmt;
>>>> +     struct media_pad pad;
>>>> +
>>>> +     struct mutex lock; /* Protects vb2_q, vdev */
>>>> +     bool is_streaming;
>>>> +     struct c3_isp_device *isp;
>>>> +     struct c3_isp_resizer *rsz;
>>>> +
>>>> +     struct c3_isp_vb2_buffer *buff;
>>>> +     spinlock_t buff_lock; /* Protects stream buffer */
>>>> +     struct list_head pending;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct c3_isp_info - ISP information
>>>> + *
>>>> + * @clocks: array of ISP clock names
>>>> + * @clock_rates: array of ISP clock rate
>>>> + * @clock_num: actual clock number
>>>> + */
>>>> +struct c3_isp_info {
>>>> +     char *clocks[C3_ISP_CLOCK_NUM_MAX];
>>>> +     u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
>>>> +     u32 clock_num;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct c3_isp_device - ISP platform device
>>>> + *
>>>> + * @dev: pointer to the struct device
>>>> + * @base: base register address
>>>> + * @clks: array of clocks
>>>> + * @notifier: notifier to register on the v4l2-async API
>>>> + * @v4l2_dev: v4l2_device variable
>>>> + * @media_dev: media device variable
>>>> + * @pipe: media pipeline
>>>> + * @core: ISP core subdev
>>>> + * @resizer: ISP resizer subdev
>>>> + * @stats: ISP stats device
>>>> + * @params: ISP params device
>>>> + * @caps: array of ISP capture device
>>>> + * @frm_sequence: used to record frame id
>>>> + * @lock: protect ISP device
>>>> + * @info: version-specific ISP information
>>>> + */
>>>> +struct c3_isp_device {
>>>> +     struct device *dev;
>>>> +     void __iomem *base;
>>>> +     struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
>>>> +
>>>> +     struct v4l2_async_notifier notifier;
>>>> +     struct v4l2_device v4l2_dev;
>>>> +     struct media_device media_dev;
>>>> +     struct media_pipeline pipe;
>>>> +
>>>> +     struct c3_isp_core core;
>>>> +     struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
>>>> +     struct c3_isp_stats stats;
>>>> +     struct c3_isp_params params;
>>>> +     struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
>>>> +
>>>> +     u32 frm_sequence;
>>>> +     struct mutex lock; /* Protect ISP device */
>>>> +     const struct c3_isp_info *info;
>>>> +};
>>>> +
>>>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
>>>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
>>>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
>>>> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp);
>>>> +
>>>> +int c3_isp_core_register(struct c3_isp_device *isp);
>>>> +void c3_isp_core_unregister(struct c3_isp_device *isp);
>>>> +int c3_isp_resizers_register(struct c3_isp_device *isp);
>>>> +void c3_isp_resizers_unregister(struct c3_isp_device *isp);
>>>> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz);
>>>> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz);
>>>> +int c3_isp_captures_register(struct c3_isp_device *isp);
>>>> +void c3_isp_captures_unregister(struct c3_isp_device *isp);
>>>> +void c3_isp_captures_done(struct c3_isp_device *isp);
>>>> +int c3_isp_stats_register(struct c3_isp_device *isp);
>>>> +void c3_isp_stats_unregister(struct c3_isp_device *isp);
>>>> +int c3_isp_stats_done(struct c3_isp_device *isp);
>>>> +int c3_isp_params_register(struct c3_isp_device *isp);
>>>> +void c3_isp_params_unregister(struct c3_isp_device *isp);
>>>> +int c3_isp_params_done(struct c3_isp_device *isp);
>>>> +
>>>> +#endif
>>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
>>>> new file mode 100644
>>>> index 000000000000..d3672aff9fd2
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
>>>> @@ -0,0 +1,675 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#include <linux/pm_runtime.h>
>>>> +
>>>> +#include "c3-isp-common.h"
>>>> +#include "c3-isp-regs.h"
>>>> +#include "include/uapi/c3-isp-config.h"
>>>> +
>>>> +#define C3_ISP_CORE_SUBDEV_NAME        "isp-core"
>>>> +
>>>> +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
>>>> +     /* RAW formats */
>>>> +     {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SBGGR10_1X10,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SGBRG10_1X10,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SGRBG10_1X10,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SRGGB10_1X10,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SBGGR12_1X12,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SGBRG12_1X12,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SGRBG12_1X12,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_SRGGB12_1X12,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>>>> +     },
>>>> +     /* YUV formats */
>>>> +     {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_1_5X8,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_2X8,
>>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +     },
>>>> +};
>>>> +
>>>> +static const struct c3_isp_mbus_format_info
>>>> +*core_find_format_by_code(u32 code, u32 pad)
>>>> +{
>>>> +     int i;
>>> unsigned
>>
>> Will use unsigned int i.
>>
>>>> +
>>>> +     for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>>>> +             const struct c3_isp_mbus_format_info *info =
>>>> +                     &c3_isp_core_mbus_formats[i];
>>>> +
>>>> +             if (info->mbus_code == code && info->pads & BIT(pad))
>>>> +                     return info;
>>>> +     }
>>>> +
>>>> +     return NULL;
>>>> +}
>>>> +
>>>> +static const struct c3_isp_mbus_format_info
>>>> +*core_find_format_by_index(u32 index, u32 pad)
>>>> +{
>>>> +     int i;
>>> unsigned
>>>
>> Will use unsigned int i.
>>
>>>> +
>>>> +     for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>>>> +             const struct c3_isp_mbus_format_info *info =
>>>> +                     &c3_isp_core_mbus_formats[i];
>>>> +
>>>> +             if (!(info->pads & BIT(pad)))
>>>> +                     continue;
>>>> +
>>>> +             if (!index)
>>>> +                     return info;
>>>> +
>>>> +             index--;
>>>> +     }
>>>> +
>>>> +     return NULL;
>>>> +}
>>>> +
>>>> +static void c3_isp_core_enable(struct c3_isp_device *isp)
>>>> +{
>>>> +     /* Select the line sync signal */
>>>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_LINE_THRD,
>>>> +                        TOP_IRQ_DIN_HSYNC, TOP_IRQ_DIN_HSYNC);
>>>> +
>>>> +     /* Enable frame done and stats error irq */
>>>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
>>>> +                        TOP_IRQ_FRAME_DONE, TOP_IRQ_FRAME_DONE);
>>>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
>>>> +                        TOP_IRQ_STATS_ERR, TOP_IRQ_STATS_ERR);
>>>> +
>>>> +     /* Enable image data to ISP core */
>>>> +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
>>>> +                        TOP_DATA_PATH_MASK, TOP_PATH_MIPI_TO_CORE);
>>>> +}
>>>> +
>>>> +static void c3_isp_core_disable(struct c3_isp_device *isp)
>>>> +{
>>>> +     /* Disable image data to ISP core */
>>>> +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
>>>> +                        TOP_DATA_PATH_MASK, 0x0);
>>>> +
>>>> +     /* Disable all irq */
>>>> +     c3_isp_write(isp, ISP_TOP_IRQ_EN, 0x0);
>>>> +}
>>>> +
>>>> +/* Set the phase offset of blc, wb and lns */
>>>> +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
>>>> +                               u8 xofst, u8 yofst)
>>>> +{
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
>>>> +                        LSWB_BLC_XPHS_OFST_MASK,
>>>> +                        xofst << LSWB_BLC_XPHS_OFST_SHIFT);
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
>>>> +                        LSWB_BLC_YPHS_OFST_MASK, yofst);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
>>>> +                        LSWB_WB_XPHS_OFST_MASK,
>>>> +                        xofst << LSWB_WB_XPHS_OFST_SHIFT);
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
>>>> +                        LSWB_WB_YPHS_OFST_MASK, yofst);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
>>>> +                        LSWB_LNS_XPHS_OFST_MASK,
>>>> +                        xofst << LSWB_LNS_XPHS_OFST_SHIFT);
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
>>>> +                        LSWB_LNS_YPHS_OFST_MASK, yofst);
>>>> +}
>>>> +
>>>> +/* Set the phase offset of af, ae and awb */
>>>> +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
>>>> +                             u8 xofst, u8 yofst)
>>>> +{
>>>> +     c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_XPHS_OFST_MASK,
>>>> +                        xofst << AF_CTRL_XPHS_OFST_SHIFT);
>>>> +     c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_YPHS_OFST_MASK,
>>>> +                        yofst << AF_CTRL_YPHS_OFST_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_XPHS_OFST_MASK,
>>>> +                        xofst << AE_CTRL_XPHS_OFST_SHIFT);
>>>> +     c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_YPHS_OFST_MASK,
>>>> +                        yofst << AE_CTRL_YPHS_OFST_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_XPHS_OFST_MASK,
>>>> +                        xofst << AWB_CTRL_XPHS_OFST_SHIFT);
>>>> +     c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_YPHS_OFST_MASK, yofst);
>>>> +}
>>>> +
>>>> +/* Set the phase offset of demosaic */
>>>> +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
>>>> +                              u8 xofst, u8 yofst)
>>>> +{
>>>> +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_XPHS_OFST_MASK,
>>>> +                        xofst << DMS_COMMON_XPHS_OFST_SHIFT);
>>>> +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_YPHS_OFST_MASK, yofst);
>>>> +}
>>>> +
>>>> +static void c3_isp_core_cfg_ofst(struct c3_isp_device *isp,
>>>> +                              struct v4l2_mbus_framefmt *fmt)
>>>> +{
>>>> +     const struct c3_isp_mbus_format_info *isp_fmt =
>>>> +                     core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
>>>> +
>>>> +     c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>>>> +     c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>>>> +     c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>>>> +}
>>>> +
>>>> +/* Set format of the hardware control module */
>>>> +static void c3_isp_core_top_fmt(struct c3_isp_device *isp,
>>>> +                             struct v4l2_mbus_framefmt *fmt)
>>>> +{
>>>> +     c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
>>>> +                  TOP_INPUT_HSIZE(fmt->width) | TOP_INPUT_VSIZE(fmt->height));
>>>> +
>>>> +     c3_isp_write(isp, ISP_TOP_FRM_SIZE,
>>>> +                  TOP_FRM_CORE_IHSIZE(fmt->width) | TOP_FRM_CORE_IVSIZE(fmt->height));
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE, TOP_HOLD_HSIZE_MASK,
>>>> +                        fmt->width << TOP_HOLD_HSIZE_SHIFT);
>>>> +}
>>>> +
>>>> +static void c3_isp_core_af_fmt(struct c3_isp_device *isp,
>>>> +                            struct v4l2_mbus_framefmt *fmt)
>>>> +{
>>>> +     u32 hidx;
>>>> +     u32 vidx;
>>>> +     int i;
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, AF_HV_STAT_HBLK_NUM_MASK,
>>>> +                        AF_STAT_BLKH_NUM << AF_HV_STAT_HBLK_NUM_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM,
>>>> +                        AF_HV_STAT_VBLK_NUM_MASK, AF_STAT_BLKV_NUM);
>>>> +
>>>> +     c3_isp_write(isp, ISP_AF_HV_SIZE,
>>>> +                  AF_HV_STAT_HSIZE(fmt->width) | AF_HV_STAT_VSIZE(fmt->height));
>>>> +
>>>> +     /* Set the index address to 0 position */
>>>> +     c3_isp_write(isp, ISP_AF_IDX_ADDR, 0);
>>>> +     /*
>>>> +      * Calculate and set the coordinates of points in the grid.
>>>> +      * hidx and vidx need to be aligned with 2.
>>>> +      */
>>>> +     for (i = 0; i <= AF_STAT_BLKH_NUM; i++) {
>>>> +             hidx = i * fmt->width / AF_STAT_BLKH_NUM;
>>>> +             hidx = ALIGN_DOWN(hidx, 2);
>>>> +
>>>> +             vidx = i * fmt->height / AF_STAT_BLKV_NUM;
>>>> +             vidx = min(vidx, fmt->height);
>>>> +             vidx = ALIGN_DOWN(vidx, 2);
>>>> +             c3_isp_write(isp, ISP_AF_IDX_DATA,
>>>> +                          AF_IDX_HIDX_DATA(hidx) | AF_IDX_VIDX_DATA(vidx));
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_core_ae_fmt(struct c3_isp_device *isp,
>>>> +                            struct v4l2_mbus_framefmt *fmt)
>>>> +{
>>>> +     u32 hidx;
>>>> +     u32 vidx;
>>>> +     int i;
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, AE_HV_STAT_HBLK_NUM_MASK,
>>>> +                        AE_STAT_BLKH_NUM << AE_HV_STAT_HBLK_NUM_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM,
>>>> +                        AE_HV_STAT_VBLK_NUM_MASK, AE_STAT_BLKV_NUM);
>>>> +
>>>> +     c3_isp_write(isp, ISP_AE_HV_SIZE,
>>>> +                  AE_HV_STAT_HSIZE(fmt->width) | AE_HV_STAT_VSIZE(fmt->height));
>>>> +
>>>> +     /* Set the index address to 0 position */
>>>> +     c3_isp_write(isp, ISP_AE_IDX_ADDR, 0);
>>>> +     /*
>>>> +      * Calculate and set the coordinates of points in the grid.
>>>> +      * hidx and vidx need to be aligned with 2.
>>>> +      */
>>>> +     for (i = 0; i <= AE_STAT_BLKH_NUM; i++) {
>>>> +             hidx = i * fmt->width / AE_STAT_BLKH_NUM;
>>>> +             hidx = ALIGN_DOWN(hidx, 2);
>>>> +
>>>> +             vidx = i * fmt->height / AE_STAT_BLKV_NUM;
>>>> +             vidx = min(vidx, fmt->height);
>>>> +             vidx = ALIGN_DOWN(vidx, 2);
>>>> +
>>>> +             c3_isp_write(isp, ISP_AE_IDX_DATA,
>>>> +                          AE_IDX_HIDX_DATA(hidx) | AE_IDX_VIDX_DATA(vidx));
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_core_awb_fmt(struct c3_isp_device *isp,
>>>> +                             struct v4l2_mbus_framefmt *fmt)
>>>> +{
>>>> +     u32 hidx;
>>>> +     u32 vidx;
>>>> +     int i;
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, AWB_HV_STAT_HBLK_NUM_MASK,
>>>> +                        AWB_STAT_BLKH_NUM << AWB_HV_STAT_HBLK_NUM_SHIFT);
>>>> +     c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM,
>>>> +                        AWB_HV_STAT_VBLK_NUM_MASK, AWB_STAT_BLKV_NUM);
>>>> +
>>>> +     c3_isp_write(isp, ISP_AWB_HV_SIZE,
>>>> +                  AWB_HV_STAT_HSIZE(fmt->width) | AWB_HV_STAT_VSIZE(fmt->height));
>>>> +
>>>> +     /* Set the index address to 0 position */
>>>> +     c3_isp_write(isp, ISP_AWB_IDX_ADDR, 0);
>>>> +     /*
>>>> +      * Calculate and set the coordinates of points in the grid.
>>>> +      * hidx and vidx need to be aligned with 2.
>>>> +      */
>>>> +     for (i = 0; i <= AWB_STAT_BLKH_NUM; i++) {
>>>> +             hidx = i * fmt->width / AWB_STAT_BLKH_NUM;
>>>> +             hidx = ALIGN_DOWN(hidx, 2);
>>>> +
>>>> +             vidx = i * fmt->height / AWB_STAT_BLKV_NUM;
>>>> +             vidx = min(vidx, fmt->height);
>>>> +             vidx = ALIGN_DOWN(vidx, 2);
>>>> +
>>>> +             c3_isp_write(isp, ISP_AWB_IDX_DATA,
>>>> +                          AWB_IDX_HIDX_DATA(hidx) | AWB_IDX_VIDX_DATA(vidx));
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
>>>> +                                struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *fmt;
>>>> +
>>>> +     fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>>>> +
>>>> +     c3_isp_core_cfg_ofst(isp, fmt);
>>>> +     c3_isp_core_top_fmt(isp, fmt);
>>>> +     c3_isp_core_af_fmt(isp, fmt);
>>>> +     c3_isp_core_ae_fmt(isp, fmt);
>>>> +     c3_isp_core_awb_fmt(isp, fmt);
>>>> +}
>>>> +
>>>> +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
>>>> +                                   struct v4l2_subdev_state *state,
>>>> +                                   u32 pad, u64 streams_mask)
>>>> +{
>>>> +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>>>> +     u64 sink_streams;
>>>> +     int ret;
>>>> +
>>>> +     core->isp->frm_sequence = 0;
>>>> +     c3_isp_core_cfg_format(core->isp, state);
>>>> +     c3_isp_core_enable(core->isp);
>>>> +
>>>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>>> +                                                    C3_ISP_CORE_PAD_SINK_VIDEO,
>>>> +                                                    &streams_mask);
>>>> +     ret = v4l2_subdev_enable_streams(core->src_sd,
>>>> +                                      core->src_sd_pad, sink_streams);
>>>> +     if (ret) {
>>>> +             c3_isp_core_disable(core->isp);
>>>> +             return ret;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
>>>> +                                    struct v4l2_subdev_state *state,
>>>> +                                    u32 pad, u64 streams_mask)
>>>> +{
>>>> +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>>>> +     u64 sink_streams;
>>>> +     int ret;
>>>> +
>>>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>>> +                                                    C3_ISP_CORE_PAD_SINK_VIDEO,
>>>> +                                                    &streams_mask);
>>>> +     ret = v4l2_subdev_disable_streams(core->src_sd,
>>>> +                                       core->src_sd_pad, sink_streams);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     c3_isp_core_disable(core->isp);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
>>>> +                                struct v4l2_subdev_state *state,
>>>> +                                struct v4l2_subdev_krouting *routing)
>>>> +{
>>>> +     static const struct v4l2_mbus_framefmt format = {
>>>> +             .width = C3_ISP_DEFAULT_WIDTH,
>>>> +             .height = C3_ISP_DEFAULT_HEIGHT,
>>>> +             .code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
>>>> +             .field = V4L2_FIELD_NONE,
>>>> +             .colorspace = V4L2_COLORSPACE_SRGB,
>>>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>>>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>>>> +             .xfer_func = V4L2_XFER_FUNC_SRGB,
>>>> +     };
>>>> +     int ret;
>>>> +
>>>> +     ret = v4l2_subdev_routing_validate(sd, routing,
>>>> +                                        V4L2_SUBDEV_ROUTING_NO_SOURCE_STREAM_MIX);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
>>>> +                                 struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_subdev_route routes[2];
>>>> +     struct v4l2_subdev_krouting routing;
>>>> +
>>>> +     routes[0].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
>>>> +     routes[0].sink_stream = 0;
>>>> +     routes[0].source_pad = C3_ISP_CORE_PAD_SOURCE_STATS;
>>>> +     routes[0].source_stream = 0;
>>>> +     routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>>>> +
>>>> +     routes[1].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
>>>> +     routes[1].sink_stream = 0;
>>>> +     routes[1].source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
>>>> +     routes[1].source_stream = 0;
>>>> +     routes[1].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>>>> +
>>>> +     routing.num_routes = ARRAY_SIZE(routes);
>>>> +     routing.routes = routes;
>>>> +
>>>> +     return c3_isp_core_cfg_routing(sd, state, &routing);
>>>> +}
>>>> +
>>>> +static int c3_isp_core_set_routing(struct v4l2_subdev *sd,
>>>> +                                struct v4l2_subdev_state *state,
>>>> +                                enum v4l2_subdev_format_whence which,
>>>> +                                struct v4l2_subdev_krouting *routing)
>>> I'm not sure I see a reason for the ISP subdev to implement routing.
>>> In my understanding it will only receive a single image stream and
>>> processes it to 3 DMA output nodes, and to control which output is
>>> enabled you use media links.
>>>
>> There is no need to use routing.
>>
>> Will remove this interface.
>>
>>>> +{
>>>> +     bool is_streaming = v4l2_subdev_is_streaming(sd);
>>>> +
>>>> +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>>>> +             return -EBUSY;
>>>> +
>>>> +     return c3_isp_core_cfg_routing(sd, state, routing);
>>>> +}
>>>> +
>>>> +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
>>>> +                                   struct v4l2_subdev_state *state,
>>>> +                                   struct v4l2_subdev_mbus_code_enum *code)
>>>> +{
>>>> +     const struct c3_isp_mbus_format_info *info;
>>>> +     int ret = 0;
>>>> +
>>>> +     switch (code->pad) {
>>>> +     case C3_ISP_CORE_PAD_SINK_VIDEO:
>>>> +     case C3_ISP_CORE_PAD_SOURCE_VIDEO:
>>>> +             info = core_find_format_by_index(code->index, code->pad);
>>>> +             if (!info)
>>>> +                     ret = -EINVAL;
>>>> +             else
>>>> +                     code->code = info->mbus_code;
>>>> +
>>>> +             break;
>>>> +     case C3_ISP_CORE_PAD_SINK_PARAMS:
>>>> +     case C3_ISP_CORE_PAD_SOURCE_STATS:
>>>> +             if (code->index)
>>>> +                     ret = -EINVAL;
>>>> +             else
>>>> +                     code->code = MEDIA_BUS_FMT_METADATA_FIXED;
>>>> +
>>>> +             break;
>>>> +     default:
>>>> +             ret = -EINVAL;
>>>> +             break;
>>>> +     }
>>>> +
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_isp_core_set_sink_fmt(struct v4l2_subdev_state *state,
>>>> +                                  struct v4l2_subdev_format *format)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *sink_fmt;
>>>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>>>> +
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>>> +
>>>> +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
>>>> +     if (!isp_fmt)
>>>> +             sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>>>> +     else
>>>> +             sink_fmt->code = format->format.code;
>>>> +
>>>> +     sink_fmt->width = clamp_t(u32, format->format.width,
>>>> +                               C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>>>> +     sink_fmt->height = clamp_t(u32, format->format.height,
>>>> +                                C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>>>> +
>>>> +     format->format = *sink_fmt;
>>>> +}
>>>> +
>>>> +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
>>>> +                                    struct v4l2_subdev_format *format)
>>>> +{
>>>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>>>> +     struct v4l2_mbus_framefmt *sink_fmt;
>>>> +     struct v4l2_mbus_framefmt *src_fmt;
>>>> +
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>>> +
>>>> +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
>>>> +     if (!isp_fmt)
>>>> +             src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>>>> +     else
>>>> +             src_fmt->code = format->format.code;
>>>> +
>>>> +     /* The source size must be same with the sink size. */
>>>> +     src_fmt->width  = sink_fmt->width;
>>>> +     src_fmt->height = sink_fmt->height;
>>>> +
>>>> +     format->format = *src_fmt;
>>>> +}
>>>> +
>>>> +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
>>>> +                            struct v4l2_subdev_state *state,
>>>> +                            struct v4l2_subdev_format *format)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *fmt;
>>>> +
>>>> +     switch (format->pad) {
>>>> +     case C3_ISP_CORE_PAD_SINK_VIDEO:
>>>> +             c3_isp_core_set_sink_fmt(state, format);
>>>> +             break;
>>>> +     case C3_ISP_CORE_PAD_SINK_PARAMS:
>>>> +     case C3_ISP_CORE_PAD_SOURCE_STATS:
>>>> +             fmt = v4l2_subdev_state_get_format(state, format->pad);
>>>> +             format->format = *fmt;
>>>> +             break;
>>>> +     case C3_ISP_CORE_PAD_SOURCE_VIDEO:
>>>> +             c3_isp_core_set_source_fmt(state, format);
>>>> +             break;
>>>> +     default:
>>> I don't think this can happen. The core validates that format->pad is
>>> correct.
>>>
>> Will remove this branch or use "if" to replace "switch".
>>>> +             dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
>>>> +             return -ENOTTY;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_core_init_state(struct v4l2_subdev *sd,
>>>> +                               struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *sink_fmt;
>>>> +     struct v4l2_mbus_framefmt *src_fmt;
>>>> +
>>>> +     /* Video sink pad */
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>>>> +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>>>> +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>>>> +     sink_fmt->field = V4L2_FIELD_NONE;
>>>> +     sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>>>> +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>>>> +     sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
>>>> +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>>> +     sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
>>>> +
>>>> +     /* Video source pad */
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
>>>> +     src_fmt->width = C3_ISP_DEFAULT_WIDTH;
>>>> +     src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>>>> +     src_fmt->field = V4L2_FIELD_NONE;
>>>> +     src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>>>> +     src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>>> +     src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>>>> +     src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>>> +     src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>>>> +
>>>> +     /* Parameters pad */
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
>>>> +     sink_fmt->width = 0;
>>>> +     sink_fmt->height = 0;
>>>> +     sink_fmt->field = V4L2_FIELD_NONE;
>>>> +     sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>>>> +
>>>> +     /* Statistics pad */
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
>>>> +     src_fmt->width = 0;
>>>> +     src_fmt->height = 0;
>>>> +     src_fmt->field = V4L2_FIELD_NONE;
>>>> +     src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>>>> +
>>>> +     return c3_isp_core_init_routing(sd, state);
>>>> +}
>>>> +
>>>> +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
>>>> +     .enum_mbus_code = c3_isp_core_enum_mbus_code,
>>>> +     .get_fmt = v4l2_subdev_get_fmt,
>>>> +     .set_fmt = c3_isp_core_set_fmt,
>>>> +     .enable_streams = c3_isp_core_enable_streams,
>>>> +     .disable_streams = c3_isp_core_disable_streams,
>>>> +     .set_routing = c3_isp_core_set_routing,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
>>>> +     .pad = &c3_isp_core_pad_ops,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
>>>> +     .init_state = c3_isp_core_init_state,
>>>> +};
>>>> +
>>>> +static int c3_isp_core_link_validate(struct media_link *link)
>>>> +{
>>>> +     if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
>>>> +             return 0;
>>>> +
>>>> +     return v4l2_subdev_link_validate(link);
>>>> +}
>>>> +
>>>> +/* Media entity operations */
>>>> +static const struct media_entity_operations c3_isp_core_entity_ops = {
>>>> +     .link_validate = c3_isp_core_link_validate,
>>>> +};
>>>> +
>>>> +int c3_isp_core_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_core *core = &isp->core;
>>>> +     struct v4l2_subdev *sd = &core->sd;
>>>> +     int ret;
>>>> +
>>>> +     v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
>>>> +     sd->owner = THIS_MODULE;
>>>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>>> +     sd->internal_ops = &c3_isp_core_internal_ops;
>>>> +     snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
>>>> +
>>>> +     sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
>>>> +     sd->entity.ops = &c3_isp_core_entity_ops;
>>>> +
>>>> +     core->isp = isp;
>>>> +     sd->dev = isp->dev;
>>>> +     v4l2_set_subdevdata(sd, core);
>>>> +
>>>> +     core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
>>>> +     core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
>>>> +     core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
>>>> +     core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
>>>> +     ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = v4l2_subdev_init_finalize(sd);
>>>> +     if (ret)
>>>> +             goto err_entity_cleanup;
>>>> +
>>>> +     ret = v4l2_device_register_subdev(&isp->v4l2_dev, sd);
>>>> +     if (ret)
>>>> +             goto err_subdev_cleanup;
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_subdev_cleanup:
>>>> +     v4l2_subdev_cleanup(sd);
>>>> +err_entity_cleanup:
>>>> +     media_entity_cleanup(&sd->entity);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +void c3_isp_core_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_core *core = &isp->core;
>>>> +     struct v4l2_subdev *sd = &core->sd;
>>>> +
>>>> +     v4l2_device_unregister_subdev(sd);
>>>> +     v4l2_subdev_cleanup(sd);
>>>> +     media_entity_cleanup(&sd->entity);
>>>> +}
>>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
>>>> new file mode 100644
>>>> index 000000000000..a57b9f8dbc3c
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
>>>> @@ -0,0 +1,486 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#include <linux/clk.h>
>>>> +#include <linux/device.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/mutex.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +
>>>> +#include <media/v4l2-common.h>
>>>> +#include <media/v4l2-device.h>
>>>> +#include <media/v4l2-fwnode.h>
>>>> +#include <media/v4l2-mc.h>
>>>> +
>>>> +#include "c3-isp-common.h"
>>>> +#include "c3-isp-regs.h"
>>>> +
>>>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
>>>> +{
>>>> +     return readl(isp->base + reg);
>>>> +}
>>>> +
>>>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
>>>> +{
>>>> +     writel(val, isp->base + reg);
>>>> +}
>>>> +
>>>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
>>>> +{
>>>> +     u32 orig, tmp;
>>>> +
>>>> +     orig = c3_isp_read(isp, reg);
>>>> +
>>>> +     tmp = orig & ~mask;
>>>> +     tmp |= val & mask;
>>>> +
>>>> +     if (tmp != orig)
>>>> +             c3_isp_write(isp, reg, tmp);
>>>> +}
>>>> +
>>>> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct media_pipeline_entity_iter iter;
>>>> +     unsigned int n_video_devices = 0;
>>>> +     struct media_entity *entity;
>>>> +     int ret;
>>>> +
>>>> +     ret = media_pipeline_entity_iter_init(&isp->pipe, &iter);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     media_pipeline_for_each_entity(&isp->pipe, &iter, entity) {
>>>> +             if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
>>>> +                     n_video_devices++;
>>>> +     }
>>>> +
>>>> +     media_pipeline_entity_iter_cleanup(&iter);
>>>> +
>>>> +     return n_video_devices == isp->pipe.start_count;
>>>> +}
>>> As suggested in the review of the -params.c module, I suggest to
>>> implement start_streaming on the capture nodes only. From there you
>>> can call enable_streams on the ISP subdevice. The ISP subdevice can
>>> count how many links to resizers are enabled, and actually start its
>>> operation when all the enabled ones have been started.
>>>
>>> The idea is to use enabled media links to identify how many capture video
>>> devices are expected to be used, and only start the ISP (and the
>>> downstream subdevices like the CSI-2 Adap and RX and the sensor) when
>>> all of linked ones have been started.
>>>
>> OK, Will implement start_streaming on the capture nodes only and count how
>> many
>>
>> links to resizer are enabled in ISP sub device.
>>
>>>> +
>>>> +/* PM runtime suspend */
>>>> +static int __maybe_unused c3_isp_runtime_suspend(struct device *dev)
>>>> +{
>>>> +     struct c3_isp_device *isp = dev_get_drvdata(dev);
>>>> +
>>>> +     clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +/* PM runtime resume */
>>>> +static int __maybe_unused c3_isp_runtime_resume(struct device *dev)
>>>> +{
>>>> +     struct c3_isp_device *isp = dev_get_drvdata(dev);
>>>> +
>>>> +     return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
>>>> +}
>>>> +
>>>> +static const struct dev_pm_ops c3_isp_pm_ops = {
>>>> +     SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>>>> +                             pm_runtime_force_resume)
>>>> +     SET_RUNTIME_PM_OPS(c3_isp_runtime_suspend,
>>>> +                        c3_isp_runtime_resume, NULL)
>>>> +};
>>>> +
>>>> +/* IRQ handling */
>>>> +static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
>>>> +{
>>>> +     struct c3_isp_device *isp = dev;
>>>> +     u32 status;
>>>> +
>>>> +     /* Get irq status and clear irq status */
>>>> +     status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
>>>> +     c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
>>>> +
>>>> +     if (status & TOP_IRQ_FRAME_DONE) {
>>>> +             c3_isp_stats_done(isp);
>>>> +             c3_isp_params_done(isp);
>>>> +             c3_isp_captures_done(isp);
>>>> +             isp->frm_sequence++;
>>>> +     }
>>>> +
>>>> +     if (status & TOP_IRQ_STATS_ERR)
>>>> +             dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
>>>> +
>>>> +     return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +/* Subdev notifier register */
>>>> +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
>>>> +                            struct v4l2_subdev *sd,
>>>> +                            struct v4l2_async_connection *asc)
>>>> +{
>>>> +     struct c3_isp_device *isp =
>>>> +             container_of(notifier, struct c3_isp_device, notifier);
>>>> +     struct c3_isp_core *core = &isp->core;
>>>> +     struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
>>>> +     int ret;
>>>> +
>>>> +     ret = media_entity_get_fwnode_pad(&sd->entity,
>>>> +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
>>>> +     if (ret < 0) {
>>>> +             dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
>>>> +             return ret;
>>>> +     }
>>>> +
>>>> +     core->src_sd = sd;
>>>> +     core->src_sd_pad = ret;
>>>> +
>>>> +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
>>>> +                                            MEDIA_LNK_FL_IMMUTABLE);
>>>> +}
>>>> +
>>>> +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
>>>> +{
>>>> +     struct c3_isp_device *isp =
>>>> +             container_of(notifier, struct c3_isp_device, notifier);
>>>> +     int ret;
>>>> +
>>>> +     ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
>>>> +     if (ret < 0) {
>>>> +             dev_err(isp->dev,
>>>> +                     "Failed to register subdev nodes: %d\n", ret);
>>>> +             return ret;
>>>> +     }
>>>> +
>>>> +     dev_info(isp->dev, "notify complete\n");
>>>> +
>>>> +     return media_device_register(&isp->media_dev);
>>>> +}
>>>> +
>>>> +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
>>>> +{
>>>> +     struct c3_isp_device *isp =
>>>> +             container_of(asc->notifier, struct c3_isp_device, notifier);
>>>> +
>>>> +     media_device_unregister(&isp->media_dev);
>>>> +}
>>>> +
>>>> +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
>>>> +     .bound = c3_isp_notify_bound,
>>>> +     .complete = c3_isp_notify_complete,
>>>> +     .destroy = c3_isp_notify_destroy,
>>>> +};
>>>> +
>>>> +static int c3_isp_async_nf_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct v4l2_async_connection *asc;
>>>> +     struct fwnode_handle *ep;
>>>> +     int ret;
>>>> +
>>>> +     v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
>>>> +
>>>> +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
>>>> +                                          FWNODE_GRAPH_ENDPOINT_NEXT);
>>>> +     if (!ep)
>>>> +             return -ENOTCONN;
>>>> +
>>>> +     asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
>>>> +                                           struct v4l2_async_connection);
>>>> +     if (IS_ERR(asc)) {
>>>> +             fwnode_handle_put(ep);
>>>> +             return PTR_ERR(asc);
>>>> +     }
>>>> +
>>>> +     fwnode_handle_put(ep);
>>>> +
>>>> +     isp->notifier.ops = &c3_isp_notify_ops;
>>>> +     ret = v4l2_async_nf_register(&isp->notifier);
>>>> +     if (ret)
>>>> +             v4l2_async_nf_cleanup(&isp->notifier);
>>>> +
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     v4l2_async_nf_unregister(&isp->notifier);
>>>> +     v4l2_async_nf_cleanup(&isp->notifier);
>>>> +}
>>>> +
>>>> +static int c3_isp_v4l2_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct media_device *media_dev = &isp->media_dev;
>>>> +     struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
>>>> +     int ret;
>>>> +
>>>> +     /* Initialize media device */
>>>> +     strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
>>>> +             sizeof(media_dev->model));
>>>> +     media_dev->dev = isp->dev;
>>>> +
>>>> +     media_device_init(media_dev);
>>>> +
>>>> +     /* Initialize v4l2 device */
>>>> +     v4l2_dev->mdev = media_dev;
>>>> +     strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
>>>> +             sizeof(v4l2_dev->name));
>>>> +
>>>> +     ret = v4l2_device_register(isp->dev, v4l2_dev);
>>>> +     if (ret) {
>>>> +             media_device_cleanup(media_dev);
>>>> +             dev_err(isp->dev,
>>>> +                     "Failed to register V4L2 device: %d\n", ret);
>>>> +     }
>>>> +
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     v4l2_device_unregister(&isp->v4l2_dev);
>>>> +     media_device_cleanup(&isp->media_dev);
>>>> +}
>>>> +
>>>> +static void c3_isp_remove_links(struct c3_isp_device *isp)
>>>> +{
>>>> +     unsigned int i;
>>>> +
>>>> +     media_entity_remove_links(&isp->core.sd.entity);
>>>> +
>>>> +     for (i = 0; i < C3_ISP_NUM_RSZ; i++)
>>>> +             media_entity_remove_links(&isp->resizers[i].sd.entity);
>>>> +
>>>> +     for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
>>>> +             media_entity_remove_links(&isp->caps[i].vdev.entity);
>>>> +}
>>>> +
>>>> +static int c3_isp_create_links(struct c3_isp_device *isp)
>>>> +{
>>>> +     unsigned int i;
>>>> +     int ret;
>>>> +
>>>> +     for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
>>>> +             ret = media_create_pad_link(&isp->resizers[i].sd.entity,
>>>> +                                         C3_ISP_RESIZER_PAD_SOURCE,
>>>> +                                         &isp->resizers[i].cap->vdev.entity,
>>>> +                                         0, MEDIA_LNK_FL_ENABLED);
>>> This could be made IMMUTABLE
>>>
>> OK,  will add IMMUTABLE flag.
>>
>>>> +             if (ret) {
>>>> +                     dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
>>>> +                     goto err_remove_links;
>>>> +             }
>>>> +
>>>> +             ret = media_create_pad_link(&isp->core.sd.entity,
>>>> +                                         C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>>> +                                         &isp->resizers[i].sd.entity,
>>>> +                                         C3_ISP_RESIZER_PAD_SINK,
>>>> +                                         MEDIA_LNK_FL_ENABLED);
>>>> +             if (ret) {
>>>> +                     dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
>>>> +                     goto err_remove_links;
>>>> +             }
>>>> +     }
>>>> +
>>>> +     ret = media_create_pad_link(&isp->core.sd.entity,
>>>> +                                 C3_ISP_CORE_PAD_SOURCE_STATS,
>>>> +                                 &isp->stats.vdev.entity,
>>>> +                                 0, MEDIA_LNK_FL_ENABLED);
>>>> +     if (ret) {
>>>> +             dev_err(isp->dev, "Failed to link core and stats\n");
>>>> +             goto err_remove_links;
>>>> +     }
>>>> +
>>>> +     ret = media_create_pad_link(&isp->params.vdev.entity, 0,
>>>> +                                 &isp->core.sd.entity,
>>>> +                                 C3_ISP_CORE_PAD_SINK_PARAMS,
>>>> +                                 MEDIA_LNK_FL_ENABLED);
>>>> +     if (ret) {
>>>> +             dev_err(isp->dev, "Failed to link params and core\n");
>>>> +             goto err_remove_links;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_remove_links:
>>>> +     c3_isp_remove_links(isp);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static int c3_isp_videos_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     int ret;
>>>> +
>>>> +     ret = c3_isp_captures_register(isp);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = c3_isp_stats_register(isp);
>>>> +     if (ret)
>>>> +             goto err_captures_unregister;
>>>> +
>>>> +     ret = c3_isp_params_register(isp);
>>>> +     if (ret)
>>>> +             goto err_stats_unregister;
>>>> +
>>>> +     ret = c3_isp_create_links(isp);
>>>> +     if (ret)
>>>> +             goto err_params_unregister;
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_params_unregister:
>>>> +     c3_isp_params_unregister(isp);
>>>> +err_stats_unregister:
>>>> +     c3_isp_stats_unregister(isp);
>>>> +err_captures_unregister:
>>>> +     c3_isp_captures_unregister(isp);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_isp_videos_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     c3_isp_remove_links(isp);
>>>> +     c3_isp_params_unregister(isp);
>>>> +     c3_isp_stats_unregister(isp);
>>>> +     c3_isp_captures_unregister(isp);
>>>> +}
>>>> +
>>>> +static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
>>>> +{
>>>> +     const struct c3_isp_info *info = isp->info;
>>>> +     int ret;
>>>> +     u32 i;
>>>> +
>>>> +     for (i = 0; i < info->clock_num; i++)
>>>> +             isp->clks[i].id = info->clocks[i];
>>>> +
>>>> +     ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     for (i = 0; i < info->clock_num; i++) {
>>>> +             if (!info->clock_rates[i])
>>>> +                     continue;
>>>> +             ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);
>>>> +             if (ret) {
>>>> +                     dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
>>>> +                             info->clock_rates[i]);
>>>> +                     return ret;
>>>> +             }
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_probe(struct platform_device *pdev)
>>>> +{
>>>> +     struct device *dev = &pdev->dev;
>>>> +     struct c3_isp_device *isp;
>>>> +     int irq;
>>>> +     int ret;
>>>> +
>>>> +     isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
>>>> +     if (!isp)
>>>> +             return -ENOMEM;
>>>> +
>>>> +     isp->info = of_device_get_match_data(dev);
>>>> +     isp->dev = dev;
>>>> +
>>>> +     isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
>>>> +     if (IS_ERR(isp->base))
>>>> +             return dev_err_probe(dev, PTR_ERR(isp->base),
>>>> +                                  "Failed to ioremap resource\n");
>>>> +
>>>> +     irq = platform_get_irq(pdev, 0);
>>>> +     if (irq < 0)
>>>> +             return irq;
>>>> +
>>>> +     ret = c3_isp_cfg_clocks(isp);
>>>> +     if (ret)
>>>> +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
>>>> +
>>>> +     platform_set_drvdata(pdev, isp);
>>>> +
>>>> +     pm_runtime_enable(dev);
>>>> +
>>>> +     ret = c3_isp_v4l2_register(isp);
>>>> +     if (ret)
>>>> +             goto err_runtime_disable;
>>>> +
>>>> +     ret = c3_isp_core_register(isp);
>>>> +     if (ret)
>>>> +             goto err_v4l2_unregister;
>>>> +
>>>> +     ret = c3_isp_resizers_register(isp);
>>>> +     if (ret)
>>>> +             goto err_core_unregister;
>>>> +
>>>> +     ret = c3_isp_async_nf_register(isp);
>>>> +     if (ret)
>>>> +             goto err_resizers_unregister;
>>>> +
>>>> +     ret = c3_isp_videos_register(isp);
>>>> +     if (ret)
>>>> +             goto err_nf_unregister;
>>>> +
>>>> +     ret = devm_request_irq(dev, irq,
>>>> +                            c3_isp_irq_handler, IRQF_SHARED,
>>>> +                            dev_driver_string(dev), isp);
>>>> +     if (ret)
>>>> +             goto err_streams_unregister;
>>> I would request the IRQ before registering devices to userspace.
>>
>> Will move IRQ to the front of registering devices.
>>
>>>> +
>>>> +     mutex_init(&isp->lock);
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_streams_unregister:
>>>> +     c3_isp_videos_unregister(isp);
>>>> +err_nf_unregister:
>>>> +     c3_isp_async_nf_unregister(isp);
>>>> +err_resizers_unregister:
>>>> +     c3_isp_resizers_unregister(isp);
>>>> +err_core_unregister:
>>>> +     c3_isp_core_unregister(isp);
>>>> +err_v4l2_unregister:
>>>> +     c3_isp_v4l2_unregister(isp);
>>>> +err_runtime_disable:
>>>> +     pm_runtime_disable(dev);
>>>> +     return ret;
>>>> +};
>>>> +
>>>> +static void c3_isp_remove(struct platform_device *pdev)
>>>> +{
>>>> +     struct c3_isp_device *isp = platform_get_drvdata(pdev);
>>>> +
>>>> +     mutex_destroy(&isp->lock);
>>>> +     c3_isp_videos_unregister(isp);
>>>> +     c3_isp_async_nf_unregister(isp);
>>>> +     c3_isp_core_unregister(isp);
>>>> +     c3_isp_resizers_unregister(isp);
>>>> +     c3_isp_v4l2_unregister(isp);
>>>> +     pm_runtime_disable(isp->dev);
>>>> +};
>>>> +
>>>> +static const struct c3_isp_info isp_info = {
>>>> +     .clocks = {"vapb", "isp0"},
>>>> +     .clock_rates = {0, 400000000},
>>>> +     .clock_num = 2
>>>> +};
>>>> +
>>>> +static const struct of_device_id c3_isp_of_match[] = {
>>>> +     { .compatible = "amlogic,c3-isp",
>>>> +       .data = &isp_info },
>>>> +     { },
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, c3_isp_of_match);
>>>> +
>>>> +static struct platform_driver c3_isp_driver = {
>>>> +     .probe = c3_isp_probe,
>>>> +     .remove = c3_isp_remove,
>>>> +     .driver = {
>>>> +             .name = "c3-isp",
>>>> +             .of_match_table = c3_isp_of_match,
>>>> +             .pm = &c3_isp_pm_ops,
>>>> +     },
>>>> +};
>>>> +
>>>> +module_platform_driver(c3_isp_driver);
>>>> +
>>>> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
>>>> +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
>>>> +MODULE_LICENSE("GPL");
>>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
>>>> new file mode 100644
>>>> index 000000000000..8a6b7ce86eaf
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
>>>> @@ -0,0 +1,857 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#include <linux/pm_runtime.h>
>>>> +
>>>> +#include <media/v4l2-ctrls.h>
>>>> +#include <media/v4l2-event.h>
>>> Do you need these ?
>>>
>> Will remove these two lines.
>>
>>>> +#include <media/v4l2-ioctl.h>
>>>> +#include <media/v4l2-mc.h>
>>>> +#include <media/videobuf2-dma-contig.h>
>>> See below about using the dma-contig vb2 ops
>>
>> Will use vmalloc ops.
>>
>>>> +
>>>> +#include "c3-isp-common.h"
>>>> +#include "c3-isp-regs.h"
>>>> +#include "include/uapi/c3-isp-config.h"
>>>> +
>>>> +typedef void (*block_handler)(struct c3_isp_device *isp,
>>>> +                           struct c3_isp_param_block_header *block);
>>>> +
>>>> +struct c3_isp_block_handler {
>>>> +     size_t size;
>>>> +     block_handler handler;
>>>> +};
>>>> +
>>>> +/* Hardware configuration */
>>>> +
>>>> +static void c3_isp_params_cfg_wb_change(struct c3_isp_device *isp,
>>>> +                                     struct c3_isp_param_block_header *block)
>>>> +{
>>>> +     struct wb_change_cfg *wb = (struct wb_change_cfg *)block;
>>>> +
>>>> +     if (!block->enabled) {
>>>> +             c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
>>>> +                                TOP_BEO_CTRL_WB_EN, false);
>>>> +             return;
>>>> +     }
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
>>>> +                        TOP_BEO_CTRL_WB_EN, TOP_BEO_CTRL_WB_EN);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, LSWB_WB_GAIN0_MASK,
>>>> +                        wb->wb_gain[0] << LSWB_WB_GAIN0_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0,
>>>> +                        LSWB_WB_GAIN1_MASK, wb->wb_gain[1]);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, LSWB_WB_GAIN2_MASK,
>>>> +                        wb->wb_gain[2] << LSWB_WB_GAIN2_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1,
>>>> +                        LSWB_WB_GAIN3_MASK, wb->wb_gain[3]);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN2,
>>>> +                        LSWB_WB_GAIN4_MASK, wb->wb_gain[4]);
>>>> +
>>>> +     c3_isp_write(isp, ISP_LSWB_WB_LIMIT0,
>>>> +                  LSWB_WB_LIMIT0(wb->wb_limit[0]) | LSWB_WB_LIMIT1(wb->wb_limit[1]));
>>>> +
>>>> +     c3_isp_write(isp, ISP_LSWB_WB_LIMIT1,
>>>> +                  LSWB_WB_LIMIT2(wb->wb_limit[2]) | LSWB_WB_LIMIT3(wb->wb_limit[3]));
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_LIMIT2, LSWB_WB_LIMIT4_MASK,
>>>> +                        wb->wb_limit[4]);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_BL12_GRBGI0_MASK,
>>>> +                        wb->ae_bl12_grbgi[0]);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_GAIN_GRBGI0_MASK,
>>>> +                        wb->ae_gain_grbgi[0] << AE_CRTL2_GAIN_GRBGI0_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_BL12_GRBGI1_MASK,
>>>> +                        wb->ae_bl12_grbgi[1]);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_GAIN_GRBGI1_MASK,
>>>> +                        wb->ae_gain_grbgi[1] << AE_CRTL2_GAIN_GRBGI1_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_BL12_GRBGI2_MASK,
>>>> +                        wb->ae_bl12_grbgi[2]);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_GAIN_GRBGI2_MASK,
>>>> +                        wb->ae_gain_grbgi[2] << AE_CRTL2_GAIN_GRBGI2_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_BL12_GRBGI3_MASK,
>>>> +                        wb->ae_bl12_grbgi[3]);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_GAIN_GRBGI3_MASK,
>>>> +                        wb->ae_gain_grbgi[3] << AE_CRTL2_GAIN_GRBGI3_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_BL12_GRBGI4_MASK,
>>>> +                        wb->ae_bl12_grbgi[4]);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_GAIN_GRBGI4_MASK,
>>>> +                        wb->ae_gain_grbgi[4] << AE_CRTL2_GAIN_GRBGI4_SHIFT);
>>>> +}
>>>> +
>>>> +static void c3_isp_params_cfg_wb_luma(struct c3_isp_device *isp,
>>>> +                                   struct c3_isp_param_block_header *block)
>>>> +{
>>>> +     struct wb_luma_cfg *wb_luma = (struct wb_luma_cfg *)block;
>>>> +
>>>> +     if (!block->enabled)
>>>> +             return;
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_0,
>>>> +                        AWB_STAT_BLC20_GR_MASK, wb_luma->awb_stat_blc20[0]);
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_1,
>>>> +                        AWB_STAT_BLC20_R_MASK, wb_luma->awb_stat_blc20[1]);
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_2,
>>>> +                        AWB_STAT_BLC20_B_MASK, wb_luma->awb_stat_blc20[2]);
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_3,
>>>> +                        AWB_STAT_BLC20_GB_MASK, wb_luma->awb_stat_blc20[3]);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_0,
>>>> +                        AWB_STAT_GAIN10_GR_MASK, wb_luma->awb_stat_gain10[0]);
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_1,
>>>> +                        AWB_STAT_GAIN10_R_MASK, wb_luma->awb_stat_gain10[1]);
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_2,
>>>> +                        AWB_STAT_GAIN10_B_MASK, wb_luma->awb_stat_gain10[2]);
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_3,
>>>> +                        AWB_STAT_GAIN10_GB_MASK, wb_luma->awb_stat_gain10[3]);
>>>> +
>>>> +     c3_isp_write(isp, ISP_AWB_STAT_SATUR_CTRL,
>>>> +                  AWB_STAT_SATUR_LOW(wb_luma->awb_stat_satur_low) |
>>>> +                  AWB_STAT_SATUR_HIGH(wb_luma->awb_stat_satur_high));
>>>> +}
>>>> +
>>>> +static void c3_isp_params_cfg_wb_triangle(struct c3_isp_device *isp,
>>>> +                                       struct c3_isp_param_block_header *block)
>>>> +{
>>>> +     struct wb_triangle_cfg *wb = (struct wb_triangle_cfg *)block;
>>>> +
>>>> +     if (!block->enabled)
>>>> +             return;
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_CTRL2, AWB_STAT_SATUR_VALID_MASK,
>>>> +                        wb->awb_stat_satur_vald);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MIN_MASK,
>>>> +                        wb->awb_stat_rg_min);
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MAX_MASK,
>>>> +                        wb->awb_stat_rg_max << AWB_STAT_RG_MAX_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MIN_MASK,
>>>> +                        wb->awb_stat_bg_min);
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MAX_MASK,
>>>> +                        wb->awb_stat_bg_max << AWB_STAT_BG_MAX_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_LOW_MASK,
>>>> +                        wb->awb_stat_rg_low);
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_HIGH_MASK,
>>>> +                        wb->awb_stat_rg_high << AWB_STAT_RG_HIGH_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_LOW_MASK,
>>>> +                        wb->awb_stat_bg_low);
>>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_HIGH_MASK,
>>>> +                        wb->awb_stat_bg_high << AWB_STAT_BG_HIGH_SHIFT);
>>>> +}
>>>> +
>>>> +static void c3_isp_params_cfg_awb_stats(struct c3_isp_device *isp,
>>>> +                                     struct c3_isp_param_block_header *block)
>>>> +{
>>>> +     struct awb_stats_cfg *awb_stats = (struct awb_stats_cfg *)block;
>>>> +     u32 *weight = awb_stats->awb_stat_blk_weight;
>>>> +     int idx_base;
>>>> +     int group;
>>>> +     int i;
>>>> +
>>>> +     if (!block->enabled)
>>>> +             return;
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AWB_STAT_SWITCH_MASK,
>>>> +                        awb_stats->awb_stat_switch << TOP_3A_AWB_STAT_SWITCH_SHIFT);
>>>> +
>>>> +     /* Calculate the group number */
>>>> +     group = AWB_BLOCK_WT_NUM / AWB_BLK_WT_DATA_NUM_OF_GROUP;
>>>> +
>>>> +     /* Set the weight address to 0 position */
>>>> +     c3_isp_write(isp, ISP_AWB_BLK_WT_ADDR, 0);
>>>> +     for (i = 0; i < group; i++) {
>>> you can now use
>>>           for (unsigned int i = 0; ...)
>>>
>>> if 'i' is not needed outside of the loop
>>
>> Will use unsigned int i in for loop.
>>
>>>> +             idx_base = i * AWB_BLK_WT_DATA_NUM_OF_GROUP;
>>>> +             c3_isp_write(isp, ISP_AWB_BLK_WT_DATA,
>>>> +                          AWB_BLK_WT_DATA0(weight[idx_base + 0]) |
>>>> +                          AWB_BLK_WT_DATA1(weight[idx_base + 1]) |
>>>> +                          AWB_BLK_WT_DATA2(weight[idx_base + 2]) |
>>>> +                          AWB_BLK_WT_DATA3(weight[idx_base + 3]) |
>>>> +                          AWB_BLK_WT_DATA4(weight[idx_base + 4]) |
>>>> +                          AWB_BLK_WT_DATA5(weight[idx_base + 5]) |
>>>> +                          AWB_BLK_WT_DATA6(weight[idx_base + 6]) |
>>>> +                          AWB_BLK_WT_DATA7(weight[idx_base + 7]));
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_params_cfg_ae_stats(struct c3_isp_device *isp,
>>>> +                                    struct c3_isp_param_block_header *block)
>>>> +{
>>>> +     struct ae_stats_cfg *ae_stats = (struct ae_stats_cfg *)block;
>>>> +     u32 *weight = ae_stats->ae_stat_blk_weight;
>>>> +     int idx_base;
>>>> +     int group;
>>>> +     int i;
>>>> +
>>>> +     if (!block->enabled)
>>>> +             return;
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AE_STAT_SWITCH_MASK,
>>>> +                        ae_stats->ae_stat_switch << TOP_3A_AE_STAT_SWITCH_SHIFT);
>>>> +
>>>> +     /* Calculate the group number */
>>>> +     group = AE_BLOCK_WT_NUM / AE_BLK_WT_DATA_NUM_OF_GROUP;
>>>> +
>>>> +     /* Set the weight address to 0 position */
>>>> +     c3_isp_write(isp, ISP_AE_BLK_WT_ADDR, 0);
>>>> +     for (i = 0; i < group; i++) {
>>>> +             idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
>>>> +             c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
>>>> +                          AE_BLK_WT_DATA0(weight[idx_base + 0]) |
>>>> +                          AE_BLK_WT_DATA1(weight[idx_base + 1]) |
>>>> +                          AE_BLK_WT_DATA2(weight[idx_base + 2]) |
>>>> +                          AE_BLK_WT_DATA3(weight[idx_base + 3]) |
>>>> +                          AE_BLK_WT_DATA4(weight[idx_base + 4]) |
>>>> +                          AE_BLK_WT_DATA5(weight[idx_base + 5]) |
>>>> +                          AE_BLK_WT_DATA6(weight[idx_base + 6]) |
>>>> +                          AE_BLK_WT_DATA7(weight[idx_base + 7]));
>>>> +     }
>>>> +
>>>> +     /* Write the last weight data */
>>>> +     idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
>>>> +     c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
>>>> +                  AE_BLK_WT_DATA0(weight[idx_base + 0]) |
>>>> +                  AE_BLK_WT_DATA1(weight[idx_base + 1]) |
>>>> +                  AE_BLK_WT_DATA2(weight[idx_base + 2]) |
>>>> +                  AE_BLK_WT_DATA3(weight[idx_base + 3]) |
>>>> +                  AE_BLK_WT_DATA4(weight[idx_base + 4]) |
>>>> +                  AE_BLK_WT_DATA5(weight[idx_base + 5]) |
>>>> +                  AE_BLK_WT_DATA6(weight[idx_base + 6]));
>>>> +}
>>>> +
>>>> +static void c3_isp_params_cfg_af_stats(struct c3_isp_device *isp,
>>>> +                                    struct c3_isp_param_block_header *block)
>>>> +{
>>>> +     struct af_stats_cfg *af_stats = (struct af_stats_cfg *)block;
>>>> +
>>>> +     if (!block->enabled)
>>>> +             return;
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AF_STAT_SWITCH_MASK,
>>>> +                        af_stats->af_stat_switch << TOP_3A_AF_STAT_SWITCH_SHIFT);
>>>> +}
>>>> +
>>>> +static void c3_isp_params_cfg_pst_gamma(struct c3_isp_device *isp,
>>>> +                                     struct c3_isp_param_block_header *block)
>>>> +{
>>>> +     struct pst_gamma_cfg *gamma = (struct pst_gamma_cfg *)block;
>>>> +     int idx_base;
>>>> +     int i, j;
>>>> +
>>>> +     if (!block->enabled) {
>>>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, false);
>>>> +             return;
>>>> +     }
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, TOP_BED_GAMMA_EN);
>>>> +
>>>> +     for (j = 0; j < GAMMA_LUT_GROUP_NUM; j++) {
>>>> +             c3_isp_write(isp, ISP_PST_GAMMA_LUT_ADDR, PST_GAMMA_LUT_ADDR(j));
>>>> +
>>>> +             /* Calculate the block number */
>>>> +             for (i = 0; i < GAMMA_LUT_POINT_NUM / GAMMA_LUT_DATA_NUM_OF_GROUP; i++) {
>>>> +                     idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
>>>> +                     c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>>>> +                                  PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]) |
>>>> +                                  PST_GAMMA_LUT_DATA1(gamma->pst_gamma_lut[j][idx_base + 1]));
>>>> +             }
>>>> +
>>>> +             /* Write the last one lut data of group j */
>>>> +             idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
>>>> +             c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>>>> +                          PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]));
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_params_cfg_dmsc(struct c3_isp_device *isp,
>>>> +                                struct c3_isp_param_block_header *block)
>>>> +{
>>>> +     if (!block->enabled) {
>>>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, false);
>>>> +             return;
>>>> +     }
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, TOP_BED_DMSC_EN);
>>>> +}
>>>> +
>>>> +/* Configure 4 x 3 ccm matrix */
>>>> +static void c3_isp_params_cfg_ccm(struct c3_isp_device *isp,
>>>> +                               struct c3_isp_param_block_header *block)
>>>> +{
>>>> +     struct ccm_cfg *ccm = (struct ccm_cfg *)block;
>>>> +
>>>> +     if (!block->enabled) {
>>>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, false);
>>>> +             return;
>>>> +     }
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, TOP_BED_CCM_EN);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_00_MASK,
>>>> +                        ccm->ccm_4x3matrix[0][0]);
>>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_01_MASK,
>>>> +                        ccm->ccm_4x3matrix[0][1] << CCM_MTX_01_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_02_MASK,
>>>> +                        ccm->ccm_4x3matrix[0][2]);
>>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_03_MASK,
>>>> +                        ccm->ccm_4x3matrix[0][3] << CCM_MTX_03_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_10_MASK,
>>>> +                        ccm->ccm_4x3matrix[1][0]);
>>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_11_MASK,
>>>> +                        ccm->ccm_4x3matrix[1][1] << CCM_MTX_11_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_12_MASK,
>>>> +                        ccm->ccm_4x3matrix[1][2]);
>>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_13_MASK,
>>>> +                        ccm->ccm_4x3matrix[1][3] << CCM_MTX_13_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_20_MASK,
>>>> +                        ccm->ccm_4x3matrix[2][0]);
>>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_21_MASK,
>>>> +                        ccm->ccm_4x3matrix[2][1] << CCM_MTX_21_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_22_MASK,
>>>> +                        ccm->ccm_4x3matrix[2][2]);
>>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_23_MASK,
>>>> +                        ccm->ccm_4x3matrix[2][3] << CCM_MTX_23_SHIFT);
>>>> +}
>>>> +
>>>> +/* Configure color space conversion matrix parameters */
>>>> +static void c3_isp_params_cfg_csc(struct c3_isp_device *isp,
>>>> +                               struct c3_isp_param_block_header *block)
>>>> +{
>>>> +     struct csc_cfg *csc = (struct csc_cfg *)block;
>>>> +
>>>> +     if (!block->enabled) {
>>>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, false);
>>>> +             return;
>>>> +     }
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, TOP_BED_CM0_EN);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST0_MASK,
>>>> +                        csc->cm0_offset_inp[0]);
>>>> +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST1_MASK,
>>>> +                        csc->cm0_offset_inp[1] << CM0_INP_OFST1_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST2, CM0_INP_OFST2_MASK,
>>>> +                        csc->cm0_offset_inp[2]);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_00_MASK,
>>>> +                        csc->cm0_3x3matrix[0][0]);
>>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_01_MASK,
>>>> +                        csc->cm0_3x3matrix[0][1] << CM0_MTX_01_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_02_MASK,
>>>> +                        csc->cm0_3x3matrix[0][2]);
>>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_10_MASK,
>>>> +                        csc->cm0_3x3matrix[1][0] << CM0_MTX_10_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_11_MASK,
>>>> +                        csc->cm0_3x3matrix[1][1]);
>>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_12_MASK,
>>>> +                        csc->cm0_3x3matrix[1][2] << CM0_MTX_12_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_20_MASK,
>>>> +                        csc->cm0_3x3matrix[2][0]);
>>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_21_MASK,
>>>> +                        csc->cm0_3x3matrix[2][1] << CM0_MTX_21_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_MTX_22_MASK,
>>>> +                        csc->cm0_3x3matrix[2][2]);
>>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_OFST_OUP0_MASK,
>>>> +                        csc->cm0_offset_oup[0] << CM0_OFST_OUP0_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP1_MASK,
>>>> +                        csc->cm0_offset_oup[1]);
>>>> +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP2_MASK,
>>>> +                        csc->cm0_offset_oup[2] << CM0_OFST_OUP2_SHIFT);
>>>> +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_MTX_RS_MASK,
>>>> +                        csc->cm0_3x3mtrx_rs << CM0_MTX_RS_SHIFT);
>>>> +}
>>>> +
>>>> +/* Set blc offset of each color channel */
>>>> +static void c3_isp_params_cfg_blc(struct c3_isp_device *isp,
>>>> +                               struct c3_isp_param_block_header *block)
>>>> +{
>>>> +     struct blc_cfg *blc = (struct blc_cfg *)block;
>>>> +
>>>> +     if (!block->enabled) {
>>>> +             c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN, false);
>>>> +             return;
>>>> +     }
>>>> +
>>>> +     c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN,
>>>> +                        TOP_BEO_CTRL_BLC_EN);
>>>> +
>>>> +     c3_isp_write(isp, ISP_FED_BL_OFST_GR, FED_BL_GR_OFST(blc->fe_bl_ofst[0]));
>>>> +     c3_isp_write(isp, ISP_FED_BL_OFST_R, FED_BL_R_OFST(blc->fe_bl_ofst[1]));
>>>> +     c3_isp_write(isp, ISP_FED_BL_OFST_B, FED_BL_B_OFST(blc->fe_bl_ofst[2]));
>>>> +     c3_isp_write(isp, ISP_FED_BL_OFST_GB, FED_BL_GB_OFST(blc->fe_bl_ofst[3]));
>>>> +     c3_isp_write(isp, ISP_FED_BL_OFST_IR, FED_BL_IR_OFST(blc->fe_bl_ofst[4]));
>>>> +
>>>> +     c3_isp_write(isp, ISP_LSWB_BLC_OFST0,
>>>> +                  LSWB_BLC_OFST0(blc->blc_ofst[0]) | LSWB_BLC_OFST1(blc->blc_ofst[1]));
>>>> +     c3_isp_write(isp, ISP_LSWB_BLC_OFST1,
>>>> +                  LSWB_BLC_OFST2(blc->blc_ofst[2]) | LSWB_BLC_OFST3(blc->blc_ofst[3]));
>>>> +     c3_isp_write(isp, ISP_LSWB_BLC_OFST2, LSWB_BLC_OFST4(blc->blc_ofst[4]));
>>>> +}
>>>> +
>>>> +static const struct c3_isp_block_handler c3_isp_block_handlers[] = {
>>>> +     [C3_ISP_PARAM_BLOCK_WB_CHANGE] = {
>>>> +             .size = sizeof(struct wb_change_cfg),
>>>> +             .handler = c3_isp_params_cfg_wb_change,
>>>> +     },
>>>> +     [C3_ISP_PARAM_BLOCK_WB_LUMA] = {
>>>> +             .size = sizeof(struct wb_luma_cfg),
>>>> +             .handler = c3_isp_params_cfg_wb_luma,
>>>> +     },
>>>> +     [C3_ISP_PARAM_BLOCK_WB_TRIANGLE] = {
>>>> +             .size = sizeof(struct wb_triangle_cfg),
>>>> +             .handler = c3_isp_params_cfg_wb_triangle,
>>>> +     },
>>>> +     [C3_ISP_PARAM_BLOCK_AWB_STATS] = {
>>>> +             .size = sizeof(struct awb_stats_cfg),
>>>> +             .handler = c3_isp_params_cfg_awb_stats,
>>>> +     },
>>>> +     [C3_ISP_PARAM_BLOCK_AE_STATS] = {
>>>> +             .size = sizeof(struct ae_stats_cfg),
>>>> +             .handler = c3_isp_params_cfg_ae_stats,
>>>> +     },
>>>> +     [C3_ISP_PARAM_BLOCK_AF_STATS] = {
>>>> +             .size = sizeof(struct af_stats_cfg),
>>>> +             .handler = c3_isp_params_cfg_af_stats,
>>>> +     },
>>>> +     [C3_ISP_PARAM_BLOCK_PST_GAMMA] = {
>>>> +             .size = sizeof(struct pst_gamma_cfg),
>>>> +             .handler = c3_isp_params_cfg_pst_gamma,
>>>> +     },
>>>> +     [C3_ISP_PARAM_BLOCK_DMSC] = {
>>>> +             .size = sizeof(struct dmsc_cfg),
>>>> +             .handler = c3_isp_params_cfg_dmsc,
>>>> +     },
>>>> +     [C3_ISP_PARAM_BLOCK_CCM] = {
>>>> +             .size = sizeof(struct ccm_cfg),
>>>> +             .handler = c3_isp_params_cfg_ccm,
>>>> +     },
>>>> +     [C3_ISP_PARAM_BLOCK_CSC] = {
>>>> +             .size = sizeof(struct csc_cfg),
>>>> +             .handler = c3_isp_params_cfg_csc,
>>>> +     },
>>>> +     [C3_ISP_PARAM_BLOCK_BLC] = {
>>>> +             .size = sizeof(struct blc_cfg),
>>>> +             .handler = c3_isp_params_cfg_blc,
>>>> +     },
>>>> +};
>>>> +
>>>> +static enum vb2_buffer_state
>>>> +c3_isp_params_cfg_blocks(struct c3_isp_params *params)
>>>> +{
>>>> +     struct c3_isp_params_buffer *config = params->buff->vaddr;
>>>> +     enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
>>>> +     size_t block_offset = 0;
>>>> +     size_t max_offset = 0;
>>>> +
>>>> +     if (config->total_size > C3_ISP_PARAMS_MAX_SIZE) {
>>>> +             dev_dbg(params->isp->dev, "Invalid parameters buffer size %lu\n",
>>>> +                     config->total_size);
>>>> +             state = VB2_BUF_STATE_ERROR;
>>>> +             goto err_return_state;
>>>> +     }
>>> I suggest to move validation of the parameters buffer to .buf_prepare
>>> time.
>>>
>>> This function is called in irq context, and it's better to do all
>>> validation check at buffer queuing time instead.
>>>
>>> You can have a look at the rkisp1-params.c module, where
>>> rkisp1_params_prepare_ext_params does the validation in the
>>> .buf_prepare call path
>>>
>> Will refer the rkisp1-params.c module.
>>
>>>> +
>>>> +     /* Ensure config->data has a full struct c3_isp_param_block_header */
>>>> +     max_offset = config->total_size - sizeof(struct c3_isp_param_block_header);
>>>> +
>>>> +     while (block_offset <= max_offset) {
>>>> +             const struct c3_isp_block_handler *block_handler;
>>>> +             struct c3_isp_param_block_header *block;
>>>> +
>>>> +             block = (struct c3_isp_param_block_header *)
>>>> +                      &config->data[block_offset];
>>>> +
>>>> +             if (block->type >= C3_ISP_PARAM_BLOCK_SENTINEL) {
>>>> +                     dev_dbg(params->isp->dev, "Invalid parameters block type\n");
>>>> +                     state = VB2_BUF_STATE_ERROR;
>>>> +                     goto err_return_state;
>>>> +             }
>>>> +
>>>> +             block_handler = &c3_isp_block_handlers[block->type];
>>>> +             if (block->size != block_handler->size) {
>>>> +                     dev_dbg(params->isp->dev, "Invalid parameters block size\n");
>>>> +                     state = VB2_BUF_STATE_ERROR;
>>>> +                     goto err_return_state;
>>>> +             }
>>>> +
>>>> +             block_handler->handler(params->isp, block);
>>>> +
>>>> +             block_offset += block->size;
>>>> +     }
>>>> +
>>>> +err_return_state:
>>>> +     return state;
>>>> +}
>>>> +
>>>> +/* Initialize ISP pipeline */
>>>> +static int c3_isp_params_start(struct c3_isp_params *params)
>>>> +{
>>>> +     enum vb2_buffer_state state;
>>>> +     unsigned long flags;
>>>> +
>>>> +     /* Reset these controllers */
>>>> +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL0, TOP_FEO_CTRL0_ALL_DIS);
>>>> +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_0, TOP_FEO_CTRL1_0_ALL_DIS);
>>>> +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_1, TOP_FEO_CTRL1_1_ALL_DIS);
>>>> +     c3_isp_write(params->isp, ISP_TOP_FED_CTRL, TOP_FED_CTRL_ALL_DIS);
>>>> +     c3_isp_write(params->isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_ALL_DIS);
>>>> +     c3_isp_write(params->isp, ISP_TOP_BED_CTRL, TOP_BED_CTRL_ALL_DIS);
>>>> +
>>>> +     spin_lock_irqsave(&params->buff_lock, flags);
>>>> +
>>>> +     /* Only use the first buffer to initialize ISP */
>>>> +     params->buff = list_first_entry_or_null(&params->pending,
>>>> +                                             struct c3_isp_vb2_buffer, list);
>>>> +     if (!params->buff) {
>>>> +             spin_unlock_irqrestore(&params->buff_lock, flags);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     state = c3_isp_params_cfg_blocks(params);
>>>> +
>>>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +/* V4L2 video operations */
>>>> +
>>>> +static void c3_isp_params_return_buffers(struct c3_isp_params *params,
>>>> +                                      enum vb2_buffer_state state)
>>>> +{
>>>> +     unsigned long flags;
>>>> +     struct c3_isp_vb2_buffer *buff;
>>>> +
>>>> +     spin_lock_irqsave(&params->buff_lock, flags);
>>>> +
>>>> +     while (!list_empty(&params->pending)) {
>>>> +             buff = list_first_entry(&params->pending,
>>>> +                                     struct c3_isp_vb2_buffer, list);
>>>> +             list_del(&buff->list);
>>>> +             vb2_buffer_done(&buff->vb.vb2_buf, state);
>>>> +     }
>>>> +
>>>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>>>> +}
>>>> +
>>>> +static int c3_isp_params_querycap(struct file *file, void *fh,
>>>> +                               struct v4l2_capability *cap)
>>>> +{
>>>> +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>>>> +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_params_enum_fmt(struct file *file, void *fh,
>>>> +                               struct v4l2_fmtdesc *f)
>>>> +{
>>>> +     if (f->index)
>>>> +             return -EINVAL;
>>>> +
>>>> +     f->pixelformat = V4L2_META_FMT_C3ISP_PARAMS;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_params_g_fmt(struct file *file, void *fh,
>>>> +                            struct v4l2_format *f)
>>>> +{
>>>> +     struct c3_isp_params *params = video_drvdata(file);
>>>> +
>>>> +     f->fmt.meta = params->vfmt.fmt.meta;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static const struct v4l2_ioctl_ops isp_params_v4l2_ioctl_ops = {
>>>> +     .vidioc_querycap                = c3_isp_params_querycap,
>>>> +     .vidioc_enum_fmt_meta_out       = c3_isp_params_enum_fmt,
>>>> +     .vidioc_g_fmt_meta_out          = c3_isp_params_g_fmt,
>>>> +     .vidioc_s_fmt_meta_out          = c3_isp_params_g_fmt,
>>>> +     .vidioc_try_fmt_meta_out        = c3_isp_params_g_fmt,
>>>> +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>>>> +     .vidioc_querybuf                = vb2_ioctl_querybuf,
>>>> +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
>>>> +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
>>>> +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>>>> +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>>>> +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>>>> +     .vidioc_streamon                = vb2_ioctl_streamon,
>>>> +     .vidioc_streamoff               = vb2_ioctl_streamoff,
>>>> +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>>>> +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>>>> +};
>>>> +
>>>> +static const struct v4l2_file_operations isp_params_v4l2_fops = {
>>>> +     .open = v4l2_fh_open,
>>>> +     .release = vb2_fop_release,
>>>> +     .poll = vb2_fop_poll,
>>>> +     .unlocked_ioctl = video_ioctl2,
>>>> +     .mmap = vb2_fop_mmap,
>>>> +};
>>>> +
>>>> +static int c3_isp_params_vb2_queue_setup(struct vb2_queue *q,
>>>> +                                      unsigned int *num_buffers,
>>>> +                                      unsigned int *num_planes,
>>>> +                                      unsigned int sizes[],
>>>> +                                      struct device *alloc_devs[])
>>>> +{
>>>> +     if (*num_planes) {
>>>> +             if (*num_planes != 1)
>>>> +                     return -EINVAL;
>>>> +
>>>> +             if (sizes[0] < sizeof(struct c3_isp_params_buffer))
>>>> +                     return -EINVAL;
>>>> +     } else {
>>>> +             *num_planes = 1;
>>>> +             sizes[0] = sizeof(struct c3_isp_params_buffer);
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static void c3_isp_params_vb2_buf_queue(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>>> +     struct c3_isp_vb2_buffer *buf =
>>>> +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>>> +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>>>> +     unsigned long flags;
>>>> +
>>>> +     spin_lock_irqsave(&params->buff_lock, flags);
>>>> +
>>>> +     list_add_tail(&buf->list, &params->pending);
>>>> +
>>>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>>>> +}
>>>> +
>>>> +static int c3_isp_params_vb2_buf_prepare(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>>>> +     unsigned int size = params->vfmt.fmt.meta.buffersize;
>>>> +
>>>> +     if (vb2_plane_size(vb, 0) < size) {
>>> How does this work ?
>>>
>>> 'size' is
>>>           params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
>>>
>>> if I got this right
>>>
>>> Now, as you're using an extensible parameters implementation,
>>> userspace is allowed to submit buffers of a smaller size, with only
>>> the "interesting" blocks there.
>>>
>>> This check instead makes sure that userspace always fill the
>>> paramteres buffer with all blocks, am I wrong ?
>>>
>>> This defeates the purpose of extensible formats, where instead
>>> userspace is allowed to only fill the buffers with a subset of the
>>> configuration blocks.
>>>
>>> Have I missed something ?
>>
>> Didn't notice this issue and will check this issue.
>>
>>>> +             dev_err(params->isp->dev,
>>>> +                     "User buffer too small (%ld < %u)\n",
>>>> +                     vb2_plane_size(vb, 0), size);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     vb2_set_plane_payload(vb, 0, size);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_params_vb2_buf_init(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>>> +     struct c3_isp_vb2_buffer *buf =
>>>> +             container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>>> +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>>>> +
>>>> +     buf->vaddr = vb2_plane_vaddr(vb, 0);
>>>> +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>>> This is not used
>>>
>> OK, will remove this line.
>>
>>>> +
>>>> +     memset(buf->vaddr, 0, params->vfmt.fmt.meta.buffersize);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_params_vb2_start_streaming(struct vb2_queue *q,
>>>> +                                          unsigned int count)
>>>> +{
>>>> +     struct c3_isp_params *params = vb2_get_drv_priv(q);
>>>> +     int ret;
>>>> +
>>>> +     guard(mutex)(&params->isp->lock);
>>>> +
>>>> +     ret = pm_runtime_resume_and_get(params->isp->dev);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = video_device_pipeline_start(&params->vdev, &params->isp->pipe);
>>>> +     if (ret) {
>>>> +             dev_err(params->isp->dev,
>>>> +                     "Failed to start params pipeline: %d\n", ret);
>>>> +             goto err_pm_put;
>>>> +     }
>>>> +
>>>> +     if (c3_isp_pipeline_ready(params->isp)) {
>>> I understand this counts how many video devices have been started...
>>>
>> Yes.
>>>> +             ret = v4l2_subdev_enable_streams(&params->isp->core.sd,
>>>> +                                              C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>>> +                                              BIT(0));
>>> ... but does it need to be called by this module ? it's not like there is a
>>> parameters stream to be enabled in the ISP (see also the suggestion to
>>> drop routing support from there).
>>>
>>>
>> Will remove this function  from here.
>>
>>>> +             if (ret)
>>>> +                     goto err_pipeline_stop;
>>>> +     }
>>>> +
>>>> +     c3_isp_params_start(params);
>>> Does this perform initialization and apply the first parameters from a
>>> queued buffer ?
>>>
>>> I'm wondering if it wouldn't be better to:
>>>
>>> - Implement a start_streaming operation on capture nodes only
>>> - Call the ISP's enable_streams
>>> - The ISP driver counts how many enabled links to resizers are there
>>> - If the number of enable_streams calls matches the number of enabled
>>>     links:
>>>     - call c3_isp_params_start() and any function that does the stats or
>>>       params setup
>>>     - actually start the ISP
>>>     - propagate the enable_streams call to the downstream subdevs (Adap,
>>>       then csi2-rx then sensor)
>>>
>> Will refer your suggestion.
>>
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_pipeline_stop:
>>>> +     video_device_pipeline_stop(&params->vdev);
>>>> +err_pm_put:
>>>> +     pm_runtime_put(params->isp->dev);
>>>> +     c3_isp_params_return_buffers(params, VB2_BUF_STATE_QUEUED);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_isp_params_vb2_stop_streaming(struct vb2_queue *q)
>>>> +{
>>>> +     struct c3_isp_params *params = vb2_get_drv_priv(q);
>>>> +
>>>> +     guard(mutex)(&params->isp->lock);
>>>> +
>>>> +     c3_isp_params_return_buffers(params, VB2_BUF_STATE_ERROR);
>>> Even if you don't implement start_streaming as I've suggested, you
>>> will need this one to return buffers to userspace.
>>>
>> OK.
>>
>>>> +
>>>> +     if (params->isp->pipe.start_count == 1)
>>>> +             v4l2_subdev_disable_streams(&params->isp->core.sd,
>>>> +                                         C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>>> +                                         BIT(0));
>>>> +
>>>> +     video_device_pipeline_stop(&params->vdev);
>>>> +     pm_runtime_put(params->isp->dev);
>>>> +}
>>>> +
>>>> +static const struct vb2_ops isp_params_vb2_ops = {
>>>> +     .queue_setup = c3_isp_params_vb2_queue_setup,
>>>> +     .buf_queue = c3_isp_params_vb2_buf_queue,
>>>> +     .buf_prepare = c3_isp_params_vb2_buf_prepare,
>>>> +     .buf_init = c3_isp_params_vb2_buf_init,
>>>> +     .wait_prepare = vb2_ops_wait_prepare,
>>>> +     .wait_finish = vb2_ops_wait_finish,
>>>> +     .start_streaming = c3_isp_params_vb2_start_streaming,
>>>> +     .stop_streaming = c3_isp_params_vb2_stop_streaming,
>>>> +};
>>>> +
>>>> +int c3_isp_params_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_params *params = &isp->params;
>>>> +     struct video_device *vdev = &params->vdev;
>>>> +     struct vb2_queue *vb2_q = &params->vb2_q;
>>>> +     int ret;
>>>> +
>>>> +     memset(params, 0, sizeof(*params));
>>>> +     params->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_PARAMS;
>>>> +     params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
>>>> +     params->isp = isp;
>>>> +     INIT_LIST_HEAD(&params->pending);
>>>> +     spin_lock_init(&params->buff_lock);
>>>> +     mutex_init(&params->lock);
>>>> +
>>>> +     snprintf(vdev->name, sizeof(vdev->name), "isp-params");
>>> Here and in all other names, I would prefix them with "c3-"
>>>
>>>> +     vdev->fops = &isp_params_v4l2_fops;
>>>> +     vdev->ioctl_ops = &isp_params_v4l2_ioctl_ops;
>>>> +     vdev->v4l2_dev = &isp->v4l2_dev;
>>>> +     vdev->lock = &params->lock;
>>>> +     vdev->minor = -1;
>>>> +     vdev->queue = vb2_q;
>>>> +     vdev->release = video_device_release_empty;
>>>> +     vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING;
>>>> +     vdev->vfl_dir = VFL_DIR_TX;
>>>> +     video_set_drvdata(vdev, params);
>>>> +
>>>> +     vb2_q->drv_priv = params;
>>>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>>> Do you need to use DMA ?
>>>
>>> This implementation seems to rather receive a buffer of parameters,
>>> inspect its content and write registers according to values there.
>>>
>>> I don't see direct DMA transfers to a memory mapped register area, but
>>> instead single writes to registers.
>>>
>>> Should you use vb2_vmalloc_memops instead ?
>>> Make sure to change the headers inclusions and Kconfig dependencies
>>> accordingly.
>>
>> Will use vb2_vmalloc_memops instead.
>>
>>>> +     vb2_q->ops = &isp_params_vb2_ops;
>>>> +     vb2_q->type = V4L2_BUF_TYPE_META_OUTPUT;
>>>> +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>>>> +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>>>> +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>>>> +     vb2_q->dev = isp->dev;
>>>> +     vb2_q->lock = &params->lock;
>>>> +     vb2_q->min_queued_buffers = 1;
>>>> +
>>>> +     ret = vb2_queue_init(vb2_q);
>>>> +     if (ret)
>>>> +             goto err_detroy;
>>>> +
>>>> +     params->pad.flags = MEDIA_PAD_FL_SOURCE;
>>>> +     ret = media_entity_pads_init(&vdev->entity, 1, &params->pad);
>>>> +     if (ret)
>>>> +             goto err_queue_release;
>>>> +
>>>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>>>> +     if (ret < 0) {
>>>> +             dev_err(isp->dev,
>>>> +                     "Failed to register %s: %d\n", vdev->name, ret);
>>>> +             goto err_entity_cleanup;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_entity_cleanup:
>>>> +     media_entity_cleanup(&vdev->entity);
>>>> +err_queue_release:
>>>> +     vb2_queue_release(vb2_q);
>>>> +err_detroy:
>>>> +     mutex_destroy(&params->lock);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +void c3_isp_params_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_params *params = &isp->params;
>>>> +
>>>> +     vb2_queue_release(&params->vb2_q);
>>>> +     media_entity_cleanup(&params->vdev.entity);
>>>> +     video_unregister_device(&params->vdev);
>>>> +     mutex_destroy(&params->lock);
>>>> +}
>>>> +
>>>> +int c3_isp_params_done(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_params *params = &isp->params;
>>>> +     enum vb2_buffer_state state;
>>>> +     unsigned long flags;
>>>> +
>>>> +     spin_lock_irqsave(&params->buff_lock, flags);
>>>> +
>>>> +     params->buff = list_first_entry_or_null(&params->pending,
>>>> +                                             struct c3_isp_vb2_buffer, list);
>>>> +     if (!params->buff) {
>>>> +             spin_unlock_irqrestore(&params->buff_lock, flags);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     list_del(&params->buff->list);
>>>> +
>>>> +     state = c3_isp_params_cfg_blocks(params);
>>>> +
>>>> +     params->buff->vb.sequence = params->isp->frm_sequence;
>>>> +     params->buff->vb.vb2_buf.timestamp = ktime_get();
>>>> +     params->buff->vb.field = V4L2_FIELD_NONE;
>>>> +     vb2_buffer_done(&params->buff->vb.vb2_buf, state);
>>>> +
>>>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
>>>> new file mode 100644
>>>> index 000000000000..de1938f7c354
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
>>>> @@ -0,0 +1,683 @@
>>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#ifndef __C3_ISP_REGS_H__
>>>> +#define __C3_ISP_REGS_H__
>>>> +
>>>> +#define ISP_TOP_INPUT_SIZE                       0x0000
>>>> +#define TOP_INPUT_HSIZE(x)                       (((x) & 0xffff) << 16)
>>>> +#define TOP_INPUT_VSIZE(x)                       ((x) & 0xffff)
>>>> +
>>>> +#define ISP_TOP_FRM_SIZE                         0x0004
>>>> +#define TOP_FRM_CORE_IHSIZE(x)                   (((x) & 0xffff) << 16)
>>>> +#define TOP_FRM_CORE_IVSIZE(x)                   ((x) & 0xffff)
>>>> +
>>>> +#define ISP_TOP_HOLD_SIZE                        0x0008
>>>> +#define TOP_HOLD_HSIZE_MASK                      GENMASK(31, 16)
>>>> +#define TOP_HOLD_HSIZE_SHIFT                     16
>>>> +
>>>> +#define ISP_TOP_PATH_EN                          0x0010
>>>> +#define TOP_DISP_EN(x)                           BIT((x))
>>>> +#define TOP_WRMIF_EN(x)                          BIT((x) + 8)
>>>> +
>>>> +#define ISP_TOP_PATH_SEL                         0x0014
>>>> +#define TOP_DATA_PATH_MASK                       GENMASK(18, 16)
>>>> +#define TOP_PATH_MIPI_TO_CORE                    BIT(16)
>>>> +
>>>> +#define ISP_TOP_IRQ_EN                           0x0080
>>>> +#define TOP_IRQ_FRAME_DONE                       BIT(0)
>>>> +#define TOP_IRQ_STATS_ERR                        BIT(5)
>>>> +
>>>> +#define ISP_TOP_IRQ_CLR                          0x0084
>>>> +#define ISP_TOP_IRQ_LINE_THRD                    0x0088
>>>> +#define TOP_IRQ_DIN_HSYNC                        BIT(16)
>>>> +
>>>> +#define ISP_TOP_RO_IRQ_STAT                      0x01c4
>>>> +#define ISP_TOP_MODE_CTRL                        0x0400
>>>> +#define ISP_TOP_FEO_CTRL0                        0x040c
>>>> +#define TOP_FEO_CTRL0_ALL_DIS                    0
>>>> +#define TOP_FEO_INPUT_FMT_EN                     BIT(8)
>>>> +
>>>> +#define ISP_TOP_FEO_CTRL1_0                      0x0410
>>>> +#define TOP_FEO_CTRL1_0_ALL_DIS                  0
>>>> +
>>>> +#define ISP_TOP_FEO_CTRL1_1                      0x0414
>>>> +#define TOP_FEO_CTRL1_1_ALL_DIS                  0
>>>> +
>>>> +#define ISP_TOP_FED_CTRL                         0x0418
>>>> +#define TOP_FED_CTRL_ALL_DIS                     0
>>>> +
>>>> +#define ISP_TOP_BEO_CTRL                         0x041c
>>>> +#define TOP_BEO_CTRL_ALL_DIS                     0
>>>> +#define TOP_BEO_CTRL_GTM_EN                      BIT(2)
>>>> +#define TOP_BEO_CTRL_WB_EN                       BIT(6)
>>>> +#define TOP_BEO_CTRL_BLC_EN                      BIT(7)
>>>> +#define TOP_BEO_CTRL_IDGAIN_EN                   BIT(8)
>>>> +#define TOP_BEO_CTRL_EOTF_EN                     BIT(9)
>>>> +
>>>> +#define ISP_TOP_BED_CTRL                         0x0420
>>>> +#define TOP_BED_CTRL_ALL_DIS                     0
>>>> +#define TOP_BED_CM0_EN                           BIT(14)
>>>> +#define TOP_BED_GAMMA_EN                         BIT(16)
>>>> +#define TOP_BED_CCM_EN                           BIT(18)
>>>> +#define TOP_BED_DMSC_EN                          BIT(19)
>>>> +
>>>> +#define ISP_TOP_3A_STAT_CRTL                     0x0424
>>>> +#define TOP_3A_AE_STAT_EN                        BIT(0)
>>>> +#define TOP_3A_AWB_STAT_EN                       BIT(1)
>>>> +#define TOP_3A_AF_STAT_EN                        BIT(2)
>>>> +#define TOP_3A_AWB_STAT_SWITCH_MASK              GENMASK(6, 4)
>>>> +#define TOP_3A_AWB_STAT_SWITCH_SHIFT             4
>>>> +#define TOP_3A_AWB_STAT_SWITCH_BEFORE_WB         2
>>>> +
>>>> +#define TOP_3A_AE_STAT_SWITCH_MASK               GENMASK(9, 8)
>>>> +#define TOP_3A_AE_STAT_SWITCH_SHIFT              8
>>>> +#define TOP_3A_AE_STAT_SWITCH_FROM_MLS           1
>>>> +#define TOP_3A_AF_STAT_SWITCH_MASK               GENMASK(13, 12)
>>>> +#define TOP_3A_AF_STAT_SWITCH_SHIFT              12
>>>> +#define TOP_3A_AF_STAT_SWITCH_FROM_SNR            0
>>>> +
>>>> +#define ISP_FED_BL_OFST_GR                       0x2018
>>>> +#define FED_BL_GR_OFST(x)                        ((x) & 0x1fffff)
>>>> +
>>>> +#define ISP_FED_BL_OFST_R                        0x201c
>>>> +#define FED_BL_R_OFST(x)                         ((x) & 0x1fffff)
>>>> +
>>>> +#define ISP_FED_BL_OFST_B                        0x2020
>>>> +#define FED_BL_B_OFST(x)                         ((x) & 0x1fffff)
>>>> +
>>>> +#define ISP_FED_BL_OFST_GB                       0x2024
>>>> +#define FED_BL_GB_OFST(x)                        ((x) & 0x1fffff)
>>>> +
>>>> +#define ISP_FED_BL_OFST_IR                       0x2028
>>>> +#define FED_BL_IR_OFST(x)                        ((x) & 0x1fffff)
>>>> +
>>>> +#define ISP_LSWB_BLC_OFST0                       0x4028
>>>> +#define LSWB_BLC_OFST1_MASK                      GENMASK(15, 0)
>>>> +#define LSWB_BLC_OFST1(x)                        ((x) & 0xffff)
>>>> +#define LSWB_BLC_OFST1_EXT(x)                    ((x) & 0xffff)
>>>> +#define LSWB_BLC_OFST0_MASK                      GENMASK(31, 16)
>>>> +#define LSWB_BLC_OFST0(x)                        (((x) & 0xffff) << 16)
>>>> +#define LSWB_BLC_OFST0_EXT(x)                    (((x) >> 16) & 0xffff)
>>>> +
>>>> +#define ISP_LSWB_BLC_OFST1                       0x402c
>>>> +#define LSWB_BLC_OFST3_MASK                      GENMASK(15, 0)
>>>> +#define LSWB_BLC_OFST3(x)                        ((x) & 0xffff)
>>>> +#define LSWB_BLC_OFST3_EXT(x)                    ((x) & 0xffff)
>>>> +#define LSWB_BLC_OFST2_MASK                      GENMASK(31, 16)
>>>> +#define LSWB_BLC_OFST2(x)                        (((x) & 0xffff) << 16)
>>>> +#define LSWB_BLC_OFST2_EXT(x)                    (((x) >> 16) & 0xffff)
>>>> +
>>>> +#define ISP_LSWB_BLC_OFST2                       0x4030
>>>> +#define LSWB_BLC_OFST4_MASK                      GENMASK(15, 0)
>>>> +#define LSWB_BLC_OFST4(x)                        ((x) & 0xffff)
>>>> +#define LSWB_BLC_OFST4_EXT(x)                    ((x) & 0xffff)
>>>> +
>>>> +#define ISP_LSWB_BLC_PHSOFST                     0x4034
>>>> +#define LSWB_BLC_YPHS_OFST_MASK                  GENMASK(1, 0)
>>>> +#define LSWB_BLC_XPHS_OFST_MASK                  GENMASK(3, 2)
>>>> +#define LSWB_BLC_XPHS_OFST_SHIFT                 2
>>>> +
>>>> +#define ISP_LSWB_WB_GAIN0                        0x4038
>>>> +#define LSWB_WB_GAIN1_MASK                       GENMASK(11, 0)
>>>> +#define LSWB_WB_GAIN0_MASK                       GENMASK(27, 16)
>>>> +#define LSWB_WB_GAIN0_SHIFT                      16
>>>> +
>>>> +#define ISP_LSWB_WB_GAIN1                        0x403c
>>>> +#define LSWB_WB_GAIN3_MASK                       GENMASK(11, 0)
>>>> +#define LSWB_WB_GAIN2_MASK                       GENMASK(27, 16)
>>>> +#define LSWB_WB_GAIN2_SHIFT                      16
>>>> +
>>>> +#define ISP_LSWB_WB_GAIN2                        0x4040
>>>> +#define LSWB_WB_GAIN4_MASK                       GENMASK(11, 0)
>>>> +
>>>> +#define ISP_LSWB_WB_LIMIT0                       0x4044
>>>> +#define LSWB_WB_LIMIT1(x)                        ((x) & 0xffff)
>>>> +#define LSWB_WB_LIMIT0(x)                        (((x) & 0xffff) << 16)
>>>> +
>>>> +#define ISP_LSWB_WB_LIMIT1                       0x4048
>>>> +#define LSWB_WB_LIMIT3(x)                        ((x) & 0xffff)
>>>> +#define LSWB_WB_LIMIT2(x)                        (((x) & 0xffff) << 16)
>>>> +
>>>> +#define ISP_LSWB_WB_LIMIT2                       0x404c
>>>> +#define LSWB_WB_LIMIT4_MASK                      GENMASK(15, 0)
>>>> +
>>>> +#define ISP_LSWB_WB_PHSOFST                      0x4050
>>>> +#define LSWB_WB_YPHS_OFST_MASK                   GENMASK(1, 0)
>>>> +#define LSWB_WB_XPHS_OFST_MASK                   GENMASK(3, 2)
>>>> +#define LSWB_WB_XPHS_OFST_SHIFT                  2
>>>> +
>>>> +#define ISP_LSWB_LNS_PHSOFST                     0x4054
>>>> +#define LSWB_LNS_YPHS_OFST_MASK                  GENMASK(1, 0)
>>>> +#define LSWB_LNS_XPHS_OFST_MASK                  GENMASK(3, 2)
>>>> +#define LSWB_LNS_XPHS_OFST_SHIFT                 2
>>>> +
>>>> +#define ISP_DMS_COMMON_PARAM0                    0x5000
>>>> +#define DMS_COMMON_YPHS_OFST_MASK                GENMASK(1, 0)
>>>> +#define DMS_COMMON_XPHS_OFST_MASK                GENMASK(3, 2)
>>>> +#define DMS_COMMON_XPHS_OFST_SHIFT               2
>>>> +
>>>> +#define ISP_CM0_INP_OFST01                       0x6040
>>>> +#define CM0_INP_OFST0_MASK                       GENMASK(12, 0)
>>>> +#define CM0_INP_OFST1_MASK                       GENMASK(28, 16)
>>>> +#define CM0_INP_OFST1_SHIFT                      16
>>>> +
>>>> +#define ISP_CM0_INP_OFST2                        0x6044
>>>> +#define CM0_INP_OFST2_MASK                       GENMASK(12, 0)
>>>> +
>>>> +#define ISP_CM0_COEF00_01                        0x6048
>>>> +#define CM0_MTX_00_MASK                          GENMASK(12, 0)
>>>> +#define CM0_MTX_01_MASK                          GENMASK(28, 16)
>>>> +#define CM0_MTX_01_SHIFT                         16
>>>> +
>>>> +#define ISP_CM0_COEF02_10                        0x604c
>>>> +#define CM0_MTX_02_MASK                          GENMASK(12, 0)
>>>> +#define CM0_MTX_10_MASK                          GENMASK(28, 16)
>>>> +#define CM0_MTX_10_SHIFT                         16
>>>> +
>>>> +#define ISP_CM0_COEF11_12                        0x6050
>>>> +#define CM0_MTX_11_MASK                          GENMASK(12, 0)
>>>> +#define CM0_MTX_12_MASK                          GENMASK(28, 16)
>>>> +#define CM0_MTX_12_SHIFT                         16
>>>> +
>>>> +#define ISP_CM0_COEF20_21                        0x6054
>>>> +#define CM0_MTX_20_MASK                          GENMASK(12, 0)
>>>> +#define CM0_MTX_21_MASK                          GENMASK(28, 16)
>>>> +#define CM0_MTX_21_SHIFT                         16
>>>> +
>>>> +#define ISP_CM0_COEF22_OUP_OFST0                 0x6058
>>>> +#define CM0_MTX_22_MASK                          GENMASK(12, 0)
>>>> +#define CM0_OFST_OUP0_MASK                       GENMASK(28, 16)
>>>> +#define CM0_OFST_OUP0_SHIFT                      16
>>>> +
>>>> +#define ISP_CM0_OUP_OFST12_RS                    0x605c
>>>> +#define CM0_OFST_OUP1_MASK                       GENMASK(12, 0)
>>>> +#define CM0_OFST_OUP2_MASK                       GENMASK(28, 16)
>>>> +#define CM0_OFST_OUP2_SHIFT                      16
>>>> +#define CM0_MTX_RS_MASK                          GENMASK(31, 30)
>>>> +#define CM0_MTX_RS_SHIFT                         30
>>>> +
>>>> +#define ISP_CCM_MTX_00_01                        0x6098
>>>> +#define CCM_MTX_00_MASK                          GENMASK(12, 0)
>>>> +#define CCM_MTX_01_MASK                          GENMASK(28, 16)
>>>> +#define CCM_MTX_01_SHIFT                         16
>>>> +
>>>> +#define ISP_CCM_MTX_02_03                        0x609c
>>>> +#define CCM_MTX_02_MASK                          GENMASK(12, 0)
>>>> +#define CCM_MTX_03_MASK                          GENMASK(28, 16)
>>>> +#define CCM_MTX_03_SHIFT                         16
>>>> +
>>>> +#define ISP_CCM_MTX_10_11                        0x60A0
>>>> +#define CCM_MTX_10_MASK                          GENMASK(12, 0)
>>>> +#define CCM_MTX_11_MASK                          GENMASK(28, 16)
>>>> +#define CCM_MTX_11_SHIFT                         16
>>>> +
>>>> +#define ISP_CCM_MTX_12_13                        0x60A4
>>>> +#define CCM_MTX_12_MASK                          GENMASK(12, 0)
>>>> +#define CCM_MTX_13_MASK                          GENMASK(28, 16)
>>>> +#define CCM_MTX_13_SHIFT                         16
>>>> +
>>>> +#define ISP_CCM_MTX_20_21                        0x60A8
>>>> +#define CCM_MTX_20_MASK                          GENMASK(12, 0)
>>>> +#define CCM_MTX_21_MASK                          GENMASK(28, 16)
>>>> +#define CCM_MTX_21_SHIFT                         16
>>>> +
>>>> +#define ISP_CCM_MTX_22_23_RS                     0x60Ac
>>>> +#define CCM_MTX_22_MASK                          GENMASK(12, 0)
>>>> +#define CCM_MTX_23_MASK                          GENMASK(28, 16)
>>>> +#define CCM_MTX_23_SHIFT                         16
>>>> +
>>>> +#define ISP_PST_GAMMA_MODE                       0x60C0
>>>> +#define PST_GAMMA_MODE                           BIT(0)
>>>> +#define PST_GAMMA_MODE_EXT(x)                    ((x) & 0x1)
>>>> +
>>>> +#define ISP_PST_GAMMA_LUT_ADDR                   0x60cc
>>>> +#define PST_GAMMA_LUT_ADDR(x)                    ((x) << 7)
>>>> +
>>>> +#define ISP_PST_GAMMA_LUT_DATA                   0x60d0
>>>> +#define PST_GAMMA_LUT_DATA0(x)                   ((x) & 0xffff)
>>>> +#define PST_GAMMA_LUT_DATA1(x)                   (((x) & 0xffff) << 16)
>>>> +
>>>> +#define DISP0_TOP_TOP_CTRL                       0x8000
>>>> +#define DISP_CRP2_EN                             BIT(5)
>>>> +
>>>> +#define DISP0_TOP_CRP2_START                     0x8004
>>>> +#define DISP_CRP2_VSTART_MASK                    GENMASK(15, 0)
>>>> +#define DISP_CRP2_VSTART(x)                      ((x) & 0xffff)
>>>> +#define DISP_CRP2_HSTART_MASK                    GENMASK(31, 16)
>>>> +#define DISP_CRP2_HSTART(x)                      (((x) & 0xffff) << 16)
>>>> +
>>>> +#define DISP0_TOP_CRP2_SIZE                      0x8008
>>>> +#define DISP_CRP2_VSIZE_MASK                     GENMASK(15, 0)
>>>> +#define DISP_CRP2_VSIZE(x)                       ((x) & 0xffff)
>>>> +#define DISP_CRP2_HSIZE_MASK                     GENMASK(31, 16)
>>>> +#define DISP_CRP2_HSIZE(x)                       (((x) & 0xffff) << 16)
>>>> +
>>>> +#define DISP0_TOP_OUT_SIZE                       0x800c
>>>> +#define DISP_OUT_VSIZE_MASK                      GENMASK(12, 0)
>>>> +#define DISP_OUT_HSIZE_MASK                      GENMASK(28, 16)
>>>> +#define DISP_OUT_HSIZE_SHIFT                     16
>>>> +
>>>> +#define ISP_DISP0_TOP_IN_SIZE                    0x804c
>>>> +#define DISP_TOP_IN_VSIZE(x)                     ((x) & 0x1fff)
>>>> +#define DISP_TOP_IN_HSIZE(x)                     (((x) & 0x1fff) << 16)
>>>> +
>>>> +#define DISP0_PPS_SCALE_EN                       0x8200
>>>> +#define PPS_VSC_TAP_NUM_MASK                     GENMASK(3, 0)
>>>> +#define PPS_HSC_TAP_NUM_MASK                     GENMASK(7, 4)
>>>> +#define PPS_HSC_TAP_NUM_SHIFT                    4
>>>> +#define PPS_HSC_TAP_NUM_INIT                     4
>>>> +#define PPS_PREVSC_FLT_NUM_MASK                  GENMASK(11, 8)
>>>> +#define PPS_PREVSC_FLT_NUM_SHIFT                 8
>>>> +#define PPS_PREHSC_FLT_NUM_MASK                  GENMASK(15, 12)
>>>> +#define PPS_PREHSC_FLT_NUM_SHIFT                 12
>>>> +#define PPS_PREHSC_FLT_NUM_INIT                  8
>>>> +#define PPS_PREVSC_RATE_MASK                     GENMASK(17, 16)
>>>> +#define PPS_PREVSC_RATE_SHIFT                    16
>>>> +#define PPS_PREHSC_RATE_MASK                     GENMASK(19, 18)
>>>> +#define PPS_PREHSC_RATE_SHIFT                    18
>>>> +#define PPS_HSC_EN_MASK                          BIT(20)
>>>> +#define PPS_HSC_EN_SHIFT                         20
>>>> +#define PPS_VSC_EN_MASK                          BIT(21)
>>>> +#define PPS_VSC_EN_SHIFT                         21
>>>> +#define PPS_PREVSC_EN_MASK                       BIT(22)
>>>> +#define PPS_PREVSC_EN_SHIFT                      22
>>>> +#define PPS_PREHSC_EN_MASK                       BIT(23)
>>>> +#define PPS_PREHSC_EN_SHIFT                      23
>>>> +#define PPS_HSC_NOR_RS_BITS_MASK                 GENMASK(27, 24)
>>>> +#define PPS_HSC_NOR_RS_BITS_SHIFT                24
>>>> +#define PPS_HSC_NOR_RS_BITS_INIT                 9
>>>> +#define PPS_VSC_NOR_RS_BITS_MASK                 GENMASK(31, 28)
>>>> +#define PPS_VSC_NOR_RS_BITS_SHIFT                28
>>>> +#define PPS_VSC_NOR_RS_BITS_INIT                 9
>>>> +
>>>> +#define DISP0_PPS_PRE_HSCALE_COEF_0              0x8204
>>>> +#define PPS_PREHSC_LUMA_COEF0_MASK               GENMASK(9, 0)
>>>> +#define PPS_PREHSC_LUMA_COEF0_INIT               128
>>>> +#define PPS_PREHSC_LUMA_COEF1_MASK               GENMASK(25, 16)
>>>> +#define PPS_PREHSC_LUMA_COEF1_SHIFT              16
>>>> +#define PPS_PREHSC_LUMA_COEF1_INIT               128
>>>> +
>>>> +#define DISP0_PPS_PRE_HSCALE_COEF_1              0x8208
>>>> +#define PPS_PREHSC_LUMA_COEF2_MASK               GENMASK(9, 0)
>>>> +#define PPS_PREHSC_LUMA_COEF2_INIT               32
>>>> +#define PPS_PREHSC_LUMA_COEF3_MASK               GENMASK(25, 16)
>>>> +#define PPS_PREHSC_LUMA_COEF3_SHIFT              16
>>>> +#define PPS_PREHSC_LUMA_COEF3_INIT               32
>>>> +
>>>> +#define DISP0_PPS_VSC_START_PHASE_STEP           0x8224
>>>> +#define PPS_VSC_FRACTION_PART_MASK               GENMASK(23, 0)
>>>> +#define PPS_VSC_INTEGER_PART_MASK                GENMASK(27, 24)
>>>> +#define PPS_VSC_INTEGER_PART_SHIFT               24
>>>> +
>>>> +#define DISP0_PPS_HSC_START_PHASE_STEP           0x8230
>>>> +#define PPS_HSC_FRACTION_PART_MASK               GENMASK(23, 0)
>>>> +#define PPS_HSC_INTEGER_PART_MASK                GENMASK(27, 24)
>>>> +#define PPS_HSC_INTEGER_PART_SHIFT               24
>>>> +
>>>> +#define DISP0_PPS_444TO422                       0x823c
>>>> +#define PPS_444TO422_EN_MASK                     BIT(0)
>>>> +
>>>> +#define ISP_SCALE0_COEF_IDX_LUMA                 0x8240
>>>> +#define SCALE_LUMA_COEF_S11_MODE                 BIT(9)
>>>> +#define SCALE_LUMA_CTYPE(x)                      (((x) & 0x7) << 10)
>>>> +
>>>> +#define ISP_SCALE0_COEF_LUMA                     0x8244
>>>> +#define SCALE_COEF_LUMA_DATA1(x)                 ((x) & 0x7ff)
>>>> +#define SCALE_COEF_LUMA_DATA0(x)                 (((x) & 0x7ff) << 16)
>>>> +
>>>> +#define ISP_SCALE0_COEF_IDX_CHRO                 0x8248
>>>> +#define SCALE_CHRO_COEF_S11_MODE                 BIT(9)
>>>> +#define SCALE_CHRO_CTYPE(x)                      (((x) & 0x7) << 10)
>>>> +
>>>> +#define ISP_SCALE0_COEF_CHRO                     0x824c
>>>> +#define SCALE_COEF_CHRO_DATA1(x)                 ((x) & 0x7ff)
>>>> +#define SCALE_COEF_CHRO_DATA0(x)                 (((x) & 0x7ff) << 16)
>>>> +
>>>> +#define ISP_AF_ROI0_WIN01                        0xa00c
>>>> +#define AF_ROI_XYWIN_00_INIT                     (50 & 0xffff)
>>>> +#define AF_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AF_ROI1_WIN01                        0xa010
>>>> +#define AF_ROI_XYWIN_10_INIT                     (100 & 0xffff)
>>>> +#define AF_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AF_ROI0_WIN23                        0xa014
>>>> +#define AF_ROI_XYWIN_02_INIT                     (80 & 0xffff)
>>>> +#define AF_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AF_ROI1_WIN23                        0xa018
>>>> +#define AF_ROI_XYWIN_12_INIT                     (80 & 0xffff)
>>>> +#define AF_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AF_CTRL                              0xa044
>>>> +#define AF_CTRL_YPHS_OFST_MASK                   GENMASK(15, 14)
>>>> +#define AF_CTRL_YPHS_OFST_SHIFT                  14
>>>> +#define AF_CTRL_XPHS_OFST_MASK                   GENMASK(17, 16)
>>>> +#define AF_CTRL_XPHS_OFST_SHIFT                  16
>>>> +
>>>> +#define ISP_AF_HV_SIZE                           0xa04c
>>>> +#define AF_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
>>>> +#define AF_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AF_HV_BLKNUM                         0xa050
>>>> +#define AF_HV_STAT_VBLK_NUM_MASK                 GENMASK(5, 0)
>>>> +#define AF_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x3f)
>>>> +#define AF_HV_STAT_HBLK_NUM_MASK                 GENMASK(21, 16)
>>>> +#define AF_HV_STAT_HBLK_NUM_SHIFT                16
>>>> +
>>>> +#define ISP_AF_EN_CTRL                           0xa054
>>>> +#define AF_STAT_SELECT                           BIT(21)
>>>> +#define AF_STAT_SELECT_SHIFT                     21
>>>> +
>>>> +#define ISP_RO_AF_GLB_STAT_PCK0                  0xa0e0
>>>> +#define ISP_RO_AF_GLB_STAT_PCK1                  0xa0e4
>>>> +#define ISP_RO_AF_GLB_STAT_PCK2                  0xa0e8
>>>> +#define ISP_RO_AF_GLB_STAT_PCK3                  0xa0eC
>>>> +#define ISP_AF_IDX_ADDR                          0xa1c0
>>>> +#define ISP_AF_IDX_DATA                          0xa1c4
>>>> +#define AF_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
>>>> +#define AF_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AE_ROI0_WIN01                        0xa40c
>>>> +#define AE_ROI_XYWIN_00_INIT                     (50 & 0xffff)
>>>> +#define AE_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AE_ROI1_WIN01                        0xa410
>>>> +#define AE_ROI_XYWIN_10_INIT                     (100 & 0xffff)
>>>> +#define AE_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AE_ROI0_WIN23                        0xa414
>>>> +#define AE_ROI_XYWIN_02_INIT                     (80 & 0xffff)
>>>> +#define AE_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AE_ROI1_WIN23                        0xa418
>>>> +#define AE_ROI_XYWIN_12_INIT                     (80 & 0xffff)
>>>> +#define AE_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_RO_AE_ROI_STAT_PCK0_0                0xa424
>>>> +#define ISP_RO_AE_ROI_STAT_PCK1_0                0xa428
>>>> +#define ISP_RO_AE_ROI_STAT_PCK0_1                0xa42c
>>>> +#define ISP_RO_AE_ROI_STAT_PCK1_1                0xa430
>>>> +#define ISP_AE_CTRL                              0xa448
>>>> +#define AE_CTRL_AE_STAT_LOCAL_MODE               GENMASK(3, 2)
>>>> +#define AE_CTRL_AE_STAT_LOCAL_MODE_EXT(x)        (((x) >> 2) & 0x3)
>>>> +#define AE_CTRL_INPUT_2LINE_TOGETHER             BIT(7)
>>>> +#define AE_CTRL_LUMA_MODE_MASK                   GENMASK(9, 8)
>>>> +#define AE_CTRL_LUMA_MODE_SHIFT                  8
>>>> +#define AE_CTRL_LUMA_MODE_FILTER                 2
>>>> +#define AE_CTRL_YPHS_OFST_MASK                   GENMASK(25, 24)
>>>> +#define AE_CTRL_YPHS_OFST_SHIFT                  24
>>>> +#define AE_CTRL_XPHS_OFST_MASK                   GENMASK(27, 26)
>>>> +#define AE_CTRL_XPHS_OFST_SHIFT                  26
>>>> +
>>>> +#define ISP_AE_CRTL2_0                           0xa44c
>>>> +#define AE_CRTL2_BL12_GRBGI0_MASK                GENMASK(11, 0)
>>>> +#define AE_CRTL2_GAIN_GRBGI0_MASK                GENMASK(23, 16)
>>>> +#define AE_CRTL2_GAIN_GRBGI0_SHIFT               16
>>>> +
>>>> +#define ISP_AE_CRTL2_1                           0xa450
>>>> +#define AE_CRTL2_BL12_GRBGI1_MASK                GENMASK(11, 0)
>>>> +#define AE_CRTL2_GAIN_GRBGI1_MASK                GENMASK(23, 16)
>>>> +#define AE_CRTL2_GAIN_GRBGI1_SHIFT               16
>>>> +
>>>> +#define ISP_AE_CRTL2_2                           0xa454
>>>> +#define AE_CRTL2_BL12_GRBGI2_MASK                GENMASK(11, 0)
>>>> +#define AE_CRTL2_GAIN_GRBGI2_MASK                GENMASK(23, 16)
>>>> +#define AE_CRTL2_GAIN_GRBGI2_SHIFT               16
>>>> +
>>>> +#define ISP_AE_CRTL2_3                           0xa458
>>>> +#define AE_CRTL2_BL12_GRBGI3_MASK                GENMASK(11, 0)
>>>> +#define AE_CRTL2_GAIN_GRBGI3_MASK                GENMASK(23, 16)
>>>> +#define AE_CRTL2_GAIN_GRBGI3_SHIFT               16
>>>> +
>>>> +#define ISP_AE_CRTL2_4                           0xa45C
>>>> +#define AE_CRTL2_BL12_GRBGI4_MASK                GENMASK(11, 0)
>>>> +#define AE_CRTL2_GAIN_GRBGI4_MASK                GENMASK(23, 16)
>>>> +#define AE_CRTL2_GAIN_GRBGI4_SHIFT               16
>>>> +
>>>> +#define ISP_AE_HV_SIZE                           0xa464
>>>> +#define AE_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
>>>> +#define AE_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AE_HV_BLKNUM                         0xa468
>>>> +#define AE_HV_STAT_VBLK_NUM_MASK                 GENMASK(6, 0)
>>>> +#define AE_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x7f)
>>>> +#define AE_HV_STAT_HBLK_NUM_MASK                 GENMASK(22, 16)
>>>> +#define AE_HV_STAT_HBLK_NUM_SHIFT                16
>>>> +
>>>> +#define ISP_AE_STAT_THD01                        0xa46c
>>>> +#define AE_STAT_THRD0_INIT                       (13107 & 0xffff)
>>>> +#define AE_STAT_THRD1_INIT                       ((26214 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AE_STAT_THD23                        0xa470
>>>> +#define AE_STAT_THRD2_INIT                       (39321 & 0xffff)
>>>> +#define AE_STAT_THRD3_INIT                       ((52428 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_RO_AE_STAT_GPNUM                     0xa49c
>>>> +#define ISP_AE_IDX_ADDR                          0xa600
>>>> +#define ISP_AE_IDX_DATA                          0xa604
>>>> +#define AE_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
>>>> +#define AE_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AE_BLK_WT_ADDR                       0xa608
>>>> +#define ISP_AE_BLK_WT_DATA                       0xa60c
>>>> +#define AE_BLK_WT_DATA0(x)                       ((x) & 0xf)
>>>> +#define AE_BLK_WT_DATA1(x)                       (((x) & 0xf) << 4)
>>>> +#define AE_BLK_WT_DATA2(x)                       (((x) & 0xf) << 8)
>>>> +#define AE_BLK_WT_DATA3(x)                       (((x) & 0xf) << 12)
>>>> +#define AE_BLK_WT_DATA4(x)                       (((x) & 0xf) << 16)
>>>> +#define AE_BLK_WT_DATA5(x)                       (((x) & 0xf) << 20)
>>>> +#define AE_BLK_WT_DATA6(x)                       (((x) & 0xf) << 24)
>>>> +#define AE_BLK_WT_DATA7(x)                       (((x) & 0xf) << 28)
>>>> +
>>>> +#define ISP_AWB_CTRL                             0xa834
>>>> +#define AWB_CTRL_YPHS_OFST_MASK                  GENMASK(1, 0)
>>>> +#define AWB_CTRL_XPHS_OFST_MASK                  GENMASK(3, 2)
>>>> +#define AWB_CTRL_XPHS_OFST_SHIFT                 2
>>>> +#define AWB_CTRL_STAT_RATIO_MODE                 BIT(4)
>>>> +
>>>> +#define ISP_AWB_HV_SIZE                          0xa83c
>>>> +#define AWB_HV_STAT_VSIZE(x)                     ((x) & 0xffff)
>>>> +#define AWB_HV_STAT_HSIZE(x)                     (((x) & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AWB_STAT_ROI0_WIN01                  0xa80c
>>>> +#define AWB_ROI_XYWIN_00_INIT                    (32 & 0xffff)
>>>> +#define AWB_ROI_XYWIN_01_INIT                    ((62 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AWB_STAT_ROI1_WIN01                  0xa810
>>>> +#define AWB_ROI_XYWIN_10_INIT                    (32 & 0xffff)
>>>> +#define AWB_ROI_XYWIN_11_INIT                    ((82 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AWB_STAT_ROI0_WIN23                  0xa814
>>>> +#define AWB_ROI_XYWIN_02_INIT                    (32 & 0xffff)
>>>> +#define AWB_ROI_XYWIN_03_INIT                    ((42 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AWB_STAT_ROI1_WIN23                  0xa818
>>>> +#define AWB_ROI_XYWIN_12_INIT                    (64 & 0xffff)
>>>> +#define AWB_ROI_XYWIN_13_INIT                    ((52 & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AWB_HV_BLKNUM                        0xa840
>>>> +#define AWB_HV_STAT_VBLK_NUM_MASK                GENMASK(5, 0)
>>>> +#define AWB_HV_STAT_VBLK_NUM_EXT(x)              ((x) & 0x3f)
>>>> +#define AWB_HV_STAT_HBLK_NUM_MASK                GENMASK(21, 16)
>>>> +#define AWB_HV_STAT_HBLK_NUM_SHIFT               16
>>>> +
>>>> +#define ISP_AWB_STAT_RG                          0xa848
>>>> +#define AWB_STAT_RG_MIN_MASK                     GENMASK(11, 0)
>>>> +#define AWB_STAT_RG_MAX_MASK                     GENMASK(27, 16)
>>>> +#define AWB_STAT_RG_MAX_SHIFT                    16
>>>> +
>>>> +#define ISP_AWB_STAT_BG                          0xa84c
>>>> +#define AWB_STAT_BG_MIN_MASK                     GENMASK(11, 0)
>>>> +#define AWB_STAT_BG_MAX_MASK                     GENMASK(27, 16)
>>>> +#define AWB_STAT_BG_MAX_SHIFT                    16
>>>> +
>>>> +#define ISP_AWB_STAT_RG_HL                       0xa850
>>>> +#define AWB_STAT_RG_LOW_MASK                     GENMASK(11, 0)
>>>> +#define AWB_STAT_RG_HIGH_MASK                    GENMASK(27, 16)
>>>> +#define AWB_STAT_RG_HIGH_SHIFT                   16
>>>> +
>>>> +#define ISP_AWB_STAT_BG_HL                       0xa854
>>>> +#define AWB_STAT_BG_LOW_MASK                     GENMASK(11, 0)
>>>> +#define AWB_STAT_BG_HIGH_MASK                    GENMASK(27, 16)
>>>> +#define AWB_STAT_BG_HIGH_SHIFT                   16
>>>> +
>>>> +#define ISP_AWB_STAT_CTRL2                       0xa858
>>>> +#define AWB_STAT_SATUR_VALID_MASK                BIT(0)
>>>> +#define AWB_STAT_LOCAL_MODE                      BIT(2)
>>>> +#define AWB_STAT_LOCAL_MODE_EXT(x)               (((x) >> 2) & 0x1)
>>>> +#define AWB_STAT_LUMA_DIV_MODE                   GENMASK(4, 3)
>>>> +#define AWB_STAT_LUMA_DIV_MODE_EXT(x)            (((x) >> 3) & 0x3)
>>>> +
>>>> +#define ISP_AWB_STAT_BLC20_0                     0xa85c
>>>> +#define AWB_STAT_BLC20_GR_MASK                   GENMASK(19, 0)
>>>> +
>>>> +#define ISP_AWB_STAT_BLC20_1                     0xa860
>>>> +#define AWB_STAT_BLC20_R_MASK                    GENMASK(19, 0)
>>>> +
>>>> +#define ISP_AWB_STAT_BLC20_2                     0xa864
>>>> +#define AWB_STAT_BLC20_B_MASK                    GENMASK(19, 0)
>>>> +
>>>> +#define ISP_AWB_STAT_BLC20_3                     0xa868
>>>> +#define AWB_STAT_BLC20_GB_MASK                   GENMASK(19, 0)
>>>> +
>>>> +#define ISP_AWB_STAT_GAIN10_0                    0xa86c
>>>> +#define AWB_STAT_GAIN10_GR_MASK                  GENMASK(9, 0)
>>>> +
>>>> +#define ISP_AWB_STAT_GAIN10_1                    0xa870
>>>> +#define AWB_STAT_GAIN10_R_MASK                   GENMASK(9, 0)
>>>> +
>>>> +#define ISP_AWB_STAT_GAIN10_2                    0xa874
>>>> +#define AWB_STAT_GAIN10_B_MASK                   GENMASK(9, 0)
>>>> +
>>>> +#define ISP_AWB_STAT_GAIN10_3                    0xa878
>>>> +#define AWB_STAT_GAIN10_GB_MASK                  GENMASK(9, 0)
>>>> +
>>>> +#define ISP_AWB_STAT_SATUR_CTRL                  0xa884
>>>> +#define AWB_STAT_SATUR_LOW(x)                    ((x) & 0xffff)
>>>> +#define AWB_STAT_SATUR_HIGH(x)                   (((x) & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AWB_IDX_ADDR                         0xaa00
>>>> +#define ISP_AWB_IDX_DATA                         0xaa04
>>>> +#define AWB_IDX_VIDX_DATA(x)                     ((x) & 0xffff)
>>>> +#define AWB_IDX_HIDX_DATA(x)                     (((x) & 0xffff) << 16)
>>>> +
>>>> +#define ISP_AWB_BLK_WT_ADDR                      0xaa08
>>>> +#define ISP_AWB_BLK_WT_DATA                      0xaa0c
>>>> +#define AWB_BLK_WT_DATA0(x)                      ((x) & 0xf)
>>>> +#define AWB_BLK_WT_DATA1(x)                      (((x) & 0xf) << 4)
>>>> +#define AWB_BLK_WT_DATA2(x)                      (((x) & 0xf) << 8)
>>>> +#define AWB_BLK_WT_DATA3(x)                      (((x) & 0xf) << 12)
>>>> +#define AWB_BLK_WT_DATA4(x)                      (((x) & 0xf) << 16)
>>>> +#define AWB_BLK_WT_DATA5(x)                      (((x) & 0xf) << 20)
>>>> +#define AWB_BLK_WT_DATA6(x)                      (((x) & 0xf) << 24)
>>>> +#define AWB_BLK_WT_DATA7(x)                      (((x) & 0xf) << 28)
>>>> +
>>>> +#define ISP_WRMIFX3_0_CH0_CTRL0                  0xc400
>>>> +#define WRMIFX3_CH0_STRIDE_MASK                  GENMASK(28, 16)
>>>> +#define WRMIFX3_CH0_STRIDE_SHIFT                 16
>>>> +
>>>> +#define ISP_WRMIFX3_0_CH0_CTRL1                  0xc404
>>>> +#define WRMIFX3_CH0_PIX_BITS_MODE_MASK           GENMASK(30, 27)
>>>> +#define WRMIFX3_CH0_PIX_BITS_MODE_SHIFT          27
>>>> +#define WRMIFX3_CH0_PIX_BITS_8BITS               1
>>>> +#define WRMIFX3_CH0_PIX_BITS_16BITS              2
>>>> +
>>>> +#define ISP_WRMIFX3_0_CH1_CTRL0                  0xc408
>>>> +#define WRMIFX3_CH1_STRIDE_MASK                  GENMASK(28, 16)
>>>> +#define WRMIFX3_CH1_STRIDE_SHIFT                 16
>>>> +
>>>> +#define ISP_WRMIFX3_0_CH1_CTRL1                  0xc40c
>>>> +#define WRMIFX3_CH1_PIX_BITS_MODE_MASK           GENMASK(30, 27)
>>>> +#define WRMIFX3_CH1_PIX_BITS_MODE_SHIFT          27
>>>> +#define WRMIFX3_CH1_PIX_BITS_16BITS              2
>>>> +#define WRMIFX3_CH1_PIX_BITS_32BITS              3
>>>> +
>>>> +#define ISP_WRMIFX3_0_CH0_BADDR                  0xc440
>>>> +/* WRMIF base address need 16 bits alignment */
>>>> +#define WRMIFX3_CH0_BADDR(x)                     (((x) >> 4) & 0xffffffff)
>>>> +
>>>> +#define ISP_WRMIFX3_0_CH1_BADDR                  0xc444
>>>> +#define WRMIFX3_CH1_BADDR(x)                     (((x) >> 4) & 0xffffffff)
>>>> +
>>>> +#define ISP_WRMIFX3_0_FMT_SIZE                   0xc464
>>>> +#define WRMIFX3_FMT_HSIZE(x)                     ((x) & 0xffff)
>>>> +#define WRMIFX3_FMT_VSIZE(x)                     (((x) & 0xffff) << 16)
>>>> +
>>>> +#define ISP_WRMIFX3_0_FMT_CTRL                   0xc468
>>>> +#define WRMIFX3_FMT_MTX_IBITS_MASK               GENMASK(1, 0)
>>>> +#define WRMIFX3_FMT_MTX_IBITS_8BIT               0
>>>> +#define WRMIFX3_FMT_MTX_IBITS_10BIT              1
>>>> +#define WRMIFX3_FMT_MTX_IBITS_12BIT              2
>>>> +#define WRMIFX3_FMT_MTX_IBITS_16BIT              3
>>>> +#define WRMIFX3_FMT_MTX_UV_SWAP_MASK             BIT(2)
>>>> +#define WRMIFX3_FMT_MTX_UV_SWAP_SHIFT            2
>>>> +#define WRMIFX3_FMT_MTX_UV_SWAP_VU               0
>>>> +#define WRMIFX3_FMT_MTX_UV_SWAP_UV               1
>>>> +#define WRMIFX3_FMT_MTX_PLANE_MASK               GENMASK(5, 4)
>>>> +#define WRMIFX3_FMT_MTX_PLANE_SHIFT              4
>>>> +#define WRMIFX3_FMT_MTX_PLANE_X1                 0
>>>> +#define WRMIFX3_FMT_MTX_PLANE_X2                 1
>>>> +#define WRMIFX3_FMT_MODE_OUT_MASK                GENMASK(18, 16)
>>>> +#define WRMIFX3_FMT_MODE_OUT_SHIFT               16
>>>> +#define WRMIFX3_FMT_MODE_OUT_YUV422              1
>>>> +#define WRMIFX3_FMT_MODE_OUT_YUV420              2
>>>> +#define WRMIFX3_FMT_MODE_OUT_Y_ONLY              3
>>>> +
>>>> +#define ISP_WRMIFX3_0_WIN_LUMA_H                 0xc420
>>>> +#define WRMIFX3_WIN_LUMA_HEND_MASK               GENMASK(28, 16)
>>>> +#define WRMIFX3_WIN_LUMA_HEND_SHIFT              16
>>>> +#define WRMIFX3_WIN_LUMA_HEND(x)                 ((x) - 1)
>>>> +
>>>> +#define ISP_WRMIFX3_0_WIN_LUMA_V                 0xc424
>>>> +#define WRMIFX3_WIN_LUMA_VEND_MASK               GENMASK(28, 16)
>>>> +#define WRMIFX3_WIN_LUMA_VEND_SHIFT              16
>>>> +#define WRMIFX3_WIN_LUMA_VEND(x)                 ((x) - 1)
>>>> +
>>>> +#define ISP_WRMIFX3_0_WIN_CHROM_H                0xc428
>>>> +#define WRMIFX3_WIN_CHROM_HEND_MASK              GENMASK(28, 16)
>>>> +#define WRMIFX3_WIN_CHROM_HEND_SHIFT             16
>>>> +#define WRMIFX3_WIN_CHROM_HEND(x)                (((x) >> 1) - 1)
>>>> +
>>>> +#define ISP_WRMIFX3_0_WIN_CHROM_V                0xc42c
>>>> +#define WRMIFX3_WIN_CHROM_VEND_MASK              GENMASK(28, 16)
>>>> +#define WRMIFX3_WIN_CHROM_VEND_SHIFT             16
>>>> +#define WRMIFX3_WIN_CHROM_VEND(x)                (((x) >> 1) - 1)
>>>> +
>>>> +#define ISP_WRMIFX3_0_CRP_HSIZE                  0xc48c
>>>> +#define WRMIFX3_CROP_HEND_MASK                   GENMASK(31, 16)
>>>> +#define WRMIFX3_CROP_HEND_SHIFT                  16
>>>> +#define WRMIFX3_CROP_HEND(x)                     ((x) - 1)
>>>> +
>>>> +#define ISP_WRMIFX3_0_CRP_VSIZE                  0xc490
>>>> +#define WRMIFX3_CROP_VEND_MASK                   GENMASK(31, 16)
>>>> +#define WRMIFX3_CROP_VEND_SHIFT                  16
>>>> +#define WRMIFX3_CROP_VEND(x)                     ((x) - 1)
>>>> +
>>>> +#define VIU_DMAWR_BADDR0                         0xc840
>>>> +#define VIU_DMAWR_AF_BADDR_MASK                  GENMASK(27, 0)
>>>> +/* AF base address need 16 bits alignment */
>>>> +#define VIU_DMAWR_AF_BADDR(x)                    ((x) >> 4)
>>>> +
>>>> +#define VIU_DMAWR_BADDR1                         0xc844
>>>> +#define VIU_DMAWR_AWB_BADDR_MASK                  GENMASK(27, 0)
>>>> +/* AWB base address need 16 bits alignment */
>>>> +#define VIU_DMAWR_AWB_BADDR(x)                    ((x) >> 4)
>>>> +
>>>> +#define VIU_DMAWR_BADDR2                         0xc848
>>>> +#define VIU_DMAWR_AE_BADDR_MASK                  GENMASK(27, 0)
>>>> +/* AE base address need 16 bits alignment */
>>>> +#define VIU_DMAWR_AE_BADDR(x)                    ((x) >> 4)
>>>> +
>>>> +#define VIU_DMAWR_SIZE0                          0xc854
>>>> +#define VIU_DMAWR_SIZE_AF_MASK                   GENMASK(15, 0)
>>>> +#define VIU_DMAWR_SIZE_AWB_MASK                  GENMASK(31, 16)
>>>> +#define VIU_DMAWR_SIZE_AWB_SHIFT                 16
>>>> +
>>>> +#define VIU_DMAWR_SIZE1                          0xc858
>>>> +#define VIU_DMAWR_SIZE_AE_MASK                   GENMASK(15, 0)
>>>> +
>>>> +#endif
>>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
>>>> new file mode 100644
>>>> index 000000000000..01d99b66cb32
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
>>>> @@ -0,0 +1,768 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#include <linux/pm_runtime.h>
>>>> +
>>>> +#include "c3-isp-common.h"
>>>> +#include "c3-isp-regs.h"
>>>> +
>>>> +static const struct c3_isp_mbus_format_info c3_isp_rsz_mbus_formats[] = {
>>>> +     /* YUV formats */
>>>> +     {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
>>>> +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>>>> +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_1_5X8,
>>>> +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>>>> +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +     }, {
>>>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_2X8,
>>> These mbus_codes come from the ISP, I presume after debayering.
>>> Is the different samples number (5X8, 2X8) a representation of the
>>> format on the internal bus between the ISP and the resizers ?
>>>
>> No,  there is no internal bus.
>>
>>>> +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>>>> +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>>>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>>> +     },
>>>> +};
>>>> +
>>>> +/* The normal parameters of pps module */
>>>> +static const int pps_lut_tap4_s11[C3_PPS_TAP4_S11_H_NUM][4] =  {
>>>> +     {  0, 511,   0,   0}, { -5, 511,   5,   0}, {-10, 511,  11,   0},
>>>> +     {-14, 510,  17,  -1}, {-18, 508,  23,  -1}, {-22, 506,  29,  -1},
>>>> +     {-25, 503,  36,  -2}, {-28, 500,  43,  -3}, {-32, 496,  51,  -3},
>>>> +     {-34, 491,  59,  -4}, {-37, 487,  67,  -5}, {-39, 482,  75,  -6},
>>>> +     {-41, 476,  84,  -7}, {-42, 470,  92,  -8}, {-44, 463, 102,  -9},
>>>> +     {-45, 456, 111, -10}, {-45, 449, 120, -12}, {-47, 442, 130, -13},
>>>> +     {-47, 434, 140, -15}, {-47, 425, 151, -17}, {-47, 416, 161, -18},
>>>> +     {-47, 407, 172, -20}, {-47, 398, 182, -21}, {-47, 389, 193, -23},
>>>> +     {-46, 379, 204, -25}, {-45, 369, 215, -27}, {-44, 358, 226, -28},
>>>> +     {-43, 348, 237, -30}, {-43, 337, 249, -31}, {-41, 326, 260, -33},
>>>> +     {-40, 316, 271, -35}, {-39, 305, 282, -36}, {-37, 293, 293, -37}
>>>> +};
>>>> +
>>>> +static const struct c3_isp_mbus_format_info
>>>> +*rsz_find_format_by_code(u32 code, u32 pad)
>>>> +{
>>>> +     int i;
>>> unsigned
>>
>> Will use unsigned int.
>>
>>>> +
>>>> +     for (i = 0; i < ARRAY_SIZE(c3_isp_rsz_mbus_formats); i++) {
>>>> +             const struct c3_isp_mbus_format_info *info =
>>>> +                     &c3_isp_rsz_mbus_formats[i];
>>>> +
>>>> +             if (info->mbus_code == code && info->pads & BIT(pad))
>>>> +                     return info;
>>>> +     }
>>>> +
>>>> +     return NULL;
>>>> +}
>>>> +
>>>> +static void c3_isp_rsz_cfg_fmt(struct c3_isp_resizer *rsz,
>>>> +                            struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *fmt;
>>>> +
>>>> +     fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +
>>>> +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_DISP0_TOP_IN_SIZE, rsz->id),
>>>> +                  DISP_TOP_IN_HSIZE(fmt->width) | DISP_TOP_IN_VSIZE(fmt->height));
>>>> +}
>>>> +
>>>> +static void c3_isp_rsz_crop_enable(struct c3_isp_resizer *rsz,
>>>> +                                struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_rect *crop;
>>>> +
>>>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +
>>>> +     c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_START, rsz->id),
>>>> +                  DISP_CRP2_VSTART(crop->top) | DISP_CRP2_HSTART(crop->left));
>>>> +     c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_SIZE, rsz->id),
>>>> +                  DISP_CRP2_VSIZE(crop->height) | DISP_CRP2_HSIZE(crop->width));
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
>>>> +                        DISP_CRP2_EN, DISP_CRP2_EN);
>>>> +}
>>>> +
>>>> +static void c3_isp_rsz_pps_size(struct c3_isp_resizer *rsz,
>>>> +                             struct c3_isp_pps_io_size *io_size)
>>>> +{
>>>> +     int thsize = io_size->thsize;
>>>> +     int tvsize = io_size->tvsize;
>>>> +     u32 ohsize = io_size->ohsize;
>>>> +     u32 ovsize = io_size->ovsize;
>>>> +     u32 ihsize = io_size->ihsize;
>>>> +     u32 max_hsize = io_size->max_hsize;
>>>> +     int step_h_integer, step_v_integer;
>>>> +     int step_h_fraction, step_v_fraction;
>>>> +     int yuv444to422_en;
>>>> +
>>>> +     /* Calculate the integer part of horizonal scaler step */
>>>> +     step_h_integer = thsize / ohsize;
>>>> +
>>>> +     /* Calculate the vertical part of horizonal scaler step */
>>>> +     step_v_integer = tvsize / ovsize;
>>>> +
>>>> +     /*
>>>> +      * Calculate the fraction part of horizonal scaler step.
>>>> +      * step_h_fraction = (source / dest) * 2^24,
>>>> +      * so step_h_fraction = ((source << 12) / dest) << 12.
>>>> +      */
>>>> +     step_h_fraction = ((thsize << 12) / ohsize) << 12;
>>>> +
>>>> +     /*
>>>> +      * Calculate the fraction part of vertical scaler step
>>>> +      * step_v_fraction = (source / dest) * 2^24,
>>>> +      * so step_v_fraction = ((source << 12) / dest) << 12.
>>>> +      */
>>>> +     step_v_fraction = ((tvsize << 12) / ovsize) << 12;
>>>> +
>>>> +     yuv444to422_en = ihsize > (max_hsize / 2) ? 1 : 0;
>>>> +
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_444TO422, rsz->id),
>>>> +                        PPS_444TO422_EN_MASK, yuv444to422_en);
>>>> +
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>>>> +                        PPS_VSC_FRACTION_PART_MASK, step_v_fraction);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>>>> +                        PPS_VSC_INTEGER_PART_MASK,
>>>> +                        step_v_integer << PPS_VSC_INTEGER_PART_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>>>> +                        PPS_HSC_FRACTION_PART_MASK, step_h_fraction);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>>>> +                        PPS_HSC_INTEGER_PART_MASK,
>>>> +                        step_h_integer << PPS_HSC_INTEGER_PART_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
>>>> +                        PPS_PREHSC_LUMA_COEF0_MASK, PPS_PREHSC_LUMA_COEF0_INIT);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
>>>> +                        PPS_PREHSC_LUMA_COEF1_MASK,
>>>> +                        PPS_PREHSC_LUMA_COEF1_INIT << PPS_PREHSC_LUMA_COEF1_SHIFT);
>>>> +
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
>>>> +                        PPS_PREHSC_LUMA_COEF2_MASK, PPS_PREHSC_LUMA_COEF2_INIT);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
>>>> +                        PPS_PREHSC_LUMA_COEF3_MASK,
>>>> +                        PPS_PREHSC_LUMA_COEF3_INIT << PPS_PREHSC_LUMA_COEF3_SHIFT);
>>>> +}
>>>> +
>>>> +static void c3_isp_rsz_pps_lut(struct c3_isp_resizer *rsz, u32 ctype)
>>>> +{
>>>> +     int i;
>>> unsigned
>>
>> Will use unsigned int.
>>
>>>> +
>>>> +     /*
>>>> +      * Default value of this register is 0,
>>>> +      * so only need to set SCALE_LUMA_COEF_S11_MODE
>>>> +      * and SCALE_LUMA_CTYPE.
>>> You can fit this in 2 lines ?
>>
>> OK, will fit this in 2 lines.
>>
>>>> +      * This register needs to be written in one time.
>>>> +      */
>>>> +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_LUMA, rsz->id),
>>>> +                  SCALE_LUMA_COEF_S11_MODE | SCALE_LUMA_CTYPE(ctype));
>>>> +
>>>> +     for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
>>>> +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>>>> +                          SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
>>>> +                          SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
>>>> +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>>>> +                          SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
>>>> +                          SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][3]));
>>>> +     }
>>>> +
>>>> +     /*
>>>> +      * Default value of this register is 0,
>>>> +      * so only need to set SCALE_CHRO_COEF_S11_MODE
>>>> +      * and SCALE_CHRO_CTYPE.
>>> same
>>>
>> OK, will fit this in 2 lines.
>>
>>>> +      * This register needs to be written in one time.
>>>> +      */
>>>> +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_CHRO, rsz->id),
>>>> +                  SCALE_CHRO_COEF_S11_MODE | SCALE_CHRO_CTYPE(ctype));
>>>> +
>>>> +     for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
>>>> +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>>>> +                          SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
>>>> +                          SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
>>>> +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>>>> +                          SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
>>>> +                          SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][3]));
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_rsz_pps_disable(struct c3_isp_resizer *rsz)
>>>> +{
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_HSC_EN_MASK, 0);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_VSC_EN_MASK, 0);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_PREVSC_EN_MASK, 0);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_PREHSC_EN_MASK, 0);
>>>> +}
>>>> +
>>>> +static int c3_isp_rsz_pps_enable(struct c3_isp_resizer *rsz,
>>>> +                              struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_rect *crop;
>>>> +     struct v4l2_rect *cmps;
>>>> +     int max_hsize;
>>>> +     int hsc_en, vsc_en;
>>>> +     int preh_en, prev_en;
>>>> +     u32 reg_prehsc_rate;
>>>> +     u32 reg_prevsc_flt_num;
>>>> +     int pre_vscale_max_hsize;
>>>> +     u32 ihsize_after_pre_hsc;
>>>> +     u32 ihsize_after_pre_hsc_alt;
>>>> +     u32 reg_vsc_tap_num_alt;
>>>> +     u32 ihsize;
>>>> +     u32 ivsize;
>>>> +     struct c3_isp_pps_io_size io_size;
>>>> +
>>>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +     cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +
>>>> +     ihsize = crop->width;
>>>> +     ivsize = crop->height;
>>>> +
>>>> +     hsc_en = (ihsize == cmps->width) ? C3_SCALE_DIS : C3_SCALE_EN;
>>>> +     vsc_en = (ivsize == cmps->height) ? C3_SCALE_DIS : C3_SCALE_EN;
>>>> +
>>>> +     /* Disable pps when there no need to use pps */
>>>> +     if (!hsc_en && !vsc_en) {
>>>> +             c3_isp_rsz_pps_disable(rsz);
>>>> +             return 0;
>>>> +     }
>>>> +
>>>> +     /*
>>>> +      * Pre-scale needs to be enable
>>>> +      * if the down scaling factor exceeds 4.
>>>> +      */
>>>> +     preh_en = (ihsize > cmps->width * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
>>>> +     prev_en = (ivsize > cmps->height * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
>>>> +
>>>> +     if (rsz->id == C3_ISP_RSZ_2) {
>>>> +             max_hsize = C3_ISP_MAX_WIDTH;
>>>> +             /*
>>>> +              * Set vertical tap number and
>>>> +              * the max hsize of pre-vertical scale.
>>>> +              */
>>>> +             reg_prevsc_flt_num = 4;
>>>> +             pre_vscale_max_hsize = max_hsize / 2;
>>>> +     } else {
>>>> +             max_hsize = C3_ISP_DEFAULT_WIDTH;
>>>> +             preh_en  = (ihsize > max_hsize) ? C3_SCALE_EN : C3_SCALE_DIS;
>>>> +             /*
>>>> +              * Set vertical tap number and
>>>> +              * the max hsize of pre-vertical scale.
>>>> +              */
>>>> +             if (ihsize > (max_hsize / 2) &&
>>>> +                 ihsize <= max_hsize && prev_en) {
>>>> +                     reg_prevsc_flt_num = 2;
>>>> +                     pre_vscale_max_hsize = max_hsize;
>>>> +             } else {
>>>> +                     reg_prevsc_flt_num = 4;
>>>> +                     pre_vscale_max_hsize = max_hsize / 2;
>>>> +             }
>>>> +     }
>>>> +
>>>> +     /*
>>>> +      * Set pre-horizonal scale rate and
>>>> +      * the hsize of after pre-horizonal scale.
>>>> +      */
>>>> +     if (preh_en) {
>>>> +             reg_prehsc_rate = 1;
>>>> +             ihsize_after_pre_hsc = DIV_ROUND_UP(ihsize, 2);
>>>> +     } else {
>>>> +             reg_prehsc_rate = 0;
>>>> +             ihsize_after_pre_hsc = ihsize;
>>>> +     }
>>>> +
>>>> +     /* Change pre-horizonal scale rate */
>>>> +     if (prev_en && ihsize_after_pre_hsc >= pre_vscale_max_hsize)
>>>> +             reg_prehsc_rate += 1;
>>>> +
>>>> +     /* Set the actual hsize of after pre-horizonal scale */
>>>> +     if (preh_en)
>>>> +             ihsize_after_pre_hsc_alt =
>>>> +                     DIV_ROUND_UP(ihsize, 1 << reg_prehsc_rate);
>>>> +     else
>>>> +             ihsize_after_pre_hsc_alt = ihsize;
>>>> +
>>>> +     /* Set vertical scaler bank length */
>>>> +     if (ihsize_after_pre_hsc_alt <= (max_hsize / 2))
>>>> +             reg_vsc_tap_num_alt = 4;
>>>> +     else if (ihsize_after_pre_hsc_alt <= max_hsize)
>>>> +             reg_vsc_tap_num_alt = prev_en ? 2 : 4;
>>>> +     else
>>>> +             reg_vsc_tap_num_alt = prev_en ? 4 : 2;
>>>> +
>>>> +     io_size.thsize = ihsize_after_pre_hsc_alt;
>>>> +     io_size.tvsize = prev_en ? DIV_ROUND_UP(ivsize, 2) : ivsize;
>>>> +     io_size.ohsize = cmps->width;
>>>> +     io_size.ovsize = cmps->height;
>>>> +     io_size.ihsize = ihsize;
>>>> +     io_size.max_hsize = max_hsize;
>>>> +
>>>> +     c3_isp_rsz_pps_size(rsz, &io_size);
>>>> +     c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_0);
>>>> +     c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_2);
>>>> +
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_VSC_TAP_NUM_MASK, reg_vsc_tap_num_alt);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_HSC_TAP_NUM_MASK,
>>>> +                        PPS_HSC_TAP_NUM_INIT << PPS_HSC_TAP_NUM_SHIFT);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_PREVSC_FLT_NUM_MASK,
>>>> +                        reg_prevsc_flt_num << PPS_PREVSC_FLT_NUM_SHIFT);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_PREHSC_FLT_NUM_MASK,
>>>> +                        PPS_PREHSC_FLT_NUM_INIT << PPS_PREHSC_FLT_NUM_SHIFT);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_PREVSC_RATE_MASK, prev_en << PPS_PREVSC_RATE_SHIFT);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_PREHSC_RATE_MASK, reg_prehsc_rate << PPS_PREHSC_RATE_SHIFT);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_HSC_EN_MASK, hsc_en << PPS_HSC_EN_SHIFT);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_VSC_EN_MASK, vsc_en << PPS_VSC_EN_SHIFT);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_PREVSC_EN_MASK, prev_en << PPS_PREVSC_EN_SHIFT);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_PREHSC_EN_MASK, preh_en << PPS_PREHSC_EN_SHIFT);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_HSC_NOR_RS_BITS_MASK,
>>>> +                        PPS_HSC_NOR_RS_BITS_INIT << PPS_HSC_NOR_RS_BITS_SHIFT);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>>> +                        PPS_VSC_NOR_RS_BITS_MASK,
>>>> +                        PPS_VSC_NOR_RS_BITS_INIT << PPS_VSC_NOR_RS_BITS_SHIFT);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz)
>>>> +{
>>>> +     struct v4l2_subdev_state *state;
>>>> +
>>>> +     state = v4l2_subdev_lock_and_get_active_state(&rsz->sd);
>>>> +
>>>> +     c3_isp_rsz_cfg_fmt(rsz, state);
>>>> +     c3_isp_rsz_crop_enable(rsz, state);
>>>> +     c3_isp_rsz_pps_enable(rsz, state);
>>>> +
>>>> +     c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN,
>>>> +                        TOP_DISP_EN(rsz->id), TOP_DISP_EN(rsz->id));
>>>> +
>>>> +     v4l2_subdev_unlock_state(state);
>>>> +}
>>>> +
>>>> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
>>>> +{
>>>> +     c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, TOP_DISP_EN(rsz->id), 0);
>>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
>>>> +                        DISP_CRP2_EN, 0x0);
>>>> +
>>>> +     c3_isp_rsz_pps_disable(rsz);
>>>> +}
>>>> +
>>>> +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
>>>> +                               struct v4l2_subdev_state *state,
>>>> +                               struct v4l2_subdev_krouting *routing)
>>>> +{
>>>> +     static const struct v4l2_mbus_framefmt format = {
>>>> +             .width = C3_ISP_DEFAULT_WIDTH,
>>>> +             .height = C3_ISP_DEFAULT_HEIGHT,
>>>> +             .code = C3_ISP_RSZ_DEF_PAD_FMT,
>>>> +             .field = V4L2_FIELD_NONE,
>>>> +             .colorspace = V4L2_COLORSPACE_SRGB,
>>>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>>>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>>>> +             .xfer_func = V4L2_XFER_FUNC_SRGB,
>>>> +     };
>>>> +     int ret;
>>>> +
>>>> +     ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
>>>> +                                struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_subdev_route routes;
>>>> +     struct v4l2_subdev_krouting routing;
>>>> +
>>>> +     routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
>>>> +     routes.sink_stream = 0;
>>>> +     routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
>>>> +     routes.source_stream = 0;
>>>> +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>>>> +
>>>> +     routing.num_routes = 1;
>>>> +     routing.routes = &routes;
>>>> +
>>>> +     return c3_isp_rsz_cfg_routing(sd, state, &routing);
>>>> +}
>>>> +
>>>> +static int c3_isp_rsz_set_routing(struct v4l2_subdev *sd,
>>>> +                               struct v4l2_subdev_state *state,
>>>> +                               enum v4l2_subdev_format_whence which,
>>>> +                               struct v4l2_subdev_krouting *routing)
>>>> +{
>>>> +     bool is_streaming = v4l2_subdev_is_streaming(sd);
>>>> +
>>>> +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>>>> +             return -EBUSY;
>>>> +
>>>> +     return c3_isp_rsz_cfg_routing(sd, state, routing);
>>>> +}
>>> Unless there are reasons I missed, I would drop routing support from
>>> the resizers
>>>
>>
>> Will remove this interface.
>>
>>>> +
>>>> +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
>>>> +                                  struct v4l2_subdev_state *state,
>>>> +                                  struct v4l2_subdev_mbus_code_enum *code)
>>>> +{
>>>> +     if (code->index >= ARRAY_SIZE(c3_isp_rsz_mbus_formats))
>>>> +             return -EINVAL;
>>>> +
>>>> +     code->code = c3_isp_rsz_mbus_formats[code->index].mbus_code;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static void c3_isp_rsz_set_sink_fmt(struct v4l2_subdev_state *state,
>>>> +                                 struct v4l2_subdev_format *format)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *sink_fmt;
>>>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>>>> +     struct v4l2_rect *sink_crop;
>>>> +     struct v4l2_rect *sink_cmps;
>>>> +
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>>> +     sink_crop = v4l2_subdev_state_get_crop(state, format->pad);
>>>> +     sink_cmps = v4l2_subdev_state_get_compose(state, format->pad);
>>>> +
>>>> +     isp_fmt = rsz_find_format_by_code(format->format.code, format->pad);
>>>> +     if (!isp_fmt)
>>>> +             sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>>>> +     else
>>>> +             sink_fmt->code = format->format.code;
>>>> +
>>>> +     sink_fmt->width = clamp_t(u32, format->format.width,
>>>> +                               C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>>>> +     sink_fmt->height = clamp_t(u32, format->format.height,
>>>> +                                C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>>>> +
>>>> +     sink_crop->width = sink_fmt->width;
>>>> +     sink_crop->height = sink_fmt->height;
>>>> +     sink_crop->left = 0;
>>>> +     sink_crop->top = 0;
>>>> +
>>>> +     sink_cmps->width = sink_crop->width;
>>>> +     sink_cmps->height = sink_crop->height;
>>>> +     sink_cmps->left = 0;
>>>> +     sink_cmps->top = 0;
>>>> +
>>>> +     format->format = *sink_fmt;
>>>> +}
>>>> +
>>>> +static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state,
>>>> +                                   struct v4l2_subdev_format *format)
>>>> +{
>>>> +     const struct c3_isp_mbus_format_info *rsz_fmt;
>>>> +     struct v4l2_mbus_framefmt *src_fmt;
>>>> +     struct v4l2_rect *sink_crop;
>>>> +     struct v4l2_rect *sink_cmps;
>>>> +
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>>> +     sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +     sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +
>>>> +     rsz_fmt = rsz_find_format_by_code(format->format.code, format->pad);
>>>> +     if (!rsz_fmt)
>>>> +             src_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>>>> +     else
>>>> +             src_fmt->code = format->format.code;
>>>> +
>>>> +     src_fmt->width = clamp_t(u32, format->format.width,
>>>> +                              C3_ISP_MIN_WIDTH, sink_crop->width);
>>>> +     src_fmt->height = clamp_t(u32, format->format.height,
>>>> +                               C3_ISP_MIN_HEIGHT, sink_crop->height);
>>>> +
>>>> +     /* The sink compose size must be same with the source size. */
>>>> +     sink_cmps->width = src_fmt->width;
>>>> +     sink_cmps->height = src_fmt->height;
>>> Shouldn't it be the other way around ? The source sizes should always
>>> match the sink compose rectangle sizes ?
>>
>> OK, will check and test this issue.
>>
>>>> +
>>>> +     format->format = *src_fmt;
>>>> +}
>>>> +
>>>> +static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd,
>>>> +                           struct v4l2_subdev_state *state,
>>>> +                           struct v4l2_subdev_format *format)
>>>> +{
>>>> +     if (format->pad == C3_ISP_RESIZER_PAD_SINK) {
>>>> +             c3_isp_rsz_set_sink_fmt(state, format);
>>>> +     } else if (format->pad == C3_ISP_RESIZER_PAD_SOURCE) {
>>>> +             c3_isp_rsz_set_source_fmt(state, format);
>>>> +     } else {
>>> This can't happen
>>
>> Will remove this branch.
>>
>>>> +             dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
>>>> +             return -ENOTTY;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd,
>>>> +                                 struct v4l2_subdev_state *state,
>>>> +                                 struct v4l2_subdev_selection *sel)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *fmt;
>>>> +     struct v4l2_rect *crop;
>>>> +     struct v4l2_rect *cmps;
>>>> +
>>>> +     if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
>>>> +             return -EINVAL;
>>>> +
>>>> +     switch (sel->target) {
>>>> +     case V4L2_SEL_TGT_CROP_BOUNDS:
>>>> +             fmt = v4l2_subdev_state_get_format(state, sel->pad);
>>>> +             sel->r.width = fmt->width;
>>>> +             sel->r.height = fmt->height;
>>>> +             sel->r.left = 0;
>>>> +             sel->r.top = 0;
>>>> +             break;
>>>> +     case V4L2_SEL_TGT_COMPOSE_BOUNDS:
>>>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>>>> +             sel->r.width = crop->width;
>>>> +             sel->r.height = crop->height;
>>>> +             sel->r.left = 0;
>>>> +             sel->r.top = 0;
>>>> +             break;
>>>> +     case V4L2_SEL_TGT_CROP:
>>>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>>>> +             sel->r = *crop;
>>>> +             break;
>>>> +     case V4L2_SEL_TGT_COMPOSE:
>>>> +             cmps = v4l2_subdev_state_get_compose(state, sel->pad);
>>>> +             sel->r = *cmps;
>>>> +             break;
>>>> +     default:
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd,
>>>> +                                 struct v4l2_subdev_state *state,
>>>> +                                 struct v4l2_subdev_selection *sel)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *fmt;
>>>> +     struct v4l2_rect *crop;
>>>> +     struct v4l2_rect *cmps;
>>>> +
>>>> +     if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
>>>> +             return -EINVAL;
>>>> +
>>>> +     switch (sel->target) {
>>>> +     case V4L2_SEL_TGT_CROP:
>>>> +             fmt = v4l2_subdev_state_get_format(state, sel->pad);
>>>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>>>> +
>>>> +             sel->r.left = clamp_t(s32, sel->r.left, 0, fmt->width - 1);
>>>> +             sel->r.top = clamp_t(s32, sel->r.top, 0, fmt->height - 1);
>>>> +             sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH,
>>>> +                                  fmt->width - sel->r.left);
>>>> +             sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT,
>>>> +                                   fmt->height - sel->r.top);
>>>> +
>>>> +             crop->width = ALIGN(sel->r.width, 2);
>>>> +             crop->height = ALIGN(sel->r.height, 2);
>>>> +             crop->left = sel->r.left;
>>>> +             crop->top = sel->r.top;
>>>> +
>>>> +             sel->r = *crop;
>>>> +             break;
>>>> +     case V4L2_SEL_TGT_COMPOSE:
>>>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>>>> +             cmps = v4l2_subdev_state_get_compose(state, sel->pad);
>>>> +
>>>> +             sel->r.left = 0;
>>>> +             sel->r.top = 0;
>>>> +             sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH, crop->width);
>>>> +             sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT, crop->height);
>>>> +
>>>> +             cmps->width = ALIGN(sel->r.width, 2);
>>>> +             cmps->height = ALIGN(sel->r.height, 2);
>>>> +             cmps->left = sel->r.left;
>>>> +             cmps->top = sel->r.top;
>>>> +
>>>> +             sel->r = *cmps;
>>>> +
>>>> +             fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
>>>> +             fmt->width = cmps->width;
>>>> +             fmt->height = cmps->height;
>>>> +             break;
>>>> +     default:
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_rsz_init_state(struct v4l2_subdev *sd,
>>>> +                              struct v4l2_subdev_state *state)
>>>> +{
>>>> +     struct v4l2_mbus_framefmt *sink_fmt;
>>>> +     struct v4l2_mbus_framefmt *src_fmt;
>>>> +     struct v4l2_rect *crop;
>>>> +     struct v4l2_rect *cmps;
>>>> +
>>>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>>>> +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>>>> +     sink_fmt->field = V4L2_FIELD_NONE;
>>>> +     sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>>>> +     sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>>> +     sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>>>> +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>>> +     sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>>>> +
>>>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +     crop->width = C3_ISP_DEFAULT_WIDTH;
>>>> +     crop->height = C3_ISP_DEFAULT_HEIGHT;
>>>> +     crop->left = 0;
>>>> +     crop->top = 0;
>>>> +
>>>> +     cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>>>> +     cmps->width = C3_ISP_DEFAULT_WIDTH;
>>>> +     cmps->height = C3_ISP_DEFAULT_HEIGHT;
>>>> +     cmps->left = 0;
>>>> +     cmps->top = 0;
>>>> +
>>>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
>>>> +     *src_fmt = *sink_fmt;
>>>> +
>>>> +     return c3_isp_rsz_init_routing(sd, state);
>>>> +}
>>>> +
>>>> +static const struct v4l2_subdev_pad_ops c3_isp_rsz_pad_ops = {
>>>> +     .enum_mbus_code = c3_isp_rsz_enum_mbus_code,
>>>> +     .get_fmt = v4l2_subdev_get_fmt,
>>>> +     .set_fmt = c3_isp_rsz_set_fmt,
>>>> +     .get_selection = c3_isp_rsz_get_selection,
>>>> +     .set_selection = c3_isp_rsz_set_selection,
>>>> +     .set_routing = c3_isp_rsz_set_routing,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_ops c3_isp_rsz_subdev_ops = {
>>>> +     .pad = &c3_isp_rsz_pad_ops,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_internal_ops c3_isp_rsz_internal_ops = {
>>>> +     .init_state = c3_isp_rsz_init_state,
>>>> +};
>>>> +
>>>> +/* Media entity operations */
>>>> +static const struct media_entity_operations c3_isp_rsz_entity_ops = {
>>>> +     .link_validate = v4l2_subdev_link_validate,
>>>> +};
>>>> +
>>>> +static int c3_isp_rsz_register(struct c3_isp_resizer *rsz)
>>>> +{
>>>> +     struct v4l2_subdev *sd = &rsz->sd;
>>>> +     int ret;
>>>> +
>>>> +     v4l2_subdev_init(sd, &c3_isp_rsz_subdev_ops);
>>>> +     sd->owner = THIS_MODULE;
>>>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>>> +     sd->internal_ops = &c3_isp_rsz_internal_ops;
>>>> +     snprintf(sd->name, sizeof(sd->name), "isp-resizer%u", rsz->id);
>>> maybe "c3-isp_resizer%u"
>>>
>> OK, will use "c3-isp_resizer%u".
>>
>>>> +
>>>> +     sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
>>>> +     sd->entity.ops = &c3_isp_rsz_entity_ops;
>>>> +
>>>> +     sd->dev = rsz->isp->dev;
>>>> +     v4l2_set_subdevdata(sd, rsz);
>>>> +
>>>> +     rsz->pads[C3_ISP_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>>>> +     rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>>>> +     ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_PAD_MAX, rsz->pads);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = v4l2_subdev_init_finalize(sd);
>>>> +     if (ret)
>>>> +             goto err_entity_cleanup;
>>>> +
>>>> +     ret = v4l2_device_register_subdev(&rsz->isp->v4l2_dev, sd);
>>>> +     if (ret)
>>>> +             goto err_subdev_cleanup;
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_subdev_cleanup:
>>>> +     v4l2_subdev_cleanup(sd);
>>>> +err_entity_cleanup:
>>>> +     media_entity_cleanup(&sd->entity);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_isp_rsz_unregister(struct c3_isp_resizer *rsz)
>>>> +{
>>>> +     struct v4l2_subdev *sd = &rsz->sd;
>>>> +
>>>> +     v4l2_device_unregister_subdev(sd);
>>>> +     v4l2_subdev_cleanup(sd);
>>>> +     media_entity_cleanup(&sd->entity);
>>>> +}
>>>> +
>>>> +int c3_isp_resizers_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     u32 i;
>>>> +     int ret;
>>>> +
>>>> +     for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
>>>> +             struct c3_isp_resizer *rsz = &isp->resizers[i];
>>>> +
>>>> +             rsz->id = i;
>>>> +             rsz->isp = isp;
>>>> +
>>>> +             if (rsz->id == C3_ISP_RSZ_0)
>>>> +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_0];
>>>> +             else if (rsz->id == C3_ISP_RSZ_1)
>>>> +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_1];
>>>> +             else
>>>> +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_2];
>>>> +
>>>> +             ret = c3_isp_rsz_register(rsz);
>>>> +             if (ret) {
>>>> +                     rsz->isp = NULL;
>>>> +                     c3_isp_resizers_unregister(isp);
>>>> +                     return ret;
>>>> +             }
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +void c3_isp_resizers_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     u32 i;
>>>> +
>>>> +     for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
>>>> +             struct c3_isp_resizer *rsz = &isp->resizers[i];
>>>> +
>>>> +             if (rsz->isp)
>>>> +                     c3_isp_rsz_unregister(rsz);
>>>> +     };
>>>> +}
>>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
>>>> new file mode 100644
>>>> index 000000000000..72024442d48f
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
>>>> @@ -0,0 +1,488 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#include <linux/cleanup.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +
>>>> +#include <media/v4l2-ctrls.h>
>>>> +#include <media/v4l2-event.h>
>>>> +#include <media/v4l2-ioctl.h>
>>>> +#include <media/v4l2-mc.h>
>>>> +#include <media/videobuf2-dma-contig.h>
>>>> +
>>>> +#include "c3-isp-common.h"
>>>> +#include "c3-isp-regs.h"
>>>> +#include "include/uapi/c3-isp-config.h"
>>>> +
>>>> +/* Hardware configuration */
>>>> +
>>>> +static void c3_isp_stats_af_init(struct c3_isp_stats *stats)
>>>> +{
>>>> +     c3_isp_write(stats->isp, ISP_AF_ROI0_WIN01,
>>>> +                  AF_ROI_XYWIN_00_INIT | AF_ROI_XYWIN_01_INIT);
>>>> +
>>>> +     c3_isp_write(stats->isp, ISP_AF_ROI0_WIN23,
>>>> +                  AF_ROI_XYWIN_02_INIT | AF_ROI_XYWIN_03_INIT);
>>>> +
>>>> +     c3_isp_write(stats->isp, ISP_AF_ROI1_WIN01,
>>>> +                  AF_ROI_XYWIN_10_INIT | AF_ROI_XYWIN_11_INIT);
>>>> +
>>>> +     c3_isp_write(stats->isp, ISP_AF_ROI1_WIN23,
>>>> +                  AF_ROI_XYWIN_12_INIT | AF_ROI_XYWIN_13_INIT);
>>>> +
>>>> +     /* 0: old statistics output, 1: new statistics output. */
>>>> +     c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL,
>>>> +                        AF_STAT_SELECT, 0x1 << AF_STAT_SELECT_SHIFT);
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_ae_init(struct c3_isp_stats *stats)
>>>> +{
>>>> +     c3_isp_write(stats->isp, ISP_AE_ROI0_WIN01,
>>>> +                  AE_ROI_XYWIN_00_INIT | AE_ROI_XYWIN_01_INIT);
>>>> +
>>>> +     c3_isp_write(stats->isp, ISP_AE_ROI0_WIN23,
>>>> +                  AE_ROI_XYWIN_02_INIT | AE_ROI_XYWIN_03_INIT);
>>>> +
>>>> +     c3_isp_write(stats->isp, ISP_AE_ROI1_WIN01,
>>>> +                  AE_ROI_XYWIN_10_INIT | AE_ROI_XYWIN_11_INIT);
>>>> +
>>>> +     c3_isp_write(stats->isp, ISP_AE_ROI1_WIN23,
>>>> +                  AE_ROI_XYWIN_12_INIT | AE_ROI_XYWIN_13_INIT);
>>>> +
>>>> +     c3_isp_write(stats->isp, ISP_AE_STAT_THD01,
>>>> +                  AE_STAT_THRD0_INIT | AE_STAT_THRD1_INIT);
>>>> +
>>>> +     c3_isp_write(stats->isp, ISP_AE_STAT_THD23,
>>>> +                  AE_STAT_THRD2_INIT | AE_STAT_THRD3_INIT);
>>>> +
>>>> +     /* Set 0 when ae_stat_switch is not 0 */
>>>> +     c3_isp_update_bits(stats->isp, ISP_AE_CTRL,
>>>> +                        AE_CTRL_INPUT_2LINE_TOGETHER, 0);
>>>> +
>>>> +     /* Configure ae luma mode */
>>>> +     c3_isp_update_bits(stats->isp, ISP_AE_CTRL, AE_CTRL_LUMA_MODE_MASK,
>>>> +                        AE_CTRL_LUMA_MODE_FILTER << AE_CTRL_LUMA_MODE_SHIFT);
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_awb_init(struct c3_isp_stats *stats)
>>>> +{
>>>> +     /* Initialize the awb statistics rectangle of image */
>>>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN01,
>>>> +                  AWB_ROI_XYWIN_00_INIT | AWB_ROI_XYWIN_01_INIT);
>>>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN23,
>>>> +                  AWB_ROI_XYWIN_02_INIT | AWB_ROI_XYWIN_03_INIT);
>>>> +
>>>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN01,
>>>> +                  AWB_ROI_XYWIN_10_INIT | AWB_ROI_XYWIN_11_INIT);
>>>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN23,
>>>> +                  AWB_ROI_XYWIN_12_INIT | AWB_ROI_XYWIN_13_INIT);
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
>>>> +{
>>>> +     struct c3_isp_device *isp = stats->isp;
>>>> +     struct c3_isp_stats_info *stats_info = stats->buff->vaddr;
>>>> +     u32 awb_dma_size = sizeof(stats_info->awb_stats);
>>>> +     u32 ae_dma_size = sizeof(stats_info->ae_stats);
>>>> +     u32 awb_dma_addr = stats->buff->paddr;
>>>> +     u32 af_dma_addr;
>>>> +     u32 ae_dma_addr;
>>>> +
>>>> +     ae_dma_addr = awb_dma_addr + awb_dma_size;
>>>> +     af_dma_addr = ae_dma_addr + ae_dma_size;
>>>> +
>>>> +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR0, VIU_DMAWR_AF_BADDR_MASK,
>>>> +                        VIU_DMAWR_AF_BADDR(af_dma_addr));
>>>> +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR1, VIU_DMAWR_AWB_BADDR_MASK,
>>>> +                        VIU_DMAWR_AWB_BADDR(awb_dma_addr));
>>>> +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR2, VIU_DMAWR_AE_BADDR_MASK,
>>>> +                        VIU_DMAWR_AE_BADDR(ae_dma_addr));
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_enable(struct c3_isp_stats *stats)
>>>> +{
>>>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>>> +                        TOP_3A_AE_STAT_EN, TOP_3A_AE_STAT_EN);
>>>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>>> +                        TOP_3A_AWB_STAT_EN, TOP_3A_AWB_STAT_EN);
>>>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>>> +                        TOP_3A_AF_STAT_EN, TOP_3A_AF_STAT_EN);
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_disable(struct c3_isp_stats *stats)
>>>> +{
>>>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>>> +                        TOP_3A_AE_STAT_EN, 0);
>>>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>>> +                        TOP_3A_AWB_STAT_EN, 0);
>>>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>>> +                        TOP_3A_AF_STAT_EN, 0);
>>>> +}
>>>> +
>>>> +/* The unit of dma_size is 16 bytes */
>>>> +static void c3_isp_stats_cfg_dmawr_size(struct c3_isp_stats *stats)
>>>> +{
>>>> +     u32 dma_size;
>>>> +
>>>> +     dma_size = sizeof(struct af_stats_info) / 16;
>>>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0,
>>>> +                        VIU_DMAWR_SIZE_AF_MASK, dma_size);
>>>> +
>>>> +     dma_size = sizeof(struct awb_stats_info) / 16;
>>>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE_AWB_MASK,
>>>> +                        dma_size << VIU_DMAWR_SIZE_AWB_SHIFT);
>>>> +
>>>> +     dma_size = sizeof(struct ae_stats_info) / 16;
>>>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1,
>>>> +                        VIU_DMAWR_SIZE_AE_MASK, dma_size);
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
>>>> +{
>>>> +     stats->buff = list_first_entry_or_null(&stats->pending,
>>>> +                                            struct c3_isp_vb2_buffer, list);
>>>> +     if (stats->buff) {
>>>> +             c3_isp_stats_cfg_dmawr_addr(stats);
>>>> +             list_del(&stats->buff->list);
>>>> +     }
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_start(struct c3_isp_stats *stats)
>>>> +{
>>>> +     c3_isp_stats_af_init(stats);
>>>> +     c3_isp_stats_ae_init(stats);
>>>> +     c3_isp_stats_awb_init(stats);
>>>> +
>>>> +     c3_isp_stats_cfg_dmawr_size(stats);
>>>> +     c3_isp_stats_cfg_buff(stats);
>>>> +     c3_isp_stats_enable(stats);
>>>> +
>>>> +     stats->is_streaming = true;
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_stop(struct c3_isp_stats *stats)
>>>> +{
>>>> +     stats->is_streaming = false;
>>>> +
>>>> +     c3_isp_stats_disable(stats);
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_return_buffers(struct c3_isp_stats *stats,
>>>> +                                     enum vb2_buffer_state state)
>>>> +{
>>>> +     unsigned long flags;
>>>> +     struct c3_isp_vb2_buffer *buff;
>>>> +
>>>> +     spin_lock_irqsave(&stats->buff_lock, flags);
>>>> +
>>>> +     if (stats->buff) {
>>>> +             vb2_buffer_done(&stats->buff->vb.vb2_buf, state);
>>>> +             stats->buff = NULL;
>>>> +     }
>>>> +
>>>> +     while (!list_empty(&stats->pending)) {
>>>> +             buff = list_first_entry(&stats->pending,
>>>> +                                     struct c3_isp_vb2_buffer, list);
>>>> +             list_del(&buff->list);
>>>> +             vb2_buffer_done(&buff->vb.vb2_buf, state);
>>>> +     }
>>>> +
>>>> +     spin_unlock_irqrestore(&stats->buff_lock, flags);
>>>> +}
>>>> +
>>>> +static int c3_isp_stats_querycap(struct file *file, void *fh,
>>>> +                              struct v4l2_capability *cap)
>>>> +{
>>>> +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>>>> +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_stats_enum_fmt(struct file *file, void *fh,
>>>> +                              struct v4l2_fmtdesc *f)
>>>> +{
>>>> +     struct c3_isp_stats *stats = video_drvdata(file);
>>>> +
>>>> +     if (f->index > 0 || f->type != stats->vb2_q.type)
>>>> +             return -EINVAL;
>>>> +
>>>> +     f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_stats_g_fmt(struct file *file, void *fh,
>>>> +                           struct v4l2_format *f)
>>>> +{
>>>> +     struct c3_isp_stats *stats = video_drvdata(file);
>>>> +
>>>> +     f->fmt.meta = stats->vfmt.fmt.meta;
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
>>>> +     .vidioc_querycap                = c3_isp_stats_querycap,
>>>> +     .vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
>>>> +     .vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
>>>> +     .vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
>>>> +     .vidioc_try_fmt_meta_cap        = c3_isp_stats_g_fmt,
>>>> +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>>>> +     .vidioc_querybuf                = vb2_ioctl_querybuf,
>>>> +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
>>>> +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
>>>> +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>>>> +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>>>> +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>>>> +     .vidioc_streamon                = vb2_ioctl_streamon,
>>>> +     .vidioc_streamoff               = vb2_ioctl_streamoff,
>>>> +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>>>> +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>>>> +};
>>>> +
>>>> +static const struct v4l2_file_operations isp_stats_v4l2_fops = {
>>>> +     .open = v4l2_fh_open,
>>>> +     .release = vb2_fop_release,
>>>> +     .poll = vb2_fop_poll,
>>>> +     .unlocked_ioctl = video_ioctl2,
>>>> +     .mmap = vb2_fop_mmap,
>>>> +};
>>>> +
>>>> +static int c3_isp_stats_vb2_queue_setup(struct vb2_queue *q,
>>>> +                                     unsigned int *num_buffers,
>>>> +                                     unsigned int *num_planes,
>>>> +                                     unsigned int sizes[],
>>>> +                                     struct device *alloc_devs[])
>>>> +{
>>>> +     if (*num_planes) {
>>>> +             if (*num_planes != 1)
>>>> +                     return -EINVAL;
>>>> +
>>>> +             if (sizes[0] < sizeof(struct c3_isp_stats_info))
>>>> +                     return -EINVAL;
>>>> +     } else {
>>>> +             *num_planes = 1;
>>>> +             sizes[0] = sizeof(struct c3_isp_stats_info);
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>>> +     struct c3_isp_vb2_buffer *buf =
>>>> +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>>>> +     unsigned long flags;
>>>> +
>>>> +     spin_lock_irqsave(&stats->buff_lock, flags);
>>>> +
>>>> +     list_add_tail(&buf->list, &stats->pending);
>>>> +
>>>> +     spin_unlock_irqrestore(&stats->buff_lock, flags);
>>>> +}
>>>> +
>>>> +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>>>> +     unsigned int size = stats->vfmt.fmt.meta.buffersize;
>>>> +
>>>> +     if (vb2_plane_size(vb, 0) < size) {
>>>> +             dev_err(stats->isp->dev,
>>>> +                     "User buffer too small (%ld < %u)\n",
>>>> +                     vb2_plane_size(vb, 0), size);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     vb2_set_plane_payload(vb, 0, size);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
>>>> +{
>>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>>> +     struct c3_isp_vb2_buffer *buf =
>>>> +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>>>> +
>>>> +     buf->vaddr = vb2_plane_vaddr(vb, 0);
>>>> +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>>>> +
>>>> +     memset(buf->vaddr, 0, stats->vfmt.fmt.meta.buffersize);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int c3_isp_stats_vb2_start_streaming(struct vb2_queue *q,
>>>> +                                         unsigned int count)
>>>> +{
>>>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(q);
>>>> +     int ret;
>>>> +
>>>> +     guard(mutex)(&stats->isp->lock);
>>>> +
>>>> +     ret = pm_runtime_resume_and_get(stats->isp->dev);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     ret = video_device_pipeline_start(&stats->vdev, &stats->isp->pipe);
>>>> +     if (ret) {
>>>> +             dev_err(stats->isp->dev,
>>>> +                     "Failed to start stats pipeline: %d\n", ret);
>>>> +             goto err_pm_put;
>>>> +     }
>>>> +
>>>> +     if (c3_isp_pipeline_ready(stats->isp)) {
>>>> +             ret = v4l2_subdev_enable_streams(&stats->isp->core.sd,
>>>> +                                              C3_ISP_CORE_PAD_SOURCE_STATS,
>>>> +                                              BIT(0));
>>>> +             if (ret)
>>>> +                     goto err_pipeline_stop;
>>>> +     }
>>>> +
>>>> +     c3_isp_stats_start(stats);
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_pipeline_stop:
>>>> +     video_device_pipeline_stop(&stats->vdev);
>>>> +err_pm_put:
>>>> +     pm_runtime_put(stats->isp->dev);
>>>> +     c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_QUEUED);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
>>>> +{
>>>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(q);
>>>> +
>>>> +     guard(mutex)(&stats->isp->lock);
>>>> +
>>>> +     c3_isp_stats_stop(stats);
>>>> +     c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_ERROR);
>>>> +
>>>> +     if (stats->isp->pipe.start_count == 1)
>>>> +             v4l2_subdev_disable_streams(&stats->isp->core.sd,
>>>> +                                         C3_ISP_CORE_PAD_SOURCE_STATS,
>>>> +                                         BIT(0));
>>>> +
>>>> +     video_device_pipeline_stop(&stats->vdev);
>>>> +     pm_runtime_put(stats->isp->dev);
>>>> +}
>>>> +
>>>> +static const struct vb2_ops isp_stats_vb2_ops = {
>>>> +     .queue_setup = c3_isp_stats_vb2_queue_setup,
>>>> +     .buf_queue = c3_isp_stats_vb2_buf_queue,
>>>> +     .buf_prepare = c3_isp_stats_vb2_buf_prepare,
>>>> +     .buf_init = c3_isp_stats_vb2_buf_init,
>>>> +     .wait_prepare = vb2_ops_wait_prepare,
>>>> +     .wait_finish = vb2_ops_wait_finish,
>>>> +     .start_streaming = c3_isp_stats_vb2_start_streaming,
>>>> +     .stop_streaming = c3_isp_stats_vb2_stop_streaming,
>>>> +};
>>>> +
>>>> +int c3_isp_stats_register(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_stats *stats = &isp->stats;
>>>> +     struct video_device *vdev = &stats->vdev;
>>>> +     struct vb2_queue *vb2_q = &stats->vb2_q;
>>>> +     int ret;
>>>> +
>>>> +     memset(stats, 0, sizeof(*stats));
>>>> +     stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
>>>> +     stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_info);
>>>> +     stats->isp = isp;
>>>> +     INIT_LIST_HEAD(&stats->pending);
>>>> +     spin_lock_init(&stats->buff_lock);
>>>> +
>>>> +     mutex_init(&stats->lock);
>>>> +
>>>> +     snprintf(vdev->name, sizeof(vdev->name), "isp-stats");
>>>> +     vdev->fops = &isp_stats_v4l2_fops;
>>>> +     vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
>>>> +     vdev->v4l2_dev = &isp->v4l2_dev;
>>>> +     vdev->lock = &stats->lock;
>>>> +     vdev->minor = -1;
>>>> +     vdev->queue = vb2_q;
>>>> +     vdev->release = video_device_release_empty;
>>>> +     vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
>>>> +     vdev->vfl_dir = VFL_DIR_RX;
>>>> +     video_set_drvdata(vdev, stats);
>>>> +
>>>> +     vb2_q->drv_priv = stats;
>>>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>>>> +     vb2_q->ops = &isp_stats_vb2_ops;
>>>> +     vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
>>>> +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>>>> +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>>>> +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>>>> +     vb2_q->dev = isp->dev;
>>>> +     vb2_q->lock = &stats->lock;
>>>> +     vb2_q->min_queued_buffers = 2;
>>>> +
>>>> +     ret = vb2_queue_init(vb2_q);
>>>> +     if (ret)
>>>> +             goto err_destroy;
>>>> +
>>>> +     stats->pad.flags = MEDIA_PAD_FL_SINK;
>>>> +     ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
>>>> +     if (ret)
>>>> +             goto err_queue_release;
>>>> +
>>>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>>>> +     if (ret) {
>>>> +             dev_err(isp->dev,
>>>> +                     "Failed to register %s: %d\n", vdev->name, ret);
>>>> +             goto err_entity_cleanup;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_entity_cleanup:
>>>> +     media_entity_cleanup(&vdev->entity);
>>>> +err_queue_release:
>>>> +     vb2_queue_release(vb2_q);
>>>> +err_destroy:
>>>> +     mutex_destroy(&stats->lock);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +void c3_isp_stats_unregister(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_stats *stats = &isp->stats;
>>>> +
>>>> +     vb2_queue_release(&stats->vb2_q);
>>>> +     media_entity_cleanup(&stats->vdev.entity);
>>>> +     video_unregister_device(&stats->vdev);
>>>> +     mutex_destroy(&stats->lock);
>>>> +}
>>>> +
>>>> +int c3_isp_stats_done(struct c3_isp_device *isp)
>>>> +{
>>>> +     struct c3_isp_stats *stats = &isp->stats;
>>>> +     struct c3_isp_vb2_buffer *buff = stats->buff;
>>>> +     unsigned long flags;
>>>> +
>>>> +     if (!stats->is_streaming)
>>>> +             return -EINVAL;
>>>> +
>>>> +     spin_lock_irqsave(&stats->buff_lock, flags);
>>>> +
>>>> +     if (buff) {
>>>> +             buff->vb.sequence = stats->isp->frm_sequence;
>>>> +             buff->vb.vb2_buf.timestamp = ktime_get();
>>>> +             buff->vb.field = V4L2_FIELD_NONE;
>>>> +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
>>>> +     }
>>>> +
>>>> +     c3_isp_stats_cfg_buff(stats);
>>>> +
>>>> +     spin_unlock_irqrestore(&stats->buff_lock, flags);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> diff --git a/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
>>>> new file mode 100644
>>>> index 000000000000..84ff5741357a
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
>>>> @@ -0,0 +1,537 @@
>>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>>>> +/*
>>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#ifndef __C3_ISP_CONFIG_H__
>>>> +#define __C3_ISP_CONFIG_H__
>>> When moving to include/uapi/linux/media/amlogic prefix this with
>>> _UAPI_
>>>
>> OK, will add "_UAPI_"
>>
>>>> +
>>>> +#define AF_STAT_BLKH_NUM             17
>>>> +#define AF_STAT_BLKV_NUM             15
>>>> +#define AF_STAT_BLK_NUM              (AF_STAT_BLKH_NUM * AF_STAT_BLKV_NUM)
>>>> +/* AF stats block size need to be aligned with 2 */
>>>> +#define AF_STAT_BLK_SIZE             ALIGN(AF_STAT_BLK_NUM, 2)
>>>> +#define AE_HISTOGRAM_SIZE         1024
>>>> +#define AE_STAT_BLKH_NUM             17
>>>> +#define AE_STAT_BLKV_NUM             15
>>>> +#define AE_STAT_BLK_NUM              (AE_STAT_BLKH_NUM * AE_STAT_BLKV_NUM)
>>>> +/* AE stats block size need to be aligned with 2 */
>>>> +#define AE_STAT_BLK_SIZE             ALIGN(AE_STAT_BLK_NUM, 2)
>>>> +#define AE_BLOCK_WT_NUM              255
>>>> +#define AE_BLK_WT_DATA_NUM_OF_GROUP  8
>>>> +#define AWB_STAT_BLKH_NUM            32
>>>> +#define AWB_STAT_BLKV_NUM            24
>>>> +#define AWB_STAT_BLK_NUM             (AWB_STAT_BLKH_NUM * AWB_STAT_BLKV_NUM)
>>>> +/* AWB stats block size need to be aligned with 2 */
>>>> +#define AWB_STAT_BLK_SIZE            ALIGN(AWB_STAT_BLK_NUM, 2)
>>>> +#define AWB_BLOCK_WT_NUM             768
>>>> +#define AWB_BLK_WT_DATA_NUM_OF_GROUP 8
>>>> +#define AWB_STAT_BLC20_NUM           4
>>>> +#define AWB_STAT_GAIN10_NUM          4
>>>> +#define BLC_OFFSET_NUM               5
>>>> +#define GAMMA_LUT_GROUP_NUM          4
>>>> +#define GAMMA_LUT_POINT_NUM          129
>>>> +#define GAMMA_LUT_DATA_NUM_OF_GROUP  2
>>>> +
>>>> +/**
>>>> + * struct awb_zone_stats - AWB statistics of a block
>>>> + *
>>>> + * AWB zone stats is aligned with 8 bytes
>>>> + *
>>>> + * @rg: the ratio of R / G in a zone
>>>> + * @bg: the ratio of B / G in a zone
>>>> + * @pixel_sum: the total number of pixels used in a zone
>>>> + */
>>>> +struct awb_zone_stats {
>>>> +     u16 rg;
>>>> +     u16 bg;
>>>> +     u32 pixel_sum;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct awb_stats_info - Auto white balance statistics information.
>>>> + *
>>>> + * AWB statistical information of all blocks.
>>>> + *
>>>> + * @awb_stats: array of auto white balance statistics
>>>> + */
>>>> +struct awb_stats_info {
>>>> +     struct awb_zone_stats awb_stats[AWB_STAT_BLK_SIZE];
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct ae_zone_stats - AE statistics of a block
>>>> + *
>>>> + * AE zone stats is aligned with 8 bytes.
>>>> + * This is a 5-bin histogram and the total sum is
>>>> + * normalized to 0xffff.
>>>> + * So hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4)
>>>> + *
>>>> + * @hist0: the global normalized pixel count for bin 0
>>>> + * @hist1: the global normalized pixel count for bin 1
>>>> + * @hist3: the global normalized pixel count for bin 3
>>>> + * @hist4: the global normalized pixel count for bin 4
>>>> + */
>>>> +struct ae_zone_stats {
>>>> +     u16 hist0;
>>> Should you include  <linux/types.h> and use types prefixed with __
>>> (__u16) ?
>>>
>> OK, will include <linux/types.h>  and use prefix "__".
>>
>>>> +     u16 hist1;
>>>> +     u16 hist3;
>>>> +     u16 hist4;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct ae_stats_info - Exposure statistics information
>>>> + *
>>>> + * AE statistical information consists of
>>>> + * all blocks information and a 1024-bin histogram.
>>>> + *
>>>> + * @ae_stats: array of auto exposure block statistics
>>>> + * @hist: a 1024-bin histogram for the entire image
>>>> + */
>>>> +struct ae_stats_info {
>>>> +     struct ae_zone_stats ae_stats[AE_STAT_BLK_SIZE];
>>>> +     u32 hist[AE_HISTOGRAM_SIZE];
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct af_zone_stats - AF statistics of a block
>>>> + *
>>>> + * AF block stats is aligned with 8 bytes.
>>>> + * The zonal accumulated contrast metrics are stored
>>>> + * in floating point format with 16 bits mantissa and
>>>> + * 5 or 6 bits exponent.
>>>> + * Apart from contrast metrics we accumulate squared image and
>>>> + * quartic image data over the zone.
>>>> + *
>>>> + * @i2_mat: the mantissa of zonal squared image pixel sum
>>>> + * @i4_mat: the mantissa of zonal quartic image pixel sum
>>>> + * @e4_mat: the mantissa of zonal multi-directional quartic edge sum
>>>> + * @i2_exp: the exponent of zonal squared image pixel sum
>>>> + * @i4_exp: the exponent of zonal quartic image pixel sum
>>>> + * @e4_exp: the exponent of zonal multi-directional quartic edge sum
>>>> + */
>>>> +struct af_zone_stats {
>>>> +     u16 i2_mat;
>>>> +     u16 i4_mat;
>>>> +     u16 e4_mat;
>>>> +     u16 i2_exp: 5;
>>>> +     u16 i4_exp: 6;
>>>> +     u16 e4_exp: 5;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct af_stats_info - Auto Focus statistics information
>>>> + *
>>>> + * AF statistical information of each block
>>>> + *
>>>> + * @af_stats: array of auto focus block statistics
>>>> + */
>>>> +struct af_stats_info {
>>>> +     struct af_zone_stats af_stats[AF_STAT_BLK_SIZE];
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct c3_isp_stats_info - V4L2_META_FMT_C3ISP_STATS
>>>> + *
>>>> + * Contains ISP statistics
>>>> + *
>>>> + * @awb_stats: auto white balance stats
>>>> + * @ae_stats: auto exposure stats
>>>> + * @af_stats: auto focus stats
>>>> + */
>>>> +struct c3_isp_stats_info {
>>>> +     struct awb_stats_info awb_stats;
>>>> +     struct ae_stats_info ae_stats;
>>>> +     struct af_stats_info af_stats;
>>>> +};
>>>> +
>>>> +/**
>>>> + * enum c3_isp_param_buffer_version -  C3 ISP parameters block versioning
>>>> + *
>>>> + * @C3_ISP_PARAM_BUFFER_V0: First version of C3 ISP parameters block
>>>> + */
>>>> +enum c3_isp_param_buffer_version {
>>>> +     C3_ISP_PARAM_BUFFER_V0,
>>>> +};
>>>> +
>>>> +/**
>>>> + * enum c3_isp_param_block_type - Enumeration of C3 ISP parameter blocks
>>>> + *
>>>> + * Each block configures a specific processing block of the C3 ISP.
>>>> + * The block type allows the driver to correctly interpret
>>>> + * the parameters block data.
>>>> + *
>>>> + * @C3_ISP_PARAM_BLOCK_WB_CHANGE: White balance change parameters
>>>> + * @C3_ISP_PARAM_BLOCK_WB_LUMA: White balance Luma-based parameters
>>>> + * @C3_ISP_PARAM_BLOCK_WB_TRIANGLE: White balance triangle parameters
>>>> + * @C3_ISP_PARAM_BLOCK_AWB_STATS: AWB statistics parameters
>>>> + * @C3_ISP_PARAM_BLOCK_AE_STATS: AE statistics parameters
>>>> + * @C3_ISP_PARAM_BLOCK_AF_STATS: AF statistics parameters
>>>> + * @C3_ISP_PARAM_BLOCK_PST_GAMMA: post gamma parameters
>>>> + * @C3_ISP_PARAM_BLOCK_DMSC: demosaic parameters
>>>> + * @C3_ISP_PARAM_BLOCK_CCM: Color correction matrix parameters
>>>> + * @C3_ISP_PARAM_BLOCK_CSC: Color space conversion parameters
>>>> + * @C3_ISP_PARAM_BLOCK_BLC: Black level correction parameters
>>>> + * @C3_ISP_PARAM_BLOCK_SENTINEL: First non-valid block index
>>>> + */
>>>> +enum c3_isp_param_block_type {
>>>> +     C3_ISP_PARAM_BLOCK_WB_CHANGE,
>>>> +     C3_ISP_PARAM_BLOCK_WB_LUMA,
>>>> +     C3_ISP_PARAM_BLOCK_WB_TRIANGLE,
>>>> +     C3_ISP_PARAM_BLOCK_AWB_STATS,
>>>> +     C3_ISP_PARAM_BLOCK_AE_STATS,
>>>> +     C3_ISP_PARAM_BLOCK_AF_STATS,
>>>> +     C3_ISP_PARAM_BLOCK_PST_GAMMA,
>>>> +     C3_ISP_PARAM_BLOCK_DMSC,
>>>> +     C3_ISP_PARAM_BLOCK_CCM,
>>>> +     C3_ISP_PARAM_BLOCK_CSC,
>>>> +     C3_ISP_PARAM_BLOCK_BLC,
>>>> +     C3_ISP_PARAM_BLOCK_SENTINEL
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct c3_isp_param_block_header - C3 ISP parameter block header
>>>> + *
>>>> + * This structure represents the common part of all the ISP configuration
>>>> + * blocks. Each parameters block shall embed an instance of this structure type
>>>> + * as its first member, followed by the block-specific configuration data. The
>>>> + * driver inspects this common header to discern the block type and its size and
>>>> + * properly handle the block content by casting it to the correct block-specific
>>>> + * type.
>>>> + *
>>>> + * @type: The parameters block type (enum c3_isp_param_block_type)
>>>> + * @enabled: Block enabled/disabled flag
>>>> + * @size: Size (in bytes) of parameters block
>>>> + */
>>>> +
>>>> +struct c3_isp_param_block_header {
>>>> +     enum c3_isp_param_block_type type;
>>>> +     bool enabled;
>>>> +     size_t size;
>>>> +};
>>> What is the size of this structure ? Is it aligned or does the
>>> compiler inserts padding bytes ? In general, I would try to align
>>> everything to 8 bytes to avoid the compiler inserting padding bytes.
>>>
>>> A tool that can help you identifies holes is pahole. Just write a
>>> small userspace program that includes types from this header and
>>> declare a variable of each type of defined in this header. Pass the
>>> executable to pahole and it will show the memory layout of each
>>> member.
>>>
>> Will check this issue.
>>
>>>> +
>>>> +/**
>>>> + * struct wb_change_cfg - White Balance configuration
>>>> + *
>>>> + * @header: The C3 ISP parameters block header
>>>> + * @wb_gain: white balance gain of each color
>>>> + *   wb_gain[0]: Gr gain, range 0~0xfff
>>>> + *   wb_gain[1]: R gain, range 0~0xfff
>>>> + *   wb_gain[2]: B gain, range 0~0xfff
>>>> + *   wb_gain[3]: Gb gain, range 0~0xfff
>>>> + *   wb_gain[4]: Ir gain, range 0~0xfff
>>>> + * @wb_limit: white balance limit of each color
>>>> + *   wb_limit[0]: Gr limit, 16 bits float
>>>> + *   wb_limit[1]: R limit, 16 bits float
>>>> + *   wb_limit[2]: B limit, 16 bits float
>>>> + *   wb_limit[3]: Gb limit, 16 bits float
>>>> + *   wb_limit[4]: Ir limit, 16 bits float
>>>> + * @ae_gain_grbgi: Gain of each color before blending to luma
>>>> + *   ae_gain_grbgi[0]: Gr gain, range 0~255
>>>> + *   ae_gain_grbgi[1]: R gain, range 0~255
>>>> + *   ae_gain_grbgi[2]: B gain, range 0~255
>>>> + *   ae_gain_grbgi[3]: Gb gain, range 0~255
>>>> + *   ae_gain_grbgi[4]: Ir gain, range 0~255
>>>> + * @ae_bl12_grbgi: Offset of each color before ae_gain_grbgi
>>>> + *   ae_bl12_grbgi[0]: Gr offset, range 0~4095
>>>> + *   ae_bl12_grbgi[1]: R offset, range 0~4095
>>>> + *   ae_bl12_grbgi[2]: B offset, range 0~4095
>>>> + *   ae_bl12_grbgi[3]: Gb offset, range 0~4095
>>>> + *   ae_bl12_grbgi[4]: Ir offset, range 0~4095
>>>> + */
>>>> +struct wb_change_cfg {
>>>> +     struct c3_isp_param_block_header header;
>>>> +     u32 wb_gain[5];
>>>> +     u32 wb_limit[5];
>>>> +     u32 ae_gain_grbgi[5];
>>>> +     u32 ae_bl12_grbgi[5];
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct wb_luma_cfg - White Balance Luma-based configuration
>>>> + *
>>>> + * @header: The C3 ISP parameters block header
>>>> + * @awb_stat_blc20: BLC in AWB statistic
>>>> + *   awb_stat_blc20[0]: Gr blc, range 0~0xfffff
>>>> + *   awb_stat_blc20[1]: R blc, range 0~0xfffff
>>>> + *   awb_stat_blc20[2]: B blc, range 0~0xfffff
>>>> + *   awb_stat_blc20[3]: Gb blc, range 0~0xfffff
>>>> + * @awb_stat_gain10: Gain in AWB statistic
>>>> + *   awb_stat_gain10[0]: Gr gain, range 0~1023
>>>> + *   awb_stat_gain10[1]: R gain, range 0~1023
>>>> + *   awb_stat_gain10[2]: B gain, range 0~1023
>>>> + *   awb_stat_gain10[3]: Gb gain, range 0~1023
>>>> + * @awb_stat_satur_low: AWB statistic under-saturation threshold
>>>> + *   value: range 0~65535
>>>> + * @awb_stat_satur_high: AWB statistic over-saturation threshold
>>>> + *   value: range 0~65535
>>>> + */
>>>> +struct wb_luma_cfg {
>>>> +     struct c3_isp_param_block_header header;
>>>> +     u32 awb_stat_blc20[AWB_STAT_BLC20_NUM];
>>>> +     u32 awb_stat_gain10[AWB_STAT_GAIN10_NUM];
>>>> +     u32 awb_stat_satur_low;
>>>> +     u32 awb_stat_satur_high;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct wb_triangle_cfg - White Balance Triangle
>>>> + *
>>>> + * @header: The C3 ISP parameters block header
>>>> + * @awb_stat_satur_vald: AWB statistic over saturation control
>>>> + *   value: 0: disable, 1: enable
>>>> + * @awb_stat_rg_min: min value of r/g
>>>> + *   value: 0~4095
>>>> + * @awb_stat_rg_max: max value of r/g
>>>> + *   value: 0~4095
>>>> + * @awb_stat_bg_min: min value of b/g
>>>> + *   value: 0~4095
>>>> + * @awb_stat_bg_max: max value of b/g
>>>> + *   value: 0~4095
>>>> + * @awb_stat_rg_low: low value of r/g
>>>> + *   value: 0~4095
>>>> + * @awb_stat_rg_high: high value of r/g
>>>> + *   value: 0~4095
>>>> + * @awb_stat_bg_low: low value of b/g
>>>> + *   value: 0~4095
>>>> + * @awb_stat_bg_high: high value of b/g
>>>> + *   value: 0~4095
>>>> + */
>>>> +struct wb_triangle_cfg {
>>>> +     struct c3_isp_param_block_header header;
>>>> +     u32 awb_stat_satur_vald;
>>>> +     u32 awb_stat_rg_min;
>>>> +     u32 awb_stat_rg_max;
>>>> +     u32 awb_stat_bg_min;
>>>> +     u32 awb_stat_bg_max;
>>>> +     u32 awb_stat_rg_low;
>>>> +     u32 awb_stat_rg_high;
>>>> +     u32 awb_stat_bg_low;
>>>> +     u32 awb_stat_bg_high;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct awb_stats_cfg - AWB statistics configuration
>>>> + *
>>>> + * This structure contains AWB statistics control information.
>>>> + *
>>>> + * @header: The C3 ISP parameters block header
>>>> + * @awb_stat_switch: the switch of AWB statistics
>>>> + *   value: 0~7
>>>> + * @awb_stat_blk_weight: Array of weights for AWB statistics blocks
>>>> + *   value: 0~15
>>>> + */
>>>> +struct awb_stats_cfg {
>>>> +     struct c3_isp_param_block_header header;
>>>> +     u8 awb_stat_switch;
>>>> +     u32 awb_stat_blk_weight[AWB_BLOCK_WT_NUM];
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct ae_stats_cfg - AE statistics configuration
>>>> + *
>>>> + * This structure contains AE statistics control information.
>>>> + *
>>>> + * @header: The C3 ISP parameters block header
>>>> + * @ae_stat_switch: the switch of AE statistics
>>>> + *   value: 0~3
>>>> + * @ae_stat_blk_weight: Array of weights for AE statistics blocks
>>>> + *   value: 0~15
>>>> + */
>>>> +struct ae_stats_cfg {
>>>> +     struct c3_isp_param_block_header header;
>>>> +     u8 ae_stat_switch;
>>>> +     u32 ae_stat_blk_weight[AE_BLOCK_WT_NUM];
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct af_stats_cfg - AF statistics configuration
>>>> + *
>>>> + * This structure contains AF statistics control information.
>>>> + *
>>>> + * @header: The C3 ISP parameters block header
>>>> + * @af_stat_switch: the switch of AF statistics
>>>> + *   value: 0~3
>>>> + */
>>>> +struct af_stats_cfg {
>>>> +     struct c3_isp_param_block_header header;
>>>> +     u8 af_stat_switch;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct pst_gamma_cfg - Post gamma configuration
>>>> + *
>>>> + * This structure contains post gamma parameters
>>>> + *
>>>> + * @header: The C3 ISP parameters block header
>>>> + * @pst_gamma_lut: LUT for P-Stitch gamma
>>>> + *   value: 0~65535
>>>> + */
>>>> +struct pst_gamma_cfg {
>>>> +     struct c3_isp_param_block_header header;
>>>> +     u32 pst_gamma_lut[GAMMA_LUT_GROUP_NUM][GAMMA_LUT_POINT_NUM];
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct dmsc_cfg - Demosaic configuration
>>>> + *
>>>> + * This structure contains demosaic parameters
>>>> + *
>>>> + * @header: The C3 ISP parameters block header
>>>> + */
>>>> +struct dmsc_cfg {
>>>> +     struct c3_isp_param_block_header header;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct ccm_cfg - ISP CCM configuration
>>>> + *
>>>> + * This structure holds the parameters for configuring the CCM,
>>>> + * which is used for color correction.
>>>> + *
>>>> + * @header: The C3 ISP parameters block header
>>>> + * @ccm_4x3matrix: A 3x4 matrix used for color correction
>>>> + *   value: 0~8191
>>>> + */
>>>> +struct ccm_cfg {
>>>> +     struct c3_isp_param_block_header header;
>>>> +     u32 ccm_4x3matrix[3][4];
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct csc_cfg - ISP Color Space Conversion configuration
>>>> + *
>>>> + * This structure contains settings for color space conversion.
>>>> + *
>>>> + * @header: The C3 ISP parameters block header
>>>> + * @cm0_offset_inp: Input offset values for the 0-order color matrix
>>>> + *   value: 0~8191
>>>> + * @cm0_offset_oup: Output offset values for the 0-order color matrix
>>>> + *   value: 0~8191
>>>> + * @cm0_3x3mtrx_rs: matrix right shift for cm0
>>>> + *   value: 0~3
>>>> + * @cm0_3x3matrix: A 3x3 matrix used for the color matrix
>>>> + *   value: 0~8191
>>>> + */
>>>> +struct csc_cfg {
>>>> +     struct c3_isp_param_block_header header;
>>>> +     u32 cm0_offset_inp[3];
>>>> +     u32 cm0_offset_oup[3];
>>>> +     u32 cm0_3x3mtrx_rs;
>>>> +     u32 cm0_3x3matrix[3][3];
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct blc_cfg - ISP Black Level Correction (BLC) configuration
>>>> + *
>>>> + * This structure holds the parameters for BLC in image processing.
>>>> + *
>>>> + * @header: The C3 ISP parameters block header
>>>> + * @fe_bl_ofst: Array of front-end BLC offsets for each color each channels
>>>> + *   fe_bl_ofst[0]: Gr blc offset, range 0~0x1fffff
>>>> + *   fe_bl_ofst[1]: R blc offset, range 0~0x1fffff
>>>> + *   fe_bl_ofst[2]: B blc offset, range 0~0x1fffff
>>>> + *   fe_bl_ofst[3]: Gb blc offset, range 0~0x1fffff
>>>> + *   fe_bl_ofst[4]: Ir blc offset, range 0~0x1fffff
>>>> + * @blc_ofst: Array of LSWB BLC offsets
>>>> + *   blc_ofst[0]: Gr blc offset, 16 bits float
>>>> + *   blc_ofst[1]: R blc offset, 16 bits float
>>>> + *   blc_ofst[2]: B blc offset, 16 bits float
>>>> + *   blc_ofst[3]: Gb blc offset, 16 bits float
>>>> + *   blc_ofst[4]: Ir blc offset, 16 bits float
>>>> + */
>>>> +struct blc_cfg {
>>>> +     struct c3_isp_param_block_header header;
>>>> +     u32 fe_bl_ofst[BLC_OFFSET_NUM];
>>>> +     u32 blc_ofst[BLC_OFFSET_NUM];
>>>> +};
>>>> +
>>>> +/**
>>>> + * define C3_ISP_PARAMS_MAX_SIZE - Maximum size of all C3 ISP Parameters
>>>> + *
>>>> + * Though the parameters for the C3 ISP are passed as optional blocks, the
>>>> + * driver still needs to know the absolute maximum size so that it can allocate
>>>> + * a buffer sized appropriately to accommodate userspace attempting to set all
>>>> + * possible parameters in a single frame.
>>>> + */
>>>> +#define C3_ISP_PARAMS_MAX_SIZE                 \
>>>> +     (sizeof(struct wb_change_cfg) +        \
>>>> +     sizeof(struct wb_luma_cfg)   +         \
>>>> +     sizeof(struct wb_triangle_cfg) +       \
>>>> +     sizeof(struct awb_stats_cfg) +          \
>>>> +     sizeof(struct ae_stats_cfg) +           \
>>>> +     sizeof(struct af_stats_cfg) +           \
>>>> +     sizeof(struct pst_gamma_cfg) +         \
>>>> +     sizeof(struct dmsc_cfg) +              \
>>>> +     sizeof(struct ccm_cfg) +               \
>>>> +     sizeof(struct csc_cfg) +               \
>>>> +     sizeof(struct blc_cfg))
>>>> +
>>>> +/**
>>>> + * struct c3_isp_params_buffer - C3 ISP configuration parameters
>>>> + *
>>>> + * This struct contains the configuration parameters of the C3 ISP
>>>> + * algorithms, serialized by userspace into an opaque data buffer. Each
>>>> + * configuration parameter block is represented by a block-specific structure
>>>> + * which contains a :c:type:`c3_isp_param_block_header` entry as first
>>>> + * member. Userspace populates the @data buffer with configuration parameters
>>>> + * for the blocks that it intends to configure. As a consequence, the data
>>>> + * buffer effective size changes according to the number of ISP blocks that
>>>> + * userspace intends to configure.
>>>> + *
>>>> + * The parameters buffer is versioned by the @version field to allow modifying
>>>> + * and extending its definition. Userspace should populate the @version field to
>>>> + * inform the driver about the version it intends to use. The driver will parse
>>>> + * and handle the @data buffer according to the data layout specific to the
>>>> + * indicated revision and return an error if the desired revision is not
>>>> + * supported.
>>>> + *
>>>> + * For each ISP block that userspace wants to configure, a block-specific
>>>> + * structure is appended to the @data buffer, one after the other without gaps
>>>> + * in between nor overlaps. Userspace shall populate the @total_size field with
>>>> + * the effective size, in bytes, of the @data buffer.
>>>> + *
>>>> + * The expected memory layout of the parameters buffer is::
>>>> + *
>>>> + *   +-------------------- struct c3_isp_params_buffer ------------------+
>>>> + *   | version = C3_ISP_PARAM_BUFFER_V0;                                   |
>>>> + *   | total_size = sizeof(sizeof(struct wb_change_cfg))                   |
>>>> + *   |              sizeof(sizeof(struct wb_luma_cfg));                    |
>>>> + *   | +------------------------- data  ---------------------------------+ |
>>>> + *   | | +------------------ struct wb_change_cfg) --------------------+ | |
>>>> + *   | | | +---------  struct c3_isp_param_block_header header  -----+ | | |
>>>> + *   | | | | type = C3_ISP_PARAM_BLOCK_WB_CHANGE;                    | | | |
>>>> + *   | | | | enabled = true;                                            | | | |
>>>> + *   | | | | size =                                                  | | | |
>>>> + *   | | | |    sizeof(struct c3_isp_param_block_header header);     | | | |
>>>> + *   | | | +---------------------------------------------------------+ | | |
>>>> + *   | | | wb_gain[5] = ...;                                           | | |
>>>> + *   | | | wb_limit[5] = ...;                                          | | |
>>>> + *   | | | ae_gain_grbgi[5] = ...;                                     | | |
>>>> + *   | | | ae_bl12_grbgi[5] = ...;                                     | | |
>>>> + *   | | +------------------ struct wb_luma_cfg -----------------------+ | |
>>>> + *   | | | +---------- struct c3_isp_param_block_header header ------+ | | |
>>>> + *   | | | | type = C3_ISP_PARAM_BLOCK_WB_LUMA;                      | | | |
>>>> + *   | | | | enabled = true;                                            | | | |
>>>> + *   | | | | size = sizeof(struct wb_luma_cfg);                      | | | |
>>>> + *   | | | +---------------------------------------------------------+ | | |
>>>> + *   | | | awb_stat_blc20[4] = ...;                                    | | |
>>>> + *   | | | awb_stat_gain10[4] = ...;                                   | | |
>>>> + *   | | | awb_stat_satur_low = ...;                                   | | |
>>>> + *   | | | awb_stat_satur_high = ...;                                  | | |
>>>> + *   | | +-------------------------------------------------------------+ | |
>>>> + *   | +-----------------------------------------------------------------+ |
>>>> + *   +---------------------------------------------------------------------+
>>>> + *
>>>> + * @version: The C3 ISP parameters buffer version
>>>> + * @total_size: The C3 ISP configuration data effective size,
>>>> + *           excluding this header
>>>> + * @data: The C3 ISP configuration blocks data
>>>> + */
>>>> +struct c3_isp_params_buffer {
>>>> +     enum c3_isp_param_buffer_version version;
>>>> +     size_t total_size;
>>>> +     u8 data[C3_ISP_PARAMS_MAX_SIZE];
>>>> +};
>>>> +
>>>> +#endif
>>>>
>>>> --
>>>> 2.46.1
>>>>
>>>>
>>>>

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

* Re: [PATCH v3 7/9] media: platform: Add c3 ISP driver
  2024-11-11  3:00         ` Keke Li
@ 2024-11-11  7:50           ` Jacopo Mondi
  0 siblings, 0 replies; 37+ messages in thread
From: Jacopo Mondi @ 2024-11-11  7:50 UTC (permalink / raw)
  To: Keke Li, Sakari Ailus, Laurent Pinchart
  Cc: Jacopo Mondi, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-media, devicetree,
	linux-kernel, kieran.bingham, laurent.pinchart, dan.scally

Hi Keke,
   let me ask Sakari's and Laurent's opinion on this specific
issue

On Mon, Nov 11, 2024 at 11:00:07AM +0800, Keke Li wrote:
> Hi Jacopo
>
> Thanks very much for your reply.
>
> On 2024/11/8 22:04, Jacopo Mondi wrote:
> > [ EXTERNAL EMAIL ]
> >
> > Hi Keke
> >
> > On Fri, Nov 08, 2024 at 08:34:41PM +0800, Keke Li wrote:
> > > Hi Jacopo
> > >
> > > Thanks very much for your reply.
> > >
> > > On 2024/11/8 00:03, Jacopo Mondi wrote:
> > > > [ EXTERNAL EMAIL ]
> > > >
> > > > Hi Keke
> > > >
> > > >      a first pass of review without going into details about the
> > > > ISP parameters and stats but mostly on architecture.
> > > >
> > > > On Wed, Sep 18, 2024 at 02:07:18PM +0800, Keke Li via B4 Relay wrote:
> > > > > From: Keke Li <keke.li@amlogic.com>
> > > > >
> > > > > The C3 ISP supports multi-camera and muti-exposure
> > > > > high dynamic range (HDR). It brings together some
> > > > > advanced imaging technologies to provide good image quality.
> > > > > This driver mainly responsible for driving ISP pipeline
> > > > > to process raw image.
> > > > >
> > > > > Signed-off-by: Keke Li <keke.li@amlogic.com>
> > > > > ---
> > > > >    drivers/media/platform/amlogic/Kconfig             |   1 +

[snip]

> >  > > > > +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
> > > > > @@ -0,0 +1,759 @@
> > > > > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> > > > > +/*
> > > > > + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> > > > > + */
> > > > > +
> > > > > +#include <linux/cleanup.h>
> > > > > +#include <linux/pm_runtime.h>
> > > > > +
> > > > > +#include <media/v4l2-ctrls.h>
> > > > > +#include <media/v4l2-event.h>
> > > > > +#include <media/v4l2-ioctl.h>
> > > > > +#include <media/v4l2-mc.h>
> > > > > +#include <media/videobuf2-dma-contig.h>
> > > > > +
> > > > > +#include "c3-isp-common.h"
> > > > > +#include "c3-isp-regs.h"
> > > > > +
> > > > > +static const struct c3_isp_capture_format cap_formats[] = {
> > > > > +     {
> > > > > +             .mbus_code = MEDIA_BUS_FMT_Y8_1X8,
> > > > > +             .fourcc = V4L2_PIX_FMT_GREY,
> > > > > +             .depth = 8,
> > > > > +     },
> > > > > +     {
> > > > > +             .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
> > > > Does the 5X8 version represents the format on the internal bus between
> > > > the resizers and the capture device ?
> > > >
> > > > How does format propagation work from the ISP to the resizers and the
> > > > capture devices ? I mean, is there an internal bus where the number of
> > > > samples (5X8 vs 2X8) changes depending on the output format ?
> > > >
> > > There is no internal bus between the resizer and the capture device.
> > >
> > I presume there is at least a data path, maybe as internal memory
> > buffers, between the different components of the ISP though.
>
>
> Yes, there is a data path between  resizer and capture node.
>
> > > The output format should be only configured in capture device.
> > >
> > Ok, what I'm after here is finding out the reason why you have
> > different mbus codes associated with different output formats. Same
> > thing as per the below question about the mbus codes used between the
> > ISP and the resizers.
> >
> > The mbus code should describe the format as it actually is in the data
> > path between the different ISP components. In example, if your
> > demosaicing block outputs data in a specific format with a specific
> > bit-depth (in example RGB data with a 30 bits wide bus) you should use
> > MEDIA_BUS_FMT_RGB101010_1X30.
> >
> > However, if this information is not available, or not relevant as it
> > doesn't influence the behaviour of any of the ISP blocks, I think
> > using MEDIA_BUS_FMT_FIXED should be fine.
> >
> > Otherwise, if there are reasons to use MEDIA_BUS_FMT_YUYV8_1_5X8 over
> > MEDIA_BUS_FMT_YUYV8_2X8 it's fine, but please explain them :)
> >
> > Thanks
> >    j
>
>
> I have checked the ISP datasheet, there is no need to use mbus code.
>
> So decided to use MEDIA_BUS_FMT_FIXED.
>
> > > > > +             .fourcc = V4L2_PIX_FMT_NV12,
> > > > > +             .depth = 12,
> > > > > +     }, {
> > > > > +             .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
> > > > > +             .fourcc = V4L2_PIX_FMT_NV21,
> > > > > +             .depth = 12,
> > > > > +     }, {
> > > > > +             .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> > > > > +             .fourcc = V4L2_PIX_FMT_NV16,
> > > > > +             .depth = 16,
> > > > > +     }, {
> > > > > +             .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> > > > > +             .fourcc = V4L2_PIX_FMT_NV61,
> > > > > +             .depth = 16,
> > > > > +     },
> > > > > +};

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

* Re: [PATCH v3 7/9] media: platform: Add c3 ISP driver
  2024-11-08 10:47   ` Dan Scally
@ 2024-11-11  9:05     ` Keke Li
  2024-11-12 11:41       ` Dan Scally
  0 siblings, 1 reply; 37+ messages in thread
From: Keke Li @ 2024-11-11  9:05 UTC (permalink / raw)
  To: Dan Scally, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-media, devicetree, linux-kernel, kieran.bingham,
	laurent.pinchart

Hi Dan

Thanks very much for your reply.

On 2024/11/8 18:47, Dan Scally wrote:
> [ EXTERNAL EMAIL ]
>
> Hi Keke - sorry for the delay in review
>
> On 18/09/2024 07:07, Keke Li via B4 Relay wrote:
>> From: Keke Li <keke.li@amlogic.com>
>>
>> The C3 ISP supports multi-camera and muti-exposure
>> high dynamic range (HDR). It brings together some
>> advanced imaging technologies to provide good image quality.
>> This driver mainly responsible for driving ISP pipeline
>> to process raw image.
>>
>> Signed-off-by: Keke Li <keke.li@amlogic.com>
>> ---
>>   drivers/media/platform/amlogic/Kconfig             |   1 +
>>   drivers/media/platform/amlogic/Makefile            |   1 +
>>   drivers/media/platform/amlogic/c3-isp/Kconfig      |  17 +
>>   drivers/media/platform/amlogic/c3-isp/Makefile     |  10 +
>>   .../media/platform/amlogic/c3-isp/c3-isp-capture.c | 759 
>> ++++++++++++++++++
>>   .../media/platform/amlogic/c3-isp/c3-isp-common.h  | 327 ++++++++
>>   .../media/platform/amlogic/c3-isp/c3-isp-core.c    | 675 
>> ++++++++++++++++
>>   drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c | 486 ++++++++++++
>>   .../media/platform/amlogic/c3-isp/c3-isp-params.c  | 857 
>> +++++++++++++++++++++
>>   .../media/platform/amlogic/c3-isp/c3-isp-regs.h    | 683 
>> ++++++++++++++++
>>   .../media/platform/amlogic/c3-isp/c3-isp-resizer.c | 768 
>> ++++++++++++++++++
>>   .../media/platform/amlogic/c3-isp/c3-isp-stats.c   | 488 ++++++++++++
>>   .../amlogic/c3-isp/include/uapi/c3-isp-config.h    | 537 +++++++++++++
>>   13 files changed, 5609 insertions(+)
>>
>> diff --git a/drivers/media/platform/amlogic/Kconfig 
>> b/drivers/media/platform/amlogic/Kconfig
>> index df09717b28d0..ebda6b7edc2d 100644
>> --- a/drivers/media/platform/amlogic/Kconfig
>> +++ b/drivers/media/platform/amlogic/Kconfig
>> @@ -2,6 +2,7 @@
>>
>>   comment "Amlogic media platform drivers"
>>
>> +source "drivers/media/platform/amlogic/c3-isp/Kconfig"
>>   source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig"
>>   source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig"
>>   source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
>> diff --git a/drivers/media/platform/amlogic/Makefile 
>> b/drivers/media/platform/amlogic/Makefile
>> index b370154b090c..d0d9363d4d8d 100644
>> --- a/drivers/media/platform/amlogic/Makefile
>> +++ b/drivers/media/platform/amlogic/Makefile
>> @@ -1,5 +1,6 @@
>>   # SPDX-License-Identifier: GPL-2.0-only
>>
>> +obj-y += c3-isp/
>>   obj-y += c3-mipi-adapter/
>>   obj-y += c3-mipi-csi2/
>>   obj-y += meson-ge2d/
>> diff --git a/drivers/media/platform/amlogic/c3-isp/Kconfig 
>> b/drivers/media/platform/amlogic/c3-isp/Kconfig
>> new file mode 100644
>> index 000000000000..e317c1e81750
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/Kconfig
>> @@ -0,0 +1,17 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +config VIDEO_C3_ISP
>> +     tristate "Amlogic C3 Image Signal Processor (ISP) driver"
>> +     depends on ARCH_MESON || COMPILE_TEST
>> +     depends on VIDEO_DEV
>> +     depends on OF
>> +     select MEDIA_CONTROLLER
>> +     select V4L2_FWNODE
>> +     select VIDEO_V4L2_SUBDEV_API
>> +     select VIDEOBUF2_DMA_CONTIG
>> +     help
>> +       Video4Linux2 driver for Amlogic C3 ISP pipeline.
>> +       C3 ISP pipeline mainly for processing raw image
>> +       and output result to memory.
>> +
>> +       To compile this driver as a module choose m here.
>> diff --git a/drivers/media/platform/amlogic/c3-isp/Makefile 
>> b/drivers/media/platform/amlogic/c3-isp/Makefile
>> new file mode 100644
>> index 000000000000..b1b064170b57
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/Makefile
>> @@ -0,0 +1,10 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +c3-isp-objs := c3-isp-dev.o \
>> +            c3-isp-params.o \
>> +            c3-isp-stats.o \
>> +            c3-isp-capture.o \
>> +            c3-isp-core.o \
>> +            c3-isp-resizer.o
>> +
>> +obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c 
>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
>> new file mode 100644
>> index 000000000000..ee9a7a17a203
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c
>> @@ -0,0 +1,759 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/cleanup.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-event.h>
>> +#include <media/v4l2-ioctl.h>
>> +#include <media/v4l2-mc.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +
>> +static const struct c3_isp_capture_format cap_formats[] = {
>> +     {
>> +             .mbus_code = MEDIA_BUS_FMT_Y8_1X8,
>> +             .fourcc = V4L2_PIX_FMT_GREY,
>> +             .depth = 8,
>> +     },
>> +     {
>> +             .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
>> +             .fourcc = V4L2_PIX_FMT_NV12,
>> +             .depth = 12,
>> +     }, {
>> +             .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
>> +             .fourcc = V4L2_PIX_FMT_NV21,
>> +             .depth = 12,
>> +     }, {
>> +             .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
>> +             .fourcc = V4L2_PIX_FMT_NV16,
>> +             .depth = 16,
>> +     }, {
>> +             .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
>> +             .fourcc = V4L2_PIX_FMT_NV61,
>> +             .depth = 16,
>> +     },
>> +};
>> +
>> +/* Hardware configuration */
>> +
>> +/* Set the address of wrmifx3(write memory interface) */
>> +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap)
>> +{
>> +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>> +     struct c3_isp_vb2_buffer *buff = cap->buff;
>> +     u32 offset;
>> +
>> +     c3_isp_write(cap->isp,
>> +                  C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id),
>> +                  WRMIFX3_CH0_BADDR(buff->paddr));
>> +
>> +     if (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
>> +         pix->pixelformat == V4L2_PIX_FMT_NV21 ||
>> +         pix->pixelformat == V4L2_PIX_FMT_NV16 ||
>> +         pix->pixelformat == V4L2_PIX_FMT_NV61) {
>> +             offset = pix->width * pix->height;
>> +             c3_isp_write(cap->isp,
>> + C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id),
>> +                          WRMIFX3_CH1_BADDR(buff->paddr + offset));
>> +     }
>> +}
>> +
>> +static void c3_isp_cap_output_size(struct c3_isp_capture *cap)
>> +{
>> +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>> +
>> +     c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, 
>> cap->id),
>> +                        DISP_OUT_VSIZE_MASK, pix->height);
>> +     c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, 
>> cap->id),
>> +                        DISP_OUT_HSIZE_MASK, pix->width << 
>> DISP_OUT_HSIZE_SHIFT);
>> +}
>> +
>> +static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap)
>> +{
>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>> +     u32 stride;
>> +
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_IBITS_MASK, 
>> WRMIFX3_FMT_MTX_IBITS_8BIT);
>> +
>> +     /* Grey has 1 plane*/
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_PLANE_MASK,
>> +                        WRMIFX3_FMT_MTX_PLANE_X1 << 
>> WRMIFX3_FMT_MTX_PLANE_SHIFT);
>> +
>> +     /* Set Y only as output format */
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MODE_OUT_MASK,
>> +                        WRMIFX3_FMT_MODE_OUT_Y_ONLY << 
>> WRMIFX3_FMT_MODE_OUT_SHIFT);
>> +
>> +     /* The unit of stride is 128 bits */
>> +     stride = DIV_ROUND_UP(fmt->width * 8, 128);
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>> +                        WRMIFX3_CH0_STRIDE_MASK, stride << 
>> WRMIFX3_CH0_STRIDE_SHIFT);
>> +
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>> +                        WRMIFX3_CH0_PIX_BITS_MODE_MASK,
>> +                        WRMIFX3_CH0_PIX_BITS_8BITS << 
>> WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
>> +}
>> +
>> +static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, 
>> u32 swap_uv)
>> +{
>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>> +     u32 stride;
>> +
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_IBITS_MASK, 
>> WRMIFX3_FMT_MTX_IBITS_8BIT);
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_UV_SWAP_MASK,
>> +                        swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
>> +
>> +     /* NV12 or NV21 has 2 planes*/
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_PLANE_MASK,
>> +                        WRMIFX3_FMT_MTX_PLANE_X2 << 
>> WRMIFX3_FMT_MTX_PLANE_SHIFT);
>> +
>> +     /* Set YUV420 as output format */
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MODE_OUT_MASK,
>> +                        WRMIFX3_FMT_MODE_OUT_YUV420 << 
>> WRMIFX3_FMT_MODE_OUT_SHIFT);
>> +
>> +     /* The unit of stride is 128 bits */
>> +     stride = DIV_ROUND_UP(fmt->width * 8, 128);
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>> +                        WRMIFX3_CH0_STRIDE_MASK, stride << 
>> WRMIFX3_CH0_STRIDE_SHIFT);
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
>> +                        WRMIFX3_CH1_STRIDE_MASK, stride << 
>> WRMIFX3_CH1_STRIDE_SHIFT);
>> +
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>> +                        WRMIFX3_CH0_PIX_BITS_MODE_MASK,
>> +                        WRMIFX3_CH0_PIX_BITS_8BITS << 
>> WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
>> +                        WRMIFX3_CH1_PIX_BITS_MODE_MASK,
>> +                        WRMIFX3_CH1_PIX_BITS_16BITS << 
>> WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
>> +}
>> +
>> +static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, 
>> u32 swap_uv)
>> +{
>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>> +     u32 stride;
>> +
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_IBITS_MASK, 
>> WRMIFX3_FMT_MTX_IBITS_16BIT);
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_UV_SWAP_MASK,
>> +                        swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT);
>> +
>> +     /* NV16 or NV61 has 2 planes*/
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MTX_PLANE_MASK,
>> +                        WRMIFX3_FMT_MTX_PLANE_X2 << 
>> WRMIFX3_FMT_MTX_PLANE_SHIFT);
>> +
>> +     /* Set YUV422 as output format */
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id),
>> +                        WRMIFX3_FMT_MODE_OUT_MASK,
>> +                        WRMIFX3_FMT_MODE_OUT_YUV422 << 
>> WRMIFX3_FMT_MODE_OUT_SHIFT);
>> +
>> +     /* The unit of stride is 128 bits */
>> +     stride = DIV_ROUND_UP(fmt->width * 16, 128);
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id),
>> +                        WRMIFX3_CH0_STRIDE_MASK, stride << 
>> WRMIFX3_CH0_STRIDE_SHIFT);
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id),
>> +                        WRMIFX3_CH1_STRIDE_MASK, stride << 
>> WRMIFX3_CH1_STRIDE_SHIFT);
>> +
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id),
>> +                        WRMIFX3_CH0_PIX_BITS_MODE_MASK,
>> +                        WRMIFX3_CH0_PIX_BITS_16BITS << 
>> WRMIFX3_CH0_PIX_BITS_MODE_SHIFT);
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id),
>> +                        WRMIFX3_CH1_PIX_BITS_MODE_MASK,
>> +                        WRMIFX3_CH1_PIX_BITS_32BITS << 
>> WRMIFX3_CH1_PIX_BITS_MODE_SHIFT);
>> +}
>> +
>> +static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap)
>> +{
>> +     struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix;
>> +
>> +     if (fmt->pixelformat == V4L2_PIX_FMT_GREY) {
>> +             c3_isp_cap_wrmifx3_grey(cap);
>> +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV12) {
>> +             c3_isp_cap_wrmifx3_yuv420(cap, 
>> WRMIFX3_FMT_MTX_UV_SWAP_UV);
>> +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV21) {
>> +             c3_isp_cap_wrmifx3_yuv420(cap, 
>> WRMIFX3_FMT_MTX_UV_SWAP_VU);
>> +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV16) {
>> +             c3_isp_cap_wrmifx3_yuv422(cap, 
>> WRMIFX3_FMT_MTX_UV_SWAP_UV);
>> +     } else if (fmt->pixelformat == V4L2_PIX_FMT_NV61) {
>> +             c3_isp_cap_wrmifx3_yuv422(cap, 
>> WRMIFX3_FMT_MTX_UV_SWAP_VU);
>> +     } else {
>> +             dev_err(cap->isp->dev, "Invalid pixel format: 0x%x\n", 
>> fmt->pixelformat);
>> +             return;
> Shouldn't this return an error code and prevent the video device from 
> being started? It's probably
> not relevant right now, but I think it'd be easier to catch mistakes 
> if the driver is extended in
> the future.


OK, will return an error.


>> +     }
>> +
>> +     c3_isp_write(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, 
>> cap->id),
>> +                  WRMIFX3_FMT_HSIZE(fmt->width) | 
>> WRMIFX3_FMT_VSIZE(fmt->height));
>> +
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id),
>> +                        WRMIFX3_WIN_LUMA_HEND_MASK,
>> +                        WRMIFX3_WIN_LUMA_HEND(fmt->width) << 
>> WRMIFX3_WIN_LUMA_HEND_SHIFT);
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id),
>> +                        WRMIFX3_WIN_LUMA_VEND_MASK,
>> +                        WRMIFX3_WIN_LUMA_VEND(fmt->height) << 
>> WRMIFX3_WIN_LUMA_VEND_SHIFT);
>> +
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_HSIZE, cap->id),
>> +                        WRMIFX3_CROP_HEND_MASK,
>> +                        WRMIFX3_CROP_HEND(fmt->width) << 
>> WRMIFX3_CROP_HEND_SHIFT);
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_VSIZE, cap->id),
>> +                        WRMIFX3_CROP_VEND_MASK,
>> +                        WRMIFX3_CROP_VEND(fmt->height) << 
>> WRMIFX3_CROP_VEND_SHIFT);
>> +
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id),
>> +                        WRMIFX3_WIN_CHROM_HEND_MASK,
>> +                        WRMIFX3_WIN_CHROM_HEND(fmt->width) << 
>> WRMIFX3_WIN_CHROM_HEND_SHIFT);
>> +     c3_isp_update_bits(cap->isp, 
>> C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id),
>> +                        WRMIFX3_WIN_CHROM_VEND_MASK,
>> +                        WRMIFX3_WIN_CHROM_VEND(fmt->height) << 
>> WRMIFX3_WIN_CHROM_VEND_SHIFT);
> Maybe these shift macros can just be part of the value macro, might 
> make things a bit tidier here.
> Not a big deal though; up to you.


OK, will check these macros.

>> +}
>> +
>> +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap)
>> +{
>> +     cap->buff = list_first_entry_or_null(&cap->pending,
>> +                                          struct c3_isp_vb2_buffer, 
>> list);
>> +     if (cap->buff) {
>> +             c3_isp_cap_wrmifx3_buff(cap);
>> +             list_del(&cap->buff->list);
>> +     }
>> +}
>
>
> Either this needs to hold cap->buff_lock or the call site in 
> c3_isp_cap_start() does.
>

Will add cap->buff_lock.

>> +
>> +static void c3_isp_cap_start(struct c3_isp_capture *cap)
>> +{
>> +     c3_isp_cap_cfg_buff(cap);
>> +
>> +     c3_isp_cap_output_size(cap);
>> +     c3_isp_cap_wrmifx3_size(cap);
>> +
>> +     c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN,
>> +                        TOP_WRMIF_EN(cap->id), TOP_WRMIF_EN(cap->id));
>> +
>> +     cap->is_streaming = true;
>> +}
>
>
> I would just drop the variable here and rely on 
> c3_isp_cap_return_buffers() having nulled
> cap->buffer and emptied the queues to replace its functionality.
>

OK, here will try to use c3_isp_cap_return_buffers().

>> +
>> +static void c3_isp_cap_stop(struct c3_isp_capture *cap)
>> +{
>> +     cap->is_streaming = false;
>> +
>> +     c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, 
>> TOP_WRMIF_EN(cap->id), 0);
>> +}
>> +
>> +static int c3_isp_cap_done(struct c3_isp_capture *cap)
>> +{
>> +     struct c3_isp_vb2_buffer *buff = cap->buff;
>> +     unsigned long flags;
>> +
>> +     if (!cap->is_streaming)
>> +             return -EINVAL;
>
>
> Return value is unchecked - it can be a void function.
>

Will modify this function to a void function.

>> +
>> +     spin_lock_irqsave(&cap->buff_lock, flags);
> scoped_guard() - similar to guard() but for a more limited scope. 
> These cleanup functions are really
> handy


Will try to use scoped_guard()

>> +
>> +     if (buff) {
>> +             buff->vb.sequence = cap->isp->frm_sequence;
>> +             buff->vb.vb2_buf.timestamp = ktime_get();
>> +             buff->vb.field = V4L2_FIELD_NONE;
>> +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
>> +     }
>> +
>> +     c3_isp_cap_cfg_buff(cap);
>> +
>> +     spin_unlock_irqrestore(&cap->buff_lock, flags);
>> +
>> +     return 0;
>> +}
>> +
>> +/* V4L2 video operations */
>> +
>> +static const struct c3_isp_capture_format
>> +*c3_cap_find_fmt(u32 fourcc)
>> +{
>> +     unsigned int i;
>> +
>> +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
>> +             if (cap_formats[i].fourcc == fourcc)
>> +                     return &cap_formats[i];
>> +     }
>> +
>> +     return NULL;
>> +}
>> +
>> +static void c3_cap_try_fmt(struct c3_isp_capture *cap,
>> +                        struct v4l2_pix_format *pix)
>> +{
>> +     const struct c3_isp_capture_format *fmt;
>> +
>> +     fmt = c3_cap_find_fmt(pix->pixelformat);
>> +     if (!fmt)
>> +             fmt = &cap_formats[0];
>> +
>> +     pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, 
>> C3_ISP_MAX_WIDTH);
>> +     pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, 
>> C3_ISP_MAX_HEIGHT);
>> +     pix->pixelformat = fmt->fourcc;
>> +     pix->field = V4L2_FIELD_NONE;
>> +     pix->colorspace = V4L2_COLORSPACE_SRGB;
>> +     pix->ycbcr_enc = V4L2_YCBCR_ENC_601;
>> +     pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>> +
>> +     /* ISP hardware requires 16 bytes alignment */
>> +     pix->bytesperline = ALIGN(pix->width, 16);
>> +     pix->sizeimage = pix->bytesperline * pix->height * fmt->depth / 8;
>> +}
>
>
> v4l2_fill_pixfmt() might do some of this for you, though I'm not sure 
> if it'll handle the alignment.
>

OK, will check this operation and refer  to other drivers.

>> +
>> +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap,
>> +                                   enum vb2_buffer_state state)
>> +{
>> +     unsigned long flags;
>> +     struct c3_isp_vb2_buffer *buff;
>> +
>> +     spin_lock_irqsave(&cap->buff_lock, flags);
> guard(spinlock_irqsave)(&cap->buff_lock);


OK,  will use guard().

>> +
>> +     if (cap->buff) {
>> +             vb2_buffer_done(&cap->buff->vb.vb2_buf, state);
>> +             cap->buff = NULL;
>> +     }
>> +
>> +     while (!list_empty(&cap->pending)) {
>> +             buff = list_first_entry(&cap->pending,
>> +                                     struct c3_isp_vb2_buffer, list);
>> +             list_del(&buff->list);
>> +             vb2_buffer_done(&buff->vb.vb2_buf, state);
>> +     }
>> +
>> +     spin_unlock_irqrestore(&cap->buff_lock, flags);
>> +}
>> +
>> +static int c3_isp_cap_querycap(struct file *file, void *fh,
>> +                            struct v4l2_capability *cap)
>> +{
>> +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>> +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_cap_enum_fmt(struct file *file, void *fh,
>> +                            struct v4l2_fmtdesc *f)
>> +{
>> +     const struct c3_isp_capture_format *fmt;
>> +     unsigned int index = 0;
>> +     unsigned int i;
>> +
>> +     if (!f->mbus_code) {
>> +             if (f->index >= ARRAY_SIZE(cap_formats))
>> +                     return -EINVAL;
>> +
>> +             fmt = &cap_formats[f->index];
>> +             f->pixelformat = fmt->fourcc;
>> +             return 0;
>> +     }
>> +
>> +     for (i = 0; i < ARRAY_SIZE(cap_formats); i++) {
>> +             fmt = &cap_formats[i];
>> +             if (f->mbus_code != fmt->mbus_code)
>> +                     continue;
>> +
>> +             if (index++ == f->index) {
>> +                     f->pixelformat = cap_formats[i].fourcc;
>> +                     return 0;
>> +             }
>> +     }
>> +
>> +     return -EINVAL;
>> +}
>> +
>> +static int c3_isp_cap_g_fmt(struct file *file, void *fh,
>> +                         struct v4l2_format *f)
>> +{
>> +     struct c3_isp_capture *cap = video_drvdata(file);
>> +
>> +     f->fmt.pix = cap->vfmt.fmt.pix;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_cap_s_fmt(struct file *file, void *fh,
>> +                         struct v4l2_format *f)
>> +{
>> +     struct c3_isp_capture *cap = video_drvdata(file);
>> +
>> +     c3_cap_try_fmt(cap, &f->fmt.pix);
>> +     cap->vfmt = *f;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_cap_try_fmt(struct file *file, void *fh,
>> +                           struct v4l2_format *f)
>> +{
>> +     struct c3_isp_capture *cap = video_drvdata(file);
>> +
>> +     c3_cap_try_fmt(cap, &f->fmt.pix);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh,
>> +                                struct v4l2_frmsizeenum *fsize)
>> +{
>> +     const struct c3_isp_capture_format *fmt;
>> +
>> +     if (fsize->index)
>> +             return -EINVAL;
>> +
>> +     fmt = c3_cap_find_fmt(fsize->pixel_format);
>> +     if (!fmt)
>> +             return -EINVAL;
>> +
>> +     fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
>> +     fsize->stepwise.min_width = C3_ISP_MIN_WIDTH;
>> +     fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT;
>> +     fsize->stepwise.max_width = C3_ISP_MAX_WIDTH;
>> +     fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT;
>> +     fsize->stepwise.step_width = 2;
>> +     fsize->stepwise.step_height = 2;
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = {
>> +     .vidioc_querycap                = c3_isp_cap_querycap,
>> +     .vidioc_enum_fmt_vid_cap        = c3_isp_cap_enum_fmt,
>> +     .vidioc_g_fmt_vid_cap           = c3_isp_cap_g_fmt,
>> +     .vidioc_s_fmt_vid_cap           = c3_isp_cap_s_fmt,
>> +     .vidioc_try_fmt_vid_cap         = c3_isp_cap_try_fmt,
>> +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>> +     .vidioc_querybuf                = vb2_ioctl_querybuf,
>> +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
>> +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
>> +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>> +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>> +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>> +     .vidioc_streamon                = vb2_ioctl_streamon,
>> +     .vidioc_streamoff               = vb2_ioctl_streamoff,
>> +     .vidioc_enum_framesizes         = c3_isp_cap_enum_frmsize,
>> +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>> +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>> +};
>> +
>> +static const struct v4l2_file_operations isp_cap_v4l2_fops = {
>> +     .open = v4l2_fh_open,
>> +     .release = vb2_fop_release,
>> +     .poll = vb2_fop_poll,
>> +     .unlocked_ioctl = video_ioctl2,
>> +     .mmap = vb2_fop_mmap,
>> +};
>> +
>> +static int c3_isp_cap_link_validate(struct media_link *link)
>> +{
>> +     struct video_device *vdev =
>> + media_entity_to_video_device(link->sink->entity);
>> +     struct v4l2_subdev *sd =
>> + media_entity_to_v4l2_subdev(link->source->entity);
>> +     struct c3_isp_capture *cap = video_get_drvdata(vdev);
>> +     struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix;
>> +     struct v4l2_subdev_format src_fmt = {
>> +             .which = V4L2_SUBDEV_FORMAT_ACTIVE,
>> +             .pad = link->source->index,
>> +     };
>> +     const struct c3_isp_capture_format *cap_fmt =
>> + c3_cap_find_fmt(pix_fmt->pixelformat);
>> +     int ret;
>> +
>> +     ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt);
>> +     if (ret)
>> +             return ret;
>> +
>> +     if (src_fmt.format.width != pix_fmt->width ||
>> +         src_fmt.format.height != pix_fmt->height ||
>> +         src_fmt.format.code != cap_fmt->mbus_code) {
>> +             dev_err(cap->isp->dev,
>> +                     "link %s: %u -> %s: %u not valid: 0x%04x/%ux%u 
>> not match 0x%04x/%ux%u\n",
>> +                     link->source->entity->name, link->source->index,
>> +                     link->sink->entity->name, link->sink->index,
>> +                     src_fmt.format.code, src_fmt.format.width,
>> +                     src_fmt.format.height, cap_fmt->mbus_code,
>> +                     pix_fmt->width, pix_fmt->height);
>> +
>> +             return -EPIPE;
>> +     };
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct media_entity_operations isp_cap_entity_ops = {
>> +     .link_validate = c3_isp_cap_link_validate,
>> +};
>> +
>> +static int c3_isp_vb2_queue_setup(struct vb2_queue *q,
>> +                               unsigned int *num_buffers,
>> +                               unsigned int *num_planes,
>> +                               unsigned int sizes[],
>> +                               struct device *alloc_devs[])
>> +{
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>> +     struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix;
>> +
>> +     if (*num_planes) {
>> +             if (*num_planes != 1)
>> +                     return -EINVAL;
>> +
>> +             if (sizes[0] < pix->sizeimage)
>> +                     return -EINVAL;
>> +     } else {
>> +             *num_planes = 1;
>> +             sizes[0] = pix->sizeimage;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_vb2_buffer *buf =
>> +                     container_of(v4l2_buf, struct 
>> c3_isp_vb2_buffer, vb);
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned long flags;
>> +
>> +     spin_lock_irqsave(&cap->buff_lock, flags);
> guard(spinlock_irqsave)(&cap->buff_lock)


OK, will use guard().

>> +
>> +     list_add_tail(&buf->list, &cap->pending);
>> +
>> +     spin_unlock_irqrestore(&cap->buff_lock, flags);
>> +}
>> +
>> +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb)
>> +{
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned int size = cap->vfmt.fmt.pix.sizeimage;
>> +
>> +     if (vb2_plane_size(vb, 0) < size) {
>> +             dev_err(cap->isp->dev,
>> +                     "User buffer too small (%ld < %u)\n",
>> +                     vb2_plane_size(vb, 0), size);
>> +             return -EINVAL;
>> +     }
>> +
>> +     vb2_set_plane_payload(vb, 0, size);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_vb2_buf_init(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_vb2_buffer *buf =
>> +             container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue);
>> +
>> +     buf->vaddr = vb2_plane_vaddr(vb, 0);
>> +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>> +
>> +     memset(buf->vaddr, 0, cap->vfmt.fmt.pix.sizeimage);
> Hm, why the memset()? The ISP is going to overwrite the whole buffer 
> anyway surely? Not sure that
> you need the vaddr in this video device at all actually.


Yes, you are right.

Will remove this memset().

>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_vb2_start_streaming(struct vb2_queue *q,
>> +                                   unsigned int count)
>> +{
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>> +     int ret;
>> +
>> +     guard(mutex)(&cap->isp->lock);
>> +
>> +     ret = pm_runtime_resume_and_get(cap->isp->dev);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe);
>> +     if (ret) {
>> +             dev_err(cap->isp->dev,
>> +                     "Failed to start cap%u pipeline: %d\n", 
>> cap->id, ret);
>> +             goto err_pm_put;
>> +     }
>> +
>> +     if (c3_isp_pipeline_ready(cap->isp)) {
>> +             ret = v4l2_subdev_enable_streams(&cap->isp->core.sd,
>> + C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> +                                              BIT(0));
>> +             if (ret)
>> +                     goto err_pipeline_stop;
>> +     }
>> +
>> +     c3_isp_rsz_start(cap->rsz);
> Bit surprised there's no .enable_streams() call for a resizer 
> subdevice...


OK, will call .enable_streams() of a resizer subdevic.

>> +     c3_isp_cap_start(cap);
>> +
>> +     return 0;
>> +
>> +err_pipeline_stop:
>> +     video_device_pipeline_stop(&cap->vdev);
>> +err_pm_put:
>> +     pm_runtime_put(cap->isp->dev);
>> +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED);
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_vb2_stop_streaming(struct vb2_queue *q)
>> +{
>> +     struct c3_isp_capture *cap = vb2_get_drv_priv(q);
>> +
>> +     guard(mutex)(&cap->isp->lock);
>> +
>> +     c3_isp_cap_stop(cap);
>> +     c3_isp_rsz_stop(cap->rsz);
>> +     c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR);
>> +
>> +     if (cap->isp->pipe.start_count == 1)
> if (v4l2_subdev_is_streaming(&cap->isp->core.sd))
>> + v4l2_subdev_disable_streams(&cap->isp->core.sd,
>> + C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> +                                         BIT(0));
>> +
>> +     video_device_pipeline_stop(&cap->vdev);
>> +     pm_runtime_put(cap->isp->dev);
>> +}
>> +
>> +static const struct vb2_ops isp_video_vb2_ops = {
>> +     .queue_setup = c3_isp_vb2_queue_setup,
>> +     .buf_queue = c3_isp_vb2_buf_queue,
>> +     .buf_prepare = c3_isp_vb2_buf_prepare,
>> +     .buf_init = c3_isp_vb2_buf_init,
>> +     .wait_prepare = vb2_ops_wait_prepare,
>> +     .wait_finish = vb2_ops_wait_finish,
>> +     .start_streaming = c3_isp_vb2_start_streaming,
>> +     .stop_streaming = c3_isp_vb2_stop_streaming,
>> +};
>> +
>> +static int c3_isp_register_capture(struct c3_isp_capture *cap)
>> +{
>> +     struct video_device *vdev = &cap->vdev;
>> +     struct vb2_queue *vb2_q = &cap->vb2_q;
>> +     int ret;
>> +
>> +     snprintf(vdev->name, sizeof(vdev->name), "isp-cap%u", cap->id);
>> +     vdev->fops = &isp_cap_v4l2_fops;
>> +     vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops;
>> +     vdev->v4l2_dev = &cap->isp->v4l2_dev;
>> +     vdev->entity.ops = &isp_cap_entity_ops;
>> +     vdev->lock = &cap->lock;
>> +     vdev->minor = -1;
>> +     vdev->queue = vb2_q;
>> +     vdev->release = video_device_release_empty;
>> +     vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
>> +     vdev->vfl_dir = VFL_DIR_RX;
>> +     video_set_drvdata(vdev, cap);
>> +
>> +     vb2_q->drv_priv = cap;
>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>> +     vb2_q->ops = &isp_video_vb2_ops;
>> +     vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
>> +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>> +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>> +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>> +     vb2_q->dev = cap->isp->dev;
>> +     vb2_q->lock = &cap->lock;
>> +     vb2_q->min_queued_buffers = 2;
>> +
>> +     ret = vb2_queue_init(vb2_q);
>> +     if (ret < 0)
>> +             goto err_destroy;
>> +
>> +     cap->pad.flags = MEDIA_PAD_FL_SINK;
>> +     ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad);
>> +     if (ret < 0)
>> +             goto err_queue_release;
>> +
>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>> +     if (ret < 0) {
>> +             dev_err(cap->isp->dev,
>> +                     "Failed to register %s: %d\n", vdev->name, ret);
>> +             goto err_entity_cleanup;
>> +     }
>> +
>> +     return 0;
>> +
>> +err_entity_cleanup:
>> +     media_entity_cleanup(&vdev->entity);
>> +err_queue_release:
>> +     vb2_queue_release(vb2_q);
>> +err_destroy:
>> +     mutex_destroy(&cap->lock);
>> +     return ret;
>> +}
>> +
>> +int c3_isp_captures_register(struct c3_isp_device *isp)
>> +{
>> +     int ret;
>> +     unsigned int i;
>> +     struct c3_isp_capture *cap;
>> +
>> +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>> +             cap = &isp->caps[i];
>> +             memset(cap, 0, sizeof(*cap));
>> +
>> +             cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
>> +             cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
>> +             cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
>> +             cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
>> +             cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
>> +
>> +             c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
>> +
>> +             cap->id = i;
>> +             if (cap->id == C3_ISP_CAP_DEV_0)
>> +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_0];
>> +             else if (cap->id == C3_ISP_CAP_DEV_1)
>> +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_1];
>> +             else
>> +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_2];
> I think cap->rsz = &isp->resizers[i]; is fine personally, but it's up 
> to you


The  index "i" is C3_ISP_CAP_DEV_X, but the index of isp->resizers[] is 
C3_ISP_RSZ_X.

Can I use the index "i" in  isp->resizers[] ?

>> +
>> +             cap->isp = isp;
>> +             INIT_LIST_HEAD(&cap->pending);
>> +             spin_lock_init(&cap->buff_lock);
>> +             mutex_init(&cap->lock);
>> +
>> +             ret = c3_isp_register_capture(cap);
>> +             if (ret) {
>> +                     cap->isp = NULL;
>> +                     mutex_destroy(&cap->lock);
>> +                     c3_isp_captures_unregister(isp);
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +void c3_isp_captures_unregister(struct c3_isp_device *isp)
>> +{
>> +     unsigned int i;
>> +     struct c3_isp_capture *cap;
>> +
>> +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>> +             cap = &isp->caps[i];
>> +
>> +             if (!cap->isp)
>> +                     continue;
>> +             vb2_queue_release(&cap->vb2_q);
>> +             media_entity_cleanup(&cap->vdev.entity);
>> +             video_unregister_device(&cap->vdev);
>> +             mutex_destroy(&cap->lock);
>> +     }
>> +}
>> +
>> +void c3_isp_captures_done(struct c3_isp_device *isp)
>> +{
>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
>> +}
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h 
>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
>> new file mode 100644
>> index 000000000000..19f2a3bc29c9
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
>> @@ -0,0 +1,327 @@
>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#ifndef __C3_ISP_COMMON_H__
>> +#define __C3_ISP_COMMON_H__
>> +
>> +#include <linux/clk.h>
>> +
>> +#include <media/media-device.h>
>> +#include <media/videobuf2-core.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-subdev.h>
>> +#include <media/videobuf2-v4l2.h>
>> +
>> +#define C3_ISP_DRIVER_NAME            "c3-isp"
>> +#define C3_ISP_CLOCK_NUM_MAX          3
>> +
>> +#define C3_ISP_CORE_DEF_SINK_PAD_FMT MEDIA_BUS_FMT_SRGGB10_1X10
>> +#define C3_ISP_CORE_DEF_SRC_PAD_FMT   MEDIA_BUS_FMT_YUYV8_1_5X8
>> +#define C3_ISP_RSZ_DEF_PAD_FMT        MEDIA_BUS_FMT_YUYV8_1_5X8
>> +#define C3_ISP_DEFAULT_WIDTH          1920
>> +#define C3_ISP_DEFAULT_HEIGHT         1080
>> +#define C3_ISP_MAX_WIDTH              2888
>> +#define C3_ISP_MAX_HEIGHT             2240
>> +#define C3_ISP_MIN_WIDTH              160
>> +#define C3_ISP_MIN_HEIGHT             120
>> +
>> +#define C3_DISP_INTER                 0x400
>> +#define C3_DISP_REG(base, id)         ((base) + (id) * C3_DISP_INTER)
>> +#define C3_WRMIFX3_INTER              0x100
>> +#define C3_WRMIFX3_REG(base, id)      ((base) + (id) * 
>> C3_WRMIFX3_INTER)
>> +#define C3_PPS_TAP4_S11_H_NUM         33
>> +#define C3_PPS_LUT_CTYPE_0            0
>> +#define C3_PPS_LUT_CTYPE_2            2
>> +#define C3_SCALE_EN                   1
>> +#define C3_SCALE_DIS                  0
>> +
>> +#define C3_ISP_PHASE_OFFSET_0         0
>> +#define C3_ISP_PHASE_OFFSET_1         1
>> +#define C3_ISP_PHASE_OFFSET_NONE      0xff
>> +
>> +enum c3_isp_core_pads {
>> +     C3_ISP_CORE_PAD_SINK_VIDEO,
>> +     C3_ISP_CORE_PAD_SINK_PARAMS,
>> +     C3_ISP_CORE_PAD_SOURCE_STATS,
>> +     C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> +     C3_ISP_CORE_PAD_MAX
>> +};
>> +
>> +enum c3_isp_resizer_ids {
>> +     C3_ISP_RSZ_0,
>> +     C3_ISP_RSZ_1,
>> +     C3_ISP_RSZ_2,
>> +     C3_ISP_NUM_RSZ
>> +};
>> +
>> +enum c3_isp_resizer_pads {
>> +     C3_ISP_RESIZER_PAD_SINK,
>> +     C3_ISP_RESIZER_PAD_SOURCE,
>> +     C3_ISP_RESIZER_PAD_MAX
>> +};
>> +
>> +enum c3_isp_cap_devs {
>> +     C3_ISP_CAP_DEV_0,
>> +     C3_ISP_CAP_DEV_1,
>> +     C3_ISP_CAP_DEV_2,
>> +     C3_ISP_NUM_CAP_DEVS
>> +};
>> +
>> +/**
>> + * struct c3_isp_pps_io_size - isp scaler input and output size
>> + *
>> + * @thsize: input horizontal size of after preprocessing
>> + * @tvsize: input vertical size of after preprocessing
>> + * @ohsize: output horizontal size
>> + * @ovsize: output vertical size
>> + * @ihsize: input horizontal size
>> + * @max_hsize: maximum horizontal size
>> + */
>> +struct c3_isp_pps_io_size {
>> +     u32 thsize;
>> +     u32 tvsize;
>> +     u32 ohsize;
>> +     u32 ovsize;
>> +     u32 ihsize;
>> +     u32 max_hsize;
>> +};
>> +
>> +/**
>> + * @mbus_code: the mbus code
>> + * @pads: save the pad flag of this mbus_code
> "bitmask detailing valid pads for this mbus_code" perhaps?


OK, will do this.

>> + * @xofst: horizontal phase offset of hardware
>> + * @yofst: vertical phase offset of hardware
>> + */
>> +struct c3_isp_mbus_format_info {
>> +     u32 mbus_code;
>> +     u32 pads;
>> +     u8 xofst;
>> +     u8 yofst;
>> +};
>> +
>> +/**
>> + * @mbus_code: the mbus code
>> + * @fourcc: pixel format
>> + * @depth: pixel width
>> + */
>> +struct c3_isp_capture_format {
>> +     u32 mbus_code;
>> +     u32 fourcc;
>> +     u8 depth;
>> +};
>> +
>> +/**
>> + * struct c3_isp_vb2_buffer - A container of vb2 buffer
>> + *
>> + * @vb: vb2 buffer
>> + * @vaddr: buffer virtual address
>> + * @paddr: buffer physical address
>> + * @list: entry of the buffer in the queue
>> + */
>> +struct c3_isp_vb2_buffer {
>> +     struct vb2_v4l2_buffer vb;
>> +     void *vaddr;
>> +     dma_addr_t paddr;
>> +     struct list_head list;
>> +};
>> +
>> +/**
>> + * struct c3_isp_core - ISP core subdev
>> + *
>> + * @sd: ISP sub-device
>> + * @pads: ISP sub-device pads
>> + * @src_sd: source sub-device
>> + * @isp: pointer to c3_isp_device
>> + * @src_sd_pad: source sub-device pad
>> + */
>> +struct c3_isp_core {
>> +     struct v4l2_subdev sd;
>> +     struct media_pad pads[C3_ISP_CORE_PAD_MAX];
>> +     struct v4l2_subdev *src_sd;
>> +     u16 src_sd_pad;
>> +     struct c3_isp_device *isp;
>> +};
>> +
>> +/**
>> + * struct c3_isp_resizer - ISP resizer subdev
>> + *
>> + * @id: resizer id
>> + * @sd: resizer sub-device
>> + * @pads: resizer sub-device pads
>> + * @isp: pointer to c3_isp_device
>> + * @cap: pointer to c3_isp_capture
>> + */
>> +struct c3_isp_resizer {
>> +     enum c3_isp_resizer_ids id;
>> +     struct v4l2_subdev sd;
>> +     struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
>> +     struct c3_isp_device *isp;
>> +     struct c3_isp_capture *cap;
>> +};
>> +
>> +/**
>> + * struct c3_isp_stats - ISP statistics device
>> + *
>> + * @vb2_q: vb2 buffer queue
>> + * @vdev: video node
>> + * @vfmt: v4l2_format of the metadata format
>> + * @pad: media pad
>> + * @lock: protects vb2_q, vdev
>> + * @is_streaming: stats status
>> + * @isp: pointer to c3_isp_device
>> + * @buff: in use buffer
>> + * @buff_lock: protects stats buffer
>> + * @pending: stats buffer list head
>> + */
>> +struct c3_isp_stats {
>> +     struct vb2_queue vb2_q;
>> +     struct video_device vdev;
>> +     struct v4l2_format vfmt;
>> +     struct media_pad pad;
>> +
>> +     struct mutex lock; /* Protects vb2_q, vdev */
>> +     bool is_streaming;
>> +     struct c3_isp_device *isp;
>> +
>> +     struct c3_isp_vb2_buffer *buff;
>> +     spinlock_t buff_lock; /* Protects stream buffer */
>> +     struct list_head pending;
>> +};
>> +
>> +/**
>> + * struct c3_isp_params - ISP parameters device
>> + *
>> + * @vb2_q: vb2 buffer queue
>> + * @vdev: video node
>> + * @vfmt: v4l2_format of the metadata format
>> + * @pad: media pad
>> + * @lock: protects vb2_q, vdev
>> + * @isp: pointer to c3_isp_device
>> + * @buff: in use buffer
>> + * @buff_lock: protects stats buffer
>> + * @pending: stats buffer list head
>> + */
>> +struct c3_isp_params {
>> +     struct vb2_queue vb2_q;
>> +     struct video_device vdev;
>> +     struct v4l2_format vfmt;
>> +     struct media_pad pad;
>> +
>> +     struct mutex lock; /* Protects vb2_q, vdev */
>> +     struct c3_isp_device *isp;
>> +
>> +     struct c3_isp_vb2_buffer *buff;
>> +     spinlock_t buff_lock; /* Protects stream buffer */
>> +     struct list_head pending;
>> +};
>> +
>> +/**
>> + * struct c3_isp_capture - ISP capture device
>> + *
>> + * @id: capture device ID
>> + * @vb2_q: vb2 buffer queue
>> + * @vdev: video node
>> + * @vfmt: v4l2_format of the capture format
>> + * @pad: media pad
>> + * @lock: protects vb2_q, vdev
>> + * @is_streaming: capture device status
>> + * @isp: pointer to c3_isp_device
>> + * @rsz: pointer to c3_isp_resizer
>> + * @buff: in use buffer
>> + * @buff_lock: protects capture buffer
>> + * @pending: capture buffer list head
>> + */
>> +struct c3_isp_capture {
>> +     enum c3_isp_cap_devs id;
>> +     struct vb2_queue vb2_q;
>> +     struct video_device vdev;
>> +     struct v4l2_format vfmt;
>> +     struct media_pad pad;
>> +
>> +     struct mutex lock; /* Protects vb2_q, vdev */
>> +     bool is_streaming;
>> +     struct c3_isp_device *isp;
>> +     struct c3_isp_resizer *rsz;
>> +
>> +     struct c3_isp_vb2_buffer *buff;
>> +     spinlock_t buff_lock; /* Protects stream buffer */
>> +     struct list_head pending;
>> +};
>> +
>> +/**
>> + * struct c3_isp_info - ISP information
>> + *
>> + * @clocks: array of ISP clock names
>> + * @clock_rates: array of ISP clock rate
>> + * @clock_num: actual clock number
>> + */
>> +struct c3_isp_info {
>> +     char *clocks[C3_ISP_CLOCK_NUM_MAX];
>> +     u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
>> +     u32 clock_num;
>> +};
>> +
>> +/**
>> + * struct c3_isp_device - ISP platform device
>> + *
>> + * @dev: pointer to the struct device
>> + * @base: base register address
>> + * @clks: array of clocks
>> + * @notifier: notifier to register on the v4l2-async API
>> + * @v4l2_dev: v4l2_device variable
>> + * @media_dev: media device variable
>> + * @pipe: media pipeline
>> + * @core: ISP core subdev
>> + * @resizer: ISP resizer subdev
>> + * @stats: ISP stats device
>> + * @params: ISP params device
>> + * @caps: array of ISP capture device
>> + * @frm_sequence: used to record frame id
>> + * @lock: protect ISP device
>> + * @info: version-specific ISP information
>> + */
>> +struct c3_isp_device {
>> +     struct device *dev;
>> +     void __iomem *base;
>> +     struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
>> +
>> +     struct v4l2_async_notifier notifier;
>> +     struct v4l2_device v4l2_dev;
>> +     struct media_device media_dev;
>> +     struct media_pipeline pipe;
>> +
>> +     struct c3_isp_core core;
>> +     struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
>> +     struct c3_isp_stats stats;
>> +     struct c3_isp_params params;
>> +     struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
>> +
>> +     u32 frm_sequence;
>> +     struct mutex lock; /* Protect ISP device */
>> +     const struct c3_isp_info *info;
>> +};
>> +
>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 
>> mask, u32 val);
>> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp);
>> +
>> +int c3_isp_core_register(struct c3_isp_device *isp);
>> +void c3_isp_core_unregister(struct c3_isp_device *isp);
>> +int c3_isp_resizers_register(struct c3_isp_device *isp);
>> +void c3_isp_resizers_unregister(struct c3_isp_device *isp);
>> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz);
>> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz);
>> +int c3_isp_captures_register(struct c3_isp_device *isp);
>> +void c3_isp_captures_unregister(struct c3_isp_device *isp);
>> +void c3_isp_captures_done(struct c3_isp_device *isp);
>> +int c3_isp_stats_register(struct c3_isp_device *isp);
>> +void c3_isp_stats_unregister(struct c3_isp_device *isp);
>> +int c3_isp_stats_done(struct c3_isp_device *isp);
>> +int c3_isp_params_register(struct c3_isp_device *isp);
>> +void c3_isp_params_unregister(struct c3_isp_device *isp);
>> +int c3_isp_params_done(struct c3_isp_device *isp);
>> +
>> +#endif
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c 
>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
>> new file mode 100644
>> index 000000000000..d3672aff9fd2
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
>> @@ -0,0 +1,675 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/pm_runtime.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +#include "include/uapi/c3-isp-config.h"
>> +
>> +#define C3_ISP_CORE_SUBDEV_NAME        "isp-core"
>> +
>> +static const struct c3_isp_mbus_format_info 
>> c3_isp_core_mbus_formats[] = {
>> +     /* RAW formats */
>> +     {
>> +             .mbus_code      = MEDIA_BUS_FMT_SBGGR10_1X10,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SGBRG10_1X10,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SGRBG10_1X10,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SRGGB10_1X10,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SBGGR12_1X12,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SGBRG12_1X12,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SGRBG12_1X12,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_SRGGB12_1X12,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>> +     },
>> +     /* YUV formats */
>> +     {
>> +             .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_1_5X8,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_2X8,
>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +     },
>> +};
>> +
>> +static const struct c3_isp_mbus_format_info
>> +*core_find_format_by_code(u32 code, u32 pad)
>> +{
>> +     int i;
>> +
>> +     for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>> +             const struct c3_isp_mbus_format_info *info =
>> +                     &c3_isp_core_mbus_formats[i];
>> +
>> +             if (info->mbus_code == code && info->pads & BIT(pad))
>> +                     return info;
>> +     }
>> +
>> +     return NULL;
>> +}
>> +
>> +static const struct c3_isp_mbus_format_info
>> +*core_find_format_by_index(u32 index, u32 pad)
>> +{
>> +     int i;
>> +
>> +     for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>> +             const struct c3_isp_mbus_format_info *info =
>> +                     &c3_isp_core_mbus_formats[i];
>> +
>> +             if (!(info->pads & BIT(pad)))
>> +                     continue;
>> +
>> +             if (!index)
>> +                     return info;
>> +
>> +             index--;
>> +     }
>> +
>> +     return NULL;
>> +}
>> +
>> +static void c3_isp_core_enable(struct c3_isp_device *isp)
>> +{
>> +     /* Select the line sync signal */
>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_LINE_THRD,
>> +                        TOP_IRQ_DIN_HSYNC, TOP_IRQ_DIN_HSYNC);
>> +
>> +     /* Enable frame done and stats error irq */
>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
>> +                        TOP_IRQ_FRAME_DONE, TOP_IRQ_FRAME_DONE);
>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
>> +                        TOP_IRQ_STATS_ERR, TOP_IRQ_STATS_ERR);
>> +
>> +     /* Enable image data to ISP core */
>> +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
>> +                        TOP_DATA_PATH_MASK, TOP_PATH_MIPI_TO_CORE);
>> +}
>> +
>> +static void c3_isp_core_disable(struct c3_isp_device *isp)
>> +{
>> +     /* Disable image data to ISP core */
>> +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
>> +                        TOP_DATA_PATH_MASK, 0x0);
>> +
>> +     /* Disable all irq */
>> +     c3_isp_write(isp, ISP_TOP_IRQ_EN, 0x0);
>> +}
>> +
>> +/* Set the phase offset of blc, wb and lns */
>> +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
>> +                               u8 xofst, u8 yofst)
>> +{
>> +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
>> +                        LSWB_BLC_XPHS_OFST_MASK,
>> +                        xofst << LSWB_BLC_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
>> +                        LSWB_BLC_YPHS_OFST_MASK, yofst);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
>> +                        LSWB_WB_XPHS_OFST_MASK,
>> +                        xofst << LSWB_WB_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
>> +                        LSWB_WB_YPHS_OFST_MASK, yofst);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
>> +                        LSWB_LNS_XPHS_OFST_MASK,
>> +                        xofst << LSWB_LNS_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
>> +                        LSWB_LNS_YPHS_OFST_MASK, yofst);
>> +}
>> +
>> +/* Set the phase offset of af, ae and awb */
>> +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
>> +                             u8 xofst, u8 yofst)
>> +{
>> +     c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_XPHS_OFST_MASK,
>> +                        xofst << AF_CTRL_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_YPHS_OFST_MASK,
>> +                        yofst << AF_CTRL_YPHS_OFST_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_XPHS_OFST_MASK,
>> +                        xofst << AE_CTRL_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_YPHS_OFST_MASK,
>> +                        yofst << AE_CTRL_YPHS_OFST_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_XPHS_OFST_MASK,
>> +                        xofst << AWB_CTRL_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_YPHS_OFST_MASK, 
>> yofst);
>> +}
>> +
>> +/* Set the phase offset of demosaic */
>> +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
>> +                              u8 xofst, u8 yofst)
>> +{
>> +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, 
>> DMS_COMMON_XPHS_OFST_MASK,
>> +                        xofst << DMS_COMMON_XPHS_OFST_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, 
>> DMS_COMMON_YPHS_OFST_MASK, yofst);
>> +}
>> +
>> +static void c3_isp_core_cfg_ofst(struct c3_isp_device *isp,
>> +                              struct v4l2_mbus_framefmt *fmt)
>> +{
>> +     const struct c3_isp_mbus_format_info *isp_fmt =
>> +                     core_find_format_by_code(fmt->code, 
>> C3_ISP_CORE_PAD_SINK_VIDEO);
>> +
>> +     c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>> +     c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>> +     c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>> +}
>> +
>> +/* Set format of the hardware control module */
>> +static void c3_isp_core_top_fmt(struct c3_isp_device *isp,
>> +                             struct v4l2_mbus_framefmt *fmt)
>> +{
>> +     c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
>> +                  TOP_INPUT_HSIZE(fmt->width) | 
>> TOP_INPUT_VSIZE(fmt->height));
>> +
>> +     c3_isp_write(isp, ISP_TOP_FRM_SIZE,
>> +                  TOP_FRM_CORE_IHSIZE(fmt->width) | 
>> TOP_FRM_CORE_IVSIZE(fmt->height));
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE, TOP_HOLD_HSIZE_MASK,
>> +                        fmt->width << TOP_HOLD_HSIZE_SHIFT);
>> +}
>> +
>> +static void c3_isp_core_af_fmt(struct c3_isp_device *isp,
>> +                            struct v4l2_mbus_framefmt *fmt)
>> +{
>> +     u32 hidx;
>> +     u32 vidx;
>> +     int i;
>> +
>> +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, 
>> AF_HV_STAT_HBLK_NUM_MASK,
>> +                        AF_STAT_BLKH_NUM << AF_HV_STAT_HBLK_NUM_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM,
>> +                        AF_HV_STAT_VBLK_NUM_MASK, AF_STAT_BLKV_NUM);
>> +
>> +     c3_isp_write(isp, ISP_AF_HV_SIZE,
>> +                  AF_HV_STAT_HSIZE(fmt->width) | 
>> AF_HV_STAT_VSIZE(fmt->height));
>> +
>> +     /* Set the index address to 0 position */
>> +     c3_isp_write(isp, ISP_AF_IDX_ADDR, 0);
> blank line please


Will add a blank line.

>> +     /*
>> +      * Calculate and set the coordinates of points in the grid.
>> +      * hidx and vidx need to be aligned with 2.
>> +      */
>> +     for (i = 0; i <= AF_STAT_BLKH_NUM; i++) {
>> +             hidx = i * fmt->width / AF_STAT_BLKH_NUM;
>> +             hidx = ALIGN_DOWN(hidx, 2);
>> +
>> +             vidx = i * fmt->height / AF_STAT_BLKV_NUM;
>> +             vidx = min(vidx, fmt->height);
>> +             vidx = ALIGN_DOWN(vidx, 2);
>> +             c3_isp_write(isp, ISP_AF_IDX_DATA,
>> +                          AF_IDX_HIDX_DATA(hidx) | 
>> AF_IDX_VIDX_DATA(vidx));
>> +     }
>> +}
>> +
>> +static void c3_isp_core_ae_fmt(struct c3_isp_device *isp,
>> +                            struct v4l2_mbus_framefmt *fmt)
>> +{
>> +     u32 hidx;
>> +     u32 vidx;
>> +     int i;
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, 
>> AE_HV_STAT_HBLK_NUM_MASK,
>> +                        AE_STAT_BLKH_NUM << AE_HV_STAT_HBLK_NUM_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM,
>> +                        AE_HV_STAT_VBLK_NUM_MASK, AE_STAT_BLKV_NUM);
>> +
>> +     c3_isp_write(isp, ISP_AE_HV_SIZE,
>> +                  AE_HV_STAT_HSIZE(fmt->width) | 
>> AE_HV_STAT_VSIZE(fmt->height));
>> +
>> +     /* Set the index address to 0 position */
>> +     c3_isp_write(isp, ISP_AE_IDX_ADDR, 0);
>> +     /*
>> +      * Calculate and set the coordinates of points in the grid.
>> +      * hidx and vidx need to be aligned with 2.
>> +      */
>> +     for (i = 0; i <= AE_STAT_BLKH_NUM; i++) {
>> +             hidx = i * fmt->width / AE_STAT_BLKH_NUM;
>> +             hidx = ALIGN_DOWN(hidx, 2);
>> +
>> +             vidx = i * fmt->height / AE_STAT_BLKV_NUM;
>> +             vidx = min(vidx, fmt->height);
>> +             vidx = ALIGN_DOWN(vidx, 2);
>> +
>> +             c3_isp_write(isp, ISP_AE_IDX_DATA,
>> +                          AE_IDX_HIDX_DATA(hidx) | 
>> AE_IDX_VIDX_DATA(vidx));
>> +     }
>> +}
>> +
>> +static void c3_isp_core_awb_fmt(struct c3_isp_device *isp,
>> +                             struct v4l2_mbus_framefmt *fmt)
>> +{
>> +     u32 hidx;
>> +     u32 vidx;
>> +     int i;
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, 
>> AWB_HV_STAT_HBLK_NUM_MASK,
>> +                        AWB_STAT_BLKH_NUM << 
>> AWB_HV_STAT_HBLK_NUM_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM,
>> +                        AWB_HV_STAT_VBLK_NUM_MASK, AWB_STAT_BLKV_NUM);
>> +
>> +     c3_isp_write(isp, ISP_AWB_HV_SIZE,
>> +                  AWB_HV_STAT_HSIZE(fmt->width) | 
>> AWB_HV_STAT_VSIZE(fmt->height));
>> +
>> +     /* Set the index address to 0 position */
>> +     c3_isp_write(isp, ISP_AWB_IDX_ADDR, 0);
>> +     /*
>> +      * Calculate and set the coordinates of points in the grid.
>> +      * hidx and vidx need to be aligned with 2.
>> +      */
>> +     for (i = 0; i <= AWB_STAT_BLKH_NUM; i++) {
>> +             hidx = i * fmt->width / AWB_STAT_BLKH_NUM;
>> +             hidx = ALIGN_DOWN(hidx, 2);
>> +
>> +             vidx = i * fmt->height / AWB_STAT_BLKV_NUM;
>> +             vidx = min(vidx, fmt->height);
>> +             vidx = ALIGN_DOWN(vidx, 2);
>> +
>> +             c3_isp_write(isp, ISP_AWB_IDX_DATA,
>> +                          AWB_IDX_HIDX_DATA(hidx) | 
>> AWB_IDX_VIDX_DATA(vidx));
>> +     }
>> +}
>> +
>> +static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
>> +                                struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +
>> +     fmt = v4l2_subdev_state_get_format(state, 
>> C3_ISP_CORE_PAD_SINK_VIDEO);
>> +
>> +     c3_isp_core_cfg_ofst(isp, fmt);
>> +     c3_isp_core_top_fmt(isp, fmt);
>> +     c3_isp_core_af_fmt(isp, fmt);
>> +     c3_isp_core_ae_fmt(isp, fmt);
>> +     c3_isp_core_awb_fmt(isp, fmt);
>> +}
>> +
>> +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
>> +                                   struct v4l2_subdev_state *state,
>> +                                   u32 pad, u64 streams_mask)
>> +{
>> +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     core->isp->frm_sequence = 0;
>> +     c3_isp_core_cfg_format(core->isp, state);
>> +     c3_isp_core_enable(core->isp);
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> + C3_ISP_CORE_PAD_SINK_VIDEO,
>> + &streams_mask);
>> +     ret = v4l2_subdev_enable_streams(core->src_sd,
>> +                                      core->src_sd_pad, sink_streams);
>> +     if (ret) {
>> +             c3_isp_core_disable(core->isp);
>> +             return ret;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
>> +                                    struct v4l2_subdev_state *state,
>> +                                    u32 pad, u64 streams_mask)
>> +{
>> +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>> +     u64 sink_streams;
>> +     int ret;
>> +
>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> + C3_ISP_CORE_PAD_SINK_VIDEO,
>> + &streams_mask);
>> +     ret = v4l2_subdev_disable_streams(core->src_sd,
>> +                                       core->src_sd_pad, sink_streams);
>> +     if (ret)
>> +             return ret;
>> +
>> +     c3_isp_core_disable(core->isp);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state,
>> +                                struct v4l2_subdev_krouting *routing)
>> +{
>> +     static const struct v4l2_mbus_framefmt format = {
>> +             .width = C3_ISP_DEFAULT_WIDTH,
>> +             .height = C3_ISP_DEFAULT_HEIGHT,
>> +             .code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
>> +             .field = V4L2_FIELD_NONE,
>> +             .colorspace = V4L2_COLORSPACE_SRGB,
>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>> +             .xfer_func = V4L2_XFER_FUNC_SRGB,
>> +     };
>> +     int ret;
>> +
>> +     ret = v4l2_subdev_routing_validate(sd, routing,
>> + V4L2_SUBDEV_ROUTING_NO_SOURCE_STREAM_MIX);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, 
>> &format);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
>> +                                 struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_subdev_route routes[2];
>> +     struct v4l2_subdev_krouting routing;
>> +
>> +     routes[0].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
>> +     routes[0].sink_stream = 0;
>> +     routes[0].source_pad = C3_ISP_CORE_PAD_SOURCE_STATS;
>> +     routes[0].source_stream = 0;
>> +     routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> This doesn't look right - I wouldn't expect a route between the video 
> input and the sink. I wouldn't
> really expect routes at all for this subdevice to be honest


Will remove this route.

>> +
>> +     routes[1].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
>> +     routes[1].sink_stream = 0;
>> +     routes[1].source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
>> +     routes[1].source_stream = 0;
>> +     routes[1].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>> +
>> +     routing.num_routes = ARRAY_SIZE(routes);
>> +     routing.routes = routes;
>> +
>> +     return c3_isp_core_cfg_routing(sd, state, &routing);
>> +}
>> +
>> +static int c3_isp_core_set_routing(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state,
>> +                                enum v4l2_subdev_format_whence which,
>> +                                struct v4l2_subdev_krouting *routing)
>> +{
>> +     bool is_streaming = v4l2_subdev_is_streaming(sd);
>> +
>> +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>> +             return -EBUSY;
>> +
>> +     return c3_isp_core_cfg_routing(sd, state, routing);
>> +}
>> +
>> +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
>> +                                   struct v4l2_subdev_state *state,
>> +                                   struct v4l2_subdev_mbus_code_enum 
>> *code)
>> +{
>> +     const struct c3_isp_mbus_format_info *info;
>> +     int ret = 0;
>> +
>> +     switch (code->pad) {
>> +     case C3_ISP_CORE_PAD_SINK_VIDEO:
>> +     case C3_ISP_CORE_PAD_SOURCE_VIDEO:
>> +             info = core_find_format_by_index(code->index, code->pad);
>> +             if (!info)
>> +                     ret = -EINVAL;
>> +             else
>> +                     code->code = info->mbus_code;
>> +
>> +             break;
>> +     case C3_ISP_CORE_PAD_SINK_PARAMS:
>> +     case C3_ISP_CORE_PAD_SOURCE_STATS:
>> +             if (code->index)
>> +                     ret = -EINVAL;
>> +             else
>> +                     code->code = MEDIA_BUS_FMT_METADATA_FIXED;
>> +
>> +             break;
>> +     default:
>> +             ret = -EINVAL;
>> +             break;
>> +     }
>> +
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_core_set_sink_fmt(struct v4l2_subdev_state *state,
>> +                                  struct v4l2_subdev_format *format)
>> +{
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +
>> +     isp_fmt = core_find_format_by_code(format->format.code, 
>> format->pad);
>> +     if (!isp_fmt)
>> +             sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>> +     else
>> +             sink_fmt->code = format->format.code;
>> +
>> +     sink_fmt->width = clamp_t(u32, format->format.width,
>> +                               C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>> +     sink_fmt->height = clamp_t(u32, format->format.height,
>> +                                C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>> +
>> +     format->format = *sink_fmt;
>> +}
>> +
>> +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
>> +                                    struct v4l2_subdev_format *format)
>> +{
>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, 
>> C3_ISP_CORE_PAD_SINK_VIDEO);
>> +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +
>> +     isp_fmt = core_find_format_by_code(format->format.code, 
>> format->pad);
>> +     if (!isp_fmt)
>> +             src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>> +     else
>> +             src_fmt->code = format->format.code;
>> +
>> +     /* The source size must be same with the sink size. */
>> +     src_fmt->width  = sink_fmt->width;
>> +     src_fmt->height = sink_fmt->height;
> The source size should be propagated when the sink format is set then 
> rather than picking it up on a
> subsequent .set_fmt() call for the source pad


Will set the source size when the sink format is set.

>> +
>> +     format->format = *src_fmt;
>> +}
>> +
>> +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
>> +                            struct v4l2_subdev_state *state,
>> +                            struct v4l2_subdev_format *format)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +
>> +     switch (format->pad) {
>> +     case C3_ISP_CORE_PAD_SINK_VIDEO:
>> +             c3_isp_core_set_sink_fmt(state, format);
>> +             break;
>> +     case C3_ISP_CORE_PAD_SINK_PARAMS:
>> +     case C3_ISP_CORE_PAD_SOURCE_STATS:
>> +             fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +             format->format = *fmt;
>> +             break;
>> +     case C3_ISP_CORE_PAD_SOURCE_VIDEO:
>> +             c3_isp_core_set_source_fmt(state, format);
>> +             break;
>> +     default:
>> +             dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
>> +             return -ENOTTY;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_core_init_state(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
>> +
>> +     /* Video sink pad */
>> +     sink_fmt = v4l2_subdev_state_get_format(state, 
>> C3_ISP_CORE_PAD_SINK_VIDEO);
>> +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>> +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>> +     sink_fmt->field = V4L2_FIELD_NONE;
>> +     sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>> +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>> +     sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
>> +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>> +     sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
>> +
>> +     /* Video source pad */
>> +     src_fmt = v4l2_subdev_state_get_format(state, 
>> C3_ISP_CORE_PAD_SOURCE_VIDEO);
>> +     src_fmt->width = C3_ISP_DEFAULT_WIDTH;
>> +     src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>> +     src_fmt->field = V4L2_FIELD_NONE;
>> +     src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>> +     src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>> +     src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>> +     src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>> +     src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>> +
>> +     /* Parameters pad */
>> +     sink_fmt = v4l2_subdev_state_get_format(state, 
>> C3_ISP_CORE_PAD_SINK_PARAMS);
>> +     sink_fmt->width = 0;
>> +     sink_fmt->height = 0;
>> +     sink_fmt->field = V4L2_FIELD_NONE;
>> +     sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>> +
>> +     /* Statistics pad */
>> +     src_fmt = v4l2_subdev_state_get_format(state, 
>> C3_ISP_CORE_PAD_SOURCE_STATS);
>> +     src_fmt->width = 0;
>> +     src_fmt->height = 0;
>> +     src_fmt->field = V4L2_FIELD_NONE;
>> +     src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>> +
>> +     return c3_isp_core_init_routing(sd, state);
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
>> +     .enum_mbus_code = c3_isp_core_enum_mbus_code,
>> +     .get_fmt = v4l2_subdev_get_fmt,
>> +     .set_fmt = c3_isp_core_set_fmt,
>> +     .enable_streams = c3_isp_core_enable_streams,
>> +     .disable_streams = c3_isp_core_disable_streams,
>> +     .set_routing = c3_isp_core_set_routing,
>> +};
>> +
>> +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
>> +     .pad = &c3_isp_core_pad_ops,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops 
>> c3_isp_core_internal_ops = {
>> +     .init_state = c3_isp_core_init_state,
>> +};
>> +
>> +static int c3_isp_core_link_validate(struct media_link *link)
>> +{
>> +     if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
>> +             return 0;
>> +
>> +     return v4l2_subdev_link_validate(link);
>> +}
>> +
>> +/* Media entity operations */
>> +static const struct media_entity_operations c3_isp_core_entity_ops = {
>> +     .link_validate = c3_isp_core_link_validate,
>> +};
>> +
>> +int c3_isp_core_register(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_core *core = &isp->core;
>> +     struct v4l2_subdev *sd = &core->sd;
>> +     int ret;
>> +
>> +     v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
>> +     sd->owner = THIS_MODULE;
>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +     sd->internal_ops = &c3_isp_core_internal_ops;
>> +     snprintf(sd->name, sizeof(sd->name), "%s", 
>> C3_ISP_CORE_SUBDEV_NAME);
>> +
>> +     sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
>> +     sd->entity.ops = &c3_isp_core_entity_ops;
>> +
>> +     core->isp = isp;
>> +     sd->dev = isp->dev;
>> +     v4l2_set_subdevdata(sd, core);
>> +
>> +     core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
>> +     core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
>> +     core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = 
>> MEDIA_PAD_FL_SOURCE;
>> +     core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = 
>> MEDIA_PAD_FL_SOURCE;
>> +     ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, 
>> core->pads);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_init_finalize(sd);
>> +     if (ret)
>> +             goto err_entity_cleanup;
>> +
>> +     ret = v4l2_device_register_subdev(&isp->v4l2_dev, sd);
>> +     if (ret)
>> +             goto err_subdev_cleanup;
>> +
>> +     return 0;
>> +
>> +err_subdev_cleanup:
>> +     v4l2_subdev_cleanup(sd);
>> +err_entity_cleanup:
>> +     media_entity_cleanup(&sd->entity);
>> +     return ret;
>> +}
>> +
>> +void c3_isp_core_unregister(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_core *core = &isp->core;
>> +     struct v4l2_subdev *sd = &core->sd;
>> +
>> +     v4l2_device_unregister_subdev(sd);
>> +     v4l2_subdev_cleanup(sd);
>> +     media_entity_cleanup(&sd->entity);
>> +}
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c 
>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
>> new file mode 100644
>> index 000000000000..a57b9f8dbc3c
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
>> @@ -0,0 +1,486 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/device.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-common.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-fwnode.h>
>> +#include <media/v4l2-mc.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +
>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
>> +{
>> +     return readl(isp->base + reg);
>> +}
>> +
>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
>> +{
>> +     writel(val, isp->base + reg);
>> +}
>> +
>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 
>> mask, u32 val)
>> +{
>> +     u32 orig, tmp;
>> +
>> +     orig = c3_isp_read(isp, reg);
>> +
>> +     tmp = orig & ~mask;
>> +     tmp |= val & mask;
>> +
>> +     if (tmp != orig)
>> +             c3_isp_write(isp, reg, tmp);
>> +}
>> +
>> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp)
>> +{
>> +     struct media_pipeline_entity_iter iter;
>> +     unsigned int n_video_devices = 0;
>> +     struct media_entity *entity;
>> +     int ret;
>> +
>> +     ret = media_pipeline_entity_iter_init(&isp->pipe, &iter);
>> +     if (ret)
>> +             return ret;
>> +
>> +     media_pipeline_for_each_entity(&isp->pipe, &iter, entity) {
>> +             if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
>> +                     n_video_devices++;
>> +     }
>> +
>> +     media_pipeline_entity_iter_cleanup(&iter);
>> +
>> +     return n_video_devices == isp->pipe.start_count;
>> +}
>> +
>> +/* PM runtime suspend */
>> +static int __maybe_unused c3_isp_runtime_suspend(struct device *dev)
>> +{
>> +     struct c3_isp_device *isp = dev_get_drvdata(dev);
>> +
>> +     clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
>> +
>> +     return 0;
>> +}
>> +
>> +/* PM runtime resume */
>> +static int __maybe_unused c3_isp_runtime_resume(struct device *dev)
>> +{
>> +     struct c3_isp_device *isp = dev_get_drvdata(dev);
>> +
>> +     return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
>> +}
>> +
>> +static const struct dev_pm_ops c3_isp_pm_ops = {
>> +     SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>> +                             pm_runtime_force_resume)
>> +     SET_RUNTIME_PM_OPS(c3_isp_runtime_suspend,
>> +                        c3_isp_runtime_resume, NULL)
>> +};
>> +
>> +/* IRQ handling */
>> +static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
>> +{
>> +     struct c3_isp_device *isp = dev;
>> +     u32 status;
>> +
>> +     /* Get irq status and clear irq status */
>> +     status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
>> +     c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
>> +
>> +     if (status & TOP_IRQ_FRAME_DONE) {
>> +             c3_isp_stats_done(isp);
>> +             c3_isp_params_done(isp);
>> +             c3_isp_captures_done(isp);
>> +             isp->frm_sequence++;
>> +     }
>> +
>> +     if (status & TOP_IRQ_STATS_ERR)
>> +             dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
>> +
>> +     return IRQ_HANDLED;
>> +}
>> +
>> +/* Subdev notifier register */
>> +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
>> +                            struct v4l2_subdev *sd,
>> +                            struct v4l2_async_connection *asc)
>> +{
>> +     struct c3_isp_device *isp =
>> +             container_of(notifier, struct c3_isp_device, notifier);
>> +     struct c3_isp_core *core = &isp->core;
>> +     struct media_pad *sink = 
>> &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
>> +     int ret;
>> +
>> +     ret = media_entity_get_fwnode_pad(&sd->entity,
>> +                                       sd->fwnode, 
>> MEDIA_PAD_FL_SOURCE);
>> +     if (ret < 0) {
>> +             dev_err(isp->dev, "Failed to find pad for %s\n", 
>> sd->name);
>> +             return ret;
>> +     }
>> +
>> +     core->src_sd = sd;
>> +     core->src_sd_pad = ret;
>> +
>> +     return v4l2_create_fwnode_links_to_pad(sd, sink, 
>> MEDIA_LNK_FL_ENABLED |
>> + MEDIA_LNK_FL_IMMUTABLE);
>> +}
>> +
>> +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
>> +{
>> +     struct c3_isp_device *isp =
>> +             container_of(notifier, struct c3_isp_device, notifier);
>> +     int ret;
>> +
>> +     ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
>> +     if (ret < 0) {
>> +             dev_err(isp->dev,
>> +                     "Failed to register subdev nodes: %d\n", ret);
>> +             return ret;
>> +     }
>> +
>> +     dev_info(isp->dev, "notify complete\n");
>> +
>> +     return media_device_register(&isp->media_dev);
>> +}
>> +
>> +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
>> +{
>> +     struct c3_isp_device *isp =
>> +             container_of(asc->notifier, struct c3_isp_device, 
>> notifier);
>> +
>> +     media_device_unregister(&isp->media_dev);
>> +}
>> +
>> +static const struct v4l2_async_notifier_operations c3_isp_notify_ops 
>> = {
>> +     .bound = c3_isp_notify_bound,
>> +     .complete = c3_isp_notify_complete,
>> +     .destroy = c3_isp_notify_destroy,
>> +};
>> +
>> +static int c3_isp_async_nf_register(struct c3_isp_device *isp)
>> +{
>> +     struct v4l2_async_connection *asc;
>> +     struct fwnode_handle *ep;
>> +     int ret;
>> +
>> +     v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
>> +
>> +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
>> + FWNODE_GRAPH_ENDPOINT_NEXT);
>> +     if (!ep)
>> +             return -ENOTCONN;
>> +
>> +     asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
>> +                                           struct 
>> v4l2_async_connection);
>> +     if (IS_ERR(asc)) {
>> +             fwnode_handle_put(ep);
>> +             return PTR_ERR(asc);
>> +     }
>> +
>> +     fwnode_handle_put(ep);
>
>
> Move this above if (IS_ERR(asc)) and you don't need to call it in two 
> places
>

Will move this above "if (IS_ERR(asc))".

>> +
>> +     isp->notifier.ops = &c3_isp_notify_ops;
>> +     ret = v4l2_async_nf_register(&isp->notifier);
>> +     if (ret)
>> +             v4l2_async_nf_cleanup(&isp->notifier);
>> +
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
>> +{
>> +     v4l2_async_nf_unregister(&isp->notifier);
>> +     v4l2_async_nf_cleanup(&isp->notifier);
>> +}
>> +
>> +static int c3_isp_v4l2_register(struct c3_isp_device *isp)
>> +{
>> +     struct media_device *media_dev = &isp->media_dev;
>> +     struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
>> +     int ret;
>> +
>> +     /* Initialize media device */
>> +     strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
>> +             sizeof(media_dev->model));
>> +     media_dev->dev = isp->dev;
>> +
>> +     media_device_init(media_dev);
>> +
>> +     /* Initialize v4l2 device */
>> +     v4l2_dev->mdev = media_dev;
>> +     strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
>> +             sizeof(v4l2_dev->name));
>> +
>> +     ret = v4l2_device_register(isp->dev, v4l2_dev);
>> +     if (ret) {
>> +             media_device_cleanup(media_dev);
>> +             dev_err(isp->dev,
>> +                     "Failed to register V4L2 device: %d\n", ret);
>> +     }
>> +
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
>> +{
>> +     v4l2_device_unregister(&isp->v4l2_dev);
>> +     media_device_cleanup(&isp->media_dev);
>> +}
>> +
>> +static void c3_isp_remove_links(struct c3_isp_device *isp)
>> +{
>> +     unsigned int i;
>> +
>> +     media_entity_remove_links(&isp->core.sd.entity);
>> +
>> +     for (i = 0; i < C3_ISP_NUM_RSZ; i++)
>> + media_entity_remove_links(&isp->resizers[i].sd.entity);
>> +
>> +     for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
>> + media_entity_remove_links(&isp->caps[i].vdev.entity);
>> +}
>> +
>> +static int c3_isp_create_links(struct c3_isp_device *isp)
>> +{
>> +     unsigned int i;
>> +     int ret;
>> +
>> +     for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
>> +             ret = media_create_pad_link(&isp->resizers[i].sd.entity,
>> + C3_ISP_RESIZER_PAD_SOURCE,
>> + &isp->resizers[i].cap->vdev.entity,
>> +                                         0, MEDIA_LNK_FL_ENABLED);
>> +             if (ret) {
>> +                     dev_err(isp->dev, "Failed to link resizer %u 
>> and capture %u\n", i, i);
>> +                     goto err_remove_links;
>> +             }
>> +
>> +             ret = media_create_pad_link(&isp->core.sd.entity,
>> + C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> + &isp->resizers[i].sd.entity,
>> + C3_ISP_RESIZER_PAD_SINK,
>> +                                         MEDIA_LNK_FL_ENABLED);
>> +             if (ret) {
>> +                     dev_err(isp->dev, "Failed to link core and 
>> resizer %u\n", i);
>> +                     goto err_remove_links;
>> +             }
>> +     }
>> +
>> +     ret = media_create_pad_link(&isp->core.sd.entity,
>> +                                 C3_ISP_CORE_PAD_SOURCE_STATS,
>> + &isp->stats.vdev.entity,
>> +                                 0, MEDIA_LNK_FL_ENABLED);
>> +     if (ret) {
>> +             dev_err(isp->dev, "Failed to link core and stats\n");
>> +             goto err_remove_links;
>> +     }
>> +
>> +     ret = media_create_pad_link(&isp->params.vdev.entity, 0,
>> +                                 &isp->core.sd.entity,
>> +                                 C3_ISP_CORE_PAD_SINK_PARAMS,
>> +                                 MEDIA_LNK_FL_ENABLED);
>> +     if (ret) {
>> +             dev_err(isp->dev, "Failed to link params and core\n");
>> +             goto err_remove_links;
>> +     }
>> +
>> +     return 0;
>> +
>> +err_remove_links:
>> +     c3_isp_remove_links(isp);
>> +     return ret;
>> +}
>> +
>> +static int c3_isp_videos_register(struct c3_isp_device *isp)
>> +{
>> +     int ret;
>> +
>> +     ret = c3_isp_captures_register(isp);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = c3_isp_stats_register(isp);
>> +     if (ret)
>> +             goto err_captures_unregister;
>> +
>> +     ret = c3_isp_params_register(isp);
>> +     if (ret)
>> +             goto err_stats_unregister;
>> +
>> +     ret = c3_isp_create_links(isp);
>> +     if (ret)
>> +             goto err_params_unregister;
>> +
>> +     return 0;
>> +
>> +err_params_unregister:
>> +     c3_isp_params_unregister(isp);
>> +err_stats_unregister:
>> +     c3_isp_stats_unregister(isp);
>> +err_captures_unregister:
>> +     c3_isp_captures_unregister(isp);
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_videos_unregister(struct c3_isp_device *isp)
>> +{
>> +     c3_isp_remove_links(isp);
>> +     c3_isp_params_unregister(isp);
>> +     c3_isp_stats_unregister(isp);
>> +     c3_isp_captures_unregister(isp);
>> +}
>> +
>> +static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
>> +{
>> +     const struct c3_isp_info *info = isp->info;
>> +     int ret;
>> +     u32 i;
>> +
>> +     for (i = 0; i < info->clock_num; i++)
>> +             isp->clks[i].id = info->clocks[i];
>> +
>> +     ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
>> +     if (ret)
>> +             return ret;
>> +
>> +     for (i = 0; i < info->clock_num; i++) {
>> +             if (!info->clock_rates[i])
>> +                     continue;
>> +             ret = clk_set_rate(isp->clks[i].clk, 
>> info->clock_rates[i]);
>> +             if (ret) {
>> +                     dev_err(isp->dev, "Failed to set %s rate %u\n", 
>> info->clocks[i],
>> +                             info->clock_rates[i]);
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_probe(struct platform_device *pdev)
>> +{
>> +     struct device *dev = &pdev->dev;
>> +     struct c3_isp_device *isp;
>> +     int irq;
>> +     int ret;
>> +
>> +     isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
>> +     if (!isp)
>> +             return -ENOMEM;
>> +
>> +     isp->info = of_device_get_match_data(dev);
>> +     isp->dev = dev;
>> +
>> +     isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
>> +     if (IS_ERR(isp->base))
>> +             return dev_err_probe(dev, PTR_ERR(isp->base),
>> +                                  "Failed to ioremap resource\n");
>> +
>> +     irq = platform_get_irq(pdev, 0);
>> +     if (irq < 0)
>> +             return irq;
>> +
>> +     ret = c3_isp_cfg_clocks(isp);
>> +     if (ret)
>> +             return dev_err_probe(dev, ret, "Failed to configure 
>> clocks\n");
>> +
>> +     platform_set_drvdata(pdev, isp);
>> +
>> +     pm_runtime_enable(dev);
>> +
>> +     ret = c3_isp_v4l2_register(isp);
>> +     if (ret)
>> +             goto err_runtime_disable;
>> +
>> +     ret = c3_isp_core_register(isp);
>> +     if (ret)
>> +             goto err_v4l2_unregister;
>> +
>> +     ret = c3_isp_resizers_register(isp);
>> +     if (ret)
>> +             goto err_core_unregister;
>> +
>> +     ret = c3_isp_async_nf_register(isp);
>> +     if (ret)
>> +             goto err_resizers_unregister;
>> +
>> +     ret = c3_isp_videos_register(isp);
>> +     if (ret)
>> +             goto err_nf_unregister;
>> +
>> +     ret = devm_request_irq(dev, irq,
>> +                            c3_isp_irq_handler, IRQF_SHARED,
>> +                            dev_driver_string(dev), isp);
>> +     if (ret)
>> +             goto err_streams_unregister;
>> +
>> +     mutex_init(&isp->lock);
>> +
>> +     return 0;
>> +
>> +err_streams_unregister:
>> +     c3_isp_videos_unregister(isp);
>> +err_nf_unregister:
>> +     c3_isp_async_nf_unregister(isp);
>> +err_resizers_unregister:
>> +     c3_isp_resizers_unregister(isp);
>> +err_core_unregister:
>> +     c3_isp_core_unregister(isp);
>> +err_v4l2_unregister:
>> +     c3_isp_v4l2_unregister(isp);
>> +err_runtime_disable:
>> +     pm_runtime_disable(dev);
>> +     return ret;
>> +};
>> +
>> +static void c3_isp_remove(struct platform_device *pdev)
>> +{
>> +     struct c3_isp_device *isp = platform_get_drvdata(pdev);
>> +
>> +     mutex_destroy(&isp->lock);
>> +     c3_isp_videos_unregister(isp);
>> +     c3_isp_async_nf_unregister(isp);
>> +     c3_isp_core_unregister(isp);
>> +     c3_isp_resizers_unregister(isp);
>> +     c3_isp_v4l2_unregister(isp);
>> +     pm_runtime_disable(isp->dev);
>> +};
>> +
>> +static const struct c3_isp_info isp_info = {
>> +     .clocks = {"vapb", "isp0"},
>> +     .clock_rates = {0, 400000000},
>> +     .clock_num = 2
>> +};
>> +
>> +static const struct of_device_id c3_isp_of_match[] = {
>> +     { .compatible = "amlogic,c3-isp",
>> +       .data = &isp_info },
>> +     { },
>> +};
>> +MODULE_DEVICE_TABLE(of, c3_isp_of_match);
>> +
>> +static struct platform_driver c3_isp_driver = {
>> +     .probe = c3_isp_probe,
>> +     .remove = c3_isp_remove,
>> +     .driver = {
>> +             .name = "c3-isp",
>> +             .of_match_table = c3_isp_of_match,
>> +             .pm = &c3_isp_pm_ops,
>> +     },
>> +};
>> +
>> +module_platform_driver(c3_isp_driver);
>> +
>> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
>> +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c 
>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
>> new file mode 100644
>> index 000000000000..8a6b7ce86eaf
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
>> @@ -0,0 +1,857 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-event.h>
>> +#include <media/v4l2-ioctl.h>
>> +#include <media/v4l2-mc.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +#include "include/uapi/c3-isp-config.h"
>> +
>> +typedef void (*block_handler)(struct c3_isp_device *isp,
>> +                           struct c3_isp_param_block_header *block);
>> +
>> +struct c3_isp_block_handler {
>> +     size_t size;
>> +     block_handler handler;
>> +};
>> +
>> +/* Hardware configuration */
>> +
>> +static void c3_isp_params_cfg_wb_change(struct c3_isp_device *isp,
>> +                                     struct 
>> c3_isp_param_block_header *block)
>> +{
>> +     struct wb_change_cfg *wb = (struct wb_change_cfg *)block;
>
> Sakari had a cool suggestion that abstracted the parameter blocks 
> better using a union without the
> need to cast everywhere. See the use of union mali_c55_params_block in
> https://lore.kernel.org/linux-media/20241106100534.768400-17-dan.scally@ideasonboard.com/T/#u 
>
>
>
> OK, will refer to the union mali_c55_params_block.
>> +
>> +     if (!block->enabled) {
>> +             c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
>> +                                TOP_BEO_CTRL_WB_EN, false);
>> +             return;
>> +     }
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
>> +                        TOP_BEO_CTRL_WB_EN, TOP_BEO_CTRL_WB_EN);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, LSWB_WB_GAIN0_MASK,
>> +                        wb->wb_gain[0] << LSWB_WB_GAIN0_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0,
>> +                        LSWB_WB_GAIN1_MASK, wb->wb_gain[1]);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, LSWB_WB_GAIN2_MASK,
>> +                        wb->wb_gain[2] << LSWB_WB_GAIN2_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1,
>> +                        LSWB_WB_GAIN3_MASK, wb->wb_gain[3]);
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN2,
>> +                        LSWB_WB_GAIN4_MASK, wb->wb_gain[4]);
>> +
>> +     c3_isp_write(isp, ISP_LSWB_WB_LIMIT0,
>> +                  LSWB_WB_LIMIT0(wb->wb_limit[0]) | 
>> LSWB_WB_LIMIT1(wb->wb_limit[1]));
>> +
>> +     c3_isp_write(isp, ISP_LSWB_WB_LIMIT1,
>> +                  LSWB_WB_LIMIT2(wb->wb_limit[2]) | 
>> LSWB_WB_LIMIT3(wb->wb_limit[3]));
>> +
>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_LIMIT2, LSWB_WB_LIMIT4_MASK,
>> +                        wb->wb_limit[4]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_BL12_GRBGI0_MASK,
>> +                        wb->ae_bl12_grbgi[0]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_GAIN_GRBGI0_MASK,
>> +                        wb->ae_gain_grbgi[0] << 
>> AE_CRTL2_GAIN_GRBGI0_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_BL12_GRBGI1_MASK,
>> +                        wb->ae_bl12_grbgi[1]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_GAIN_GRBGI1_MASK,
>> +                        wb->ae_gain_grbgi[1] << 
>> AE_CRTL2_GAIN_GRBGI1_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_BL12_GRBGI2_MASK,
>> +                        wb->ae_bl12_grbgi[2]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_GAIN_GRBGI2_MASK,
>> +                        wb->ae_gain_grbgi[2] << 
>> AE_CRTL2_GAIN_GRBGI2_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_BL12_GRBGI3_MASK,
>> +                        wb->ae_bl12_grbgi[3]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_GAIN_GRBGI3_MASK,
>> +                        wb->ae_gain_grbgi[3] << 
>> AE_CRTL2_GAIN_GRBGI3_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_BL12_GRBGI4_MASK,
>> +                        wb->ae_bl12_grbgi[4]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_GAIN_GRBGI4_MASK,
>> +                        wb->ae_gain_grbgi[4] << 
>> AE_CRTL2_GAIN_GRBGI4_SHIFT);
>> +}
>> +
>> +static void c3_isp_params_cfg_wb_luma(struct c3_isp_device *isp,
>> +                                   struct c3_isp_param_block_header 
>> *block)
>> +{
>> +     struct wb_luma_cfg *wb_luma = (struct wb_luma_cfg *)block;
>> +
>> +     if (!block->enabled)
>> +             return;
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_0,
>> +                        AWB_STAT_BLC20_GR_MASK, 
>> wb_luma->awb_stat_blc20[0]);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_1,
>> +                        AWB_STAT_BLC20_R_MASK, 
>> wb_luma->awb_stat_blc20[1]);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_2,
>> +                        AWB_STAT_BLC20_B_MASK, 
>> wb_luma->awb_stat_blc20[2]);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_3,
>> +                        AWB_STAT_BLC20_GB_MASK, 
>> wb_luma->awb_stat_blc20[3]);
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_0,
>> +                        AWB_STAT_GAIN10_GR_MASK, 
>> wb_luma->awb_stat_gain10[0]);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_1,
>> +                        AWB_STAT_GAIN10_R_MASK, 
>> wb_luma->awb_stat_gain10[1]);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_2,
>> +                        AWB_STAT_GAIN10_B_MASK, 
>> wb_luma->awb_stat_gain10[2]);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_3,
>> +                        AWB_STAT_GAIN10_GB_MASK, 
>> wb_luma->awb_stat_gain10[3]);
>> +
>> +     c3_isp_write(isp, ISP_AWB_STAT_SATUR_CTRL,
>> + AWB_STAT_SATUR_LOW(wb_luma->awb_stat_satur_low) |
>> + AWB_STAT_SATUR_HIGH(wb_luma->awb_stat_satur_high));
>> +}
>> +
>> +static void c3_isp_params_cfg_wb_triangle(struct c3_isp_device *isp,
>> +                                       struct 
>> c3_isp_param_block_header *block)
>> +{
>> +     struct wb_triangle_cfg *wb = (struct wb_triangle_cfg *)block;
>> +
>> +     if (!block->enabled)
>> +             return;
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_CTRL2, 
>> AWB_STAT_SATUR_VALID_MASK,
>> +                        wb->awb_stat_satur_vald);
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MIN_MASK,
>> +                        wb->awb_stat_rg_min);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MAX_MASK,
>> +                        wb->awb_stat_rg_max << AWB_STAT_RG_MAX_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MIN_MASK,
>> +                        wb->awb_stat_bg_min);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MAX_MASK,
>> +                        wb->awb_stat_bg_max << AWB_STAT_BG_MAX_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_LOW_MASK,
>> +                        wb->awb_stat_rg_low);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_HIGH_MASK,
>> +                        wb->awb_stat_rg_high << 
>> AWB_STAT_RG_HIGH_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_LOW_MASK,
>> +                        wb->awb_stat_bg_low);
>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_HIGH_MASK,
>> +                        wb->awb_stat_bg_high << 
>> AWB_STAT_BG_HIGH_SHIFT);
>> +}
>> +
>> +static void c3_isp_params_cfg_awb_stats(struct c3_isp_device *isp,
>> +                                     struct 
>> c3_isp_param_block_header *block)
>> +{
>> +     struct awb_stats_cfg *awb_stats = (struct awb_stats_cfg *)block;
>> +     u32 *weight = awb_stats->awb_stat_blk_weight;
>> +     int idx_base;
>> +     int group;
>> +     int i;
>> +
>> +     if (!block->enabled)
>> +             return;
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, 
>> TOP_3A_AWB_STAT_SWITCH_MASK,
>> +                        awb_stats->awb_stat_switch << 
>> TOP_3A_AWB_STAT_SWITCH_SHIFT);
>> +
>> +     /* Calculate the group number */
>> +     group = AWB_BLOCK_WT_NUM / AWB_BLK_WT_DATA_NUM_OF_GROUP;
>> +
>> +     /* Set the weight address to 0 position */
>> +     c3_isp_write(isp, ISP_AWB_BLK_WT_ADDR, 0);
>> +     for (i = 0; i < group; i++) {
>> +             idx_base = i * AWB_BLK_WT_DATA_NUM_OF_GROUP;
>> +             c3_isp_write(isp, ISP_AWB_BLK_WT_DATA,
>> +                          AWB_BLK_WT_DATA0(weight[idx_base + 0]) |
>> +                          AWB_BLK_WT_DATA1(weight[idx_base + 1]) |
>> +                          AWB_BLK_WT_DATA2(weight[idx_base + 2]) |
>> +                          AWB_BLK_WT_DATA3(weight[idx_base + 3]) |
>> +                          AWB_BLK_WT_DATA4(weight[idx_base + 4]) |
>> +                          AWB_BLK_WT_DATA5(weight[idx_base + 5]) |
>> +                          AWB_BLK_WT_DATA6(weight[idx_base + 6]) |
>> +                          AWB_BLK_WT_DATA7(weight[idx_base + 7]));
>> +     }
>> +}
>> +
>> +static void c3_isp_params_cfg_ae_stats(struct c3_isp_device *isp,
>> +                                    struct c3_isp_param_block_header 
>> *block)
>> +{
>> +     struct ae_stats_cfg *ae_stats = (struct ae_stats_cfg *)block;
>> +     u32 *weight = ae_stats->ae_stat_blk_weight;
>> +     int idx_base;
>> +     int group;
>> +     int i;
>> +
>> +     if (!block->enabled)
>> +             return;
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, 
>> TOP_3A_AE_STAT_SWITCH_MASK,
>> +                        ae_stats->ae_stat_switch << 
>> TOP_3A_AE_STAT_SWITCH_SHIFT);
>> +
>> +     /* Calculate the group number */
>> +     group = AE_BLOCK_WT_NUM / AE_BLK_WT_DATA_NUM_OF_GROUP;
>> +
>> +     /* Set the weight address to 0 position */
>> +     c3_isp_write(isp, ISP_AE_BLK_WT_ADDR, 0);
>> +     for (i = 0; i < group; i++) {
>> +             idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
>> +             c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
>> +                          AE_BLK_WT_DATA0(weight[idx_base + 0]) |
>> +                          AE_BLK_WT_DATA1(weight[idx_base + 1]) |
>> +                          AE_BLK_WT_DATA2(weight[idx_base + 2]) |
>> +                          AE_BLK_WT_DATA3(weight[idx_base + 3]) |
>> +                          AE_BLK_WT_DATA4(weight[idx_base + 4]) |
>> +                          AE_BLK_WT_DATA5(weight[idx_base + 5]) |
>> +                          AE_BLK_WT_DATA6(weight[idx_base + 6]) |
>> +                          AE_BLK_WT_DATA7(weight[idx_base + 7]));
>> +     }
>> +
>> +     /* Write the last weight data */
>> +     idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
>> +     c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
>> +                  AE_BLK_WT_DATA0(weight[idx_base + 0]) |
>> +                  AE_BLK_WT_DATA1(weight[idx_base + 1]) |
>> +                  AE_BLK_WT_DATA2(weight[idx_base + 2]) |
>> +                  AE_BLK_WT_DATA3(weight[idx_base + 3]) |
>> +                  AE_BLK_WT_DATA4(weight[idx_base + 4]) |
>> +                  AE_BLK_WT_DATA5(weight[idx_base + 5]) |
>> +                  AE_BLK_WT_DATA6(weight[idx_base + 6]));
>> +}
>> +
>> +static void c3_isp_params_cfg_af_stats(struct c3_isp_device *isp,
>> +                                    struct c3_isp_param_block_header 
>> *block)
>> +{
>> +     struct af_stats_cfg *af_stats = (struct af_stats_cfg *)block;
>> +
>> +     if (!block->enabled)
>> +             return;
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, 
>> TOP_3A_AF_STAT_SWITCH_MASK,
>> +                        af_stats->af_stat_switch << 
>> TOP_3A_AF_STAT_SWITCH_SHIFT);
>> +}
>> +
>> +static void c3_isp_params_cfg_pst_gamma(struct c3_isp_device *isp,
>> +                                     struct 
>> c3_isp_param_block_header *block)
>> +{
>> +     struct pst_gamma_cfg *gamma = (struct pst_gamma_cfg *)block;
>> +     int idx_base;
>> +     int i, j;
>> +
>> +     if (!block->enabled) {
>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, 
>> TOP_BED_GAMMA_EN, false);
>> +             return;
>> +     }
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, 
>> TOP_BED_GAMMA_EN);
>> +
>> +     for (j = 0; j < GAMMA_LUT_GROUP_NUM; j++) {
>> +             c3_isp_write(isp, ISP_PST_GAMMA_LUT_ADDR, 
>> PST_GAMMA_LUT_ADDR(j));
>> +
>> +             /* Calculate the block number */
>> +             for (i = 0; i < GAMMA_LUT_POINT_NUM / 
>> GAMMA_LUT_DATA_NUM_OF_GROUP; i++) {
>> +                     idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
>> +                     c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>> + PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]) |
>> + PST_GAMMA_LUT_DATA1(gamma->pst_gamma_lut[j][idx_base + 1]));
>> +             }
>> +
>> +             /* Write the last one lut data of group j */
>> +             idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
>> +             c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>> + PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]));
>> +     }
>> +}
>> +
>> +static void c3_isp_params_cfg_dmsc(struct c3_isp_device *isp,
>> +                                struct c3_isp_param_block_header 
>> *block)
>> +{
>> +     if (!block->enabled) {
>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, 
>> TOP_BED_DMSC_EN, false);
>> +             return;
>> +     }
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, 
>> TOP_BED_DMSC_EN);
>> +}
>> +
>> +/* Configure 4 x 3 ccm matrix */
>> +static void c3_isp_params_cfg_ccm(struct c3_isp_device *isp,
>> +                               struct c3_isp_param_block_header *block)
>> +{
>> +     struct ccm_cfg *ccm = (struct ccm_cfg *)block;
>> +
>> +     if (!block->enabled) {
>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, 
>> TOP_BED_CCM_EN, false);
>> +             return;
>> +     }
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, 
>> TOP_BED_CCM_EN);
>> +
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_00_MASK,
>> +                        ccm->ccm_4x3matrix[0][0]);
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_01_MASK,
>> +                        ccm->ccm_4x3matrix[0][1] << CCM_MTX_01_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_02_MASK,
>> +                        ccm->ccm_4x3matrix[0][2]);
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_03_MASK,
>> +                        ccm->ccm_4x3matrix[0][3] << CCM_MTX_03_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_10_MASK,
>> +                        ccm->ccm_4x3matrix[1][0]);
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_11_MASK,
>> +                        ccm->ccm_4x3matrix[1][1] << CCM_MTX_11_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_12_MASK,
>> +                        ccm->ccm_4x3matrix[1][2]);
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_13_MASK,
>> +                        ccm->ccm_4x3matrix[1][3] << CCM_MTX_13_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_20_MASK,
>> +                        ccm->ccm_4x3matrix[2][0]);
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_21_MASK,
>> +                        ccm->ccm_4x3matrix[2][1] << CCM_MTX_21_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_22_MASK,
>> +                        ccm->ccm_4x3matrix[2][2]);
>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_23_MASK,
>> +                        ccm->ccm_4x3matrix[2][3] << CCM_MTX_23_SHIFT);
>> +}
>> +
>> +/* Configure color space conversion matrix parameters */
>> +static void c3_isp_params_cfg_csc(struct c3_isp_device *isp,
>> +                               struct c3_isp_param_block_header *block)
>> +{
>> +     struct csc_cfg *csc = (struct csc_cfg *)block;
>> +
>> +     if (!block->enabled) {
>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, 
>> TOP_BED_CM0_EN, false);
>> +             return;
>> +     }
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, 
>> TOP_BED_CM0_EN);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST0_MASK,
>> +                        csc->cm0_offset_inp[0]);
>> +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST1_MASK,
>> +                        csc->cm0_offset_inp[1] << CM0_INP_OFST1_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST2, CM0_INP_OFST2_MASK,
>> +                        csc->cm0_offset_inp[2]);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_00_MASK,
>> +                        csc->cm0_3x3matrix[0][0]);
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_01_MASK,
>> +                        csc->cm0_3x3matrix[0][1] << CM0_MTX_01_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_02_MASK,
>> +                        csc->cm0_3x3matrix[0][2]);
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_10_MASK,
>> +                        csc->cm0_3x3matrix[1][0] << CM0_MTX_10_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_11_MASK,
>> +                        csc->cm0_3x3matrix[1][1]);
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_12_MASK,
>> +                        csc->cm0_3x3matrix[1][2] << CM0_MTX_12_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_20_MASK,
>> +                        csc->cm0_3x3matrix[2][0]);
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_21_MASK,
>> +                        csc->cm0_3x3matrix[2][1] << CM0_MTX_21_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_MTX_22_MASK,
>> +                        csc->cm0_3x3matrix[2][2]);
>> +     c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, 
>> CM0_OFST_OUP0_MASK,
>> +                        csc->cm0_offset_oup[0] << CM0_OFST_OUP0_SHIFT);
>> +
>> +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP1_MASK,
>> +                        csc->cm0_offset_oup[1]);
>> +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP2_MASK,
>> +                        csc->cm0_offset_oup[2] << CM0_OFST_OUP2_SHIFT);
>> +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_MTX_RS_MASK,
>> +                        csc->cm0_3x3mtrx_rs << CM0_MTX_RS_SHIFT);
>> +}
>> +
>> +/* Set blc offset of each color channel */
>> +static void c3_isp_params_cfg_blc(struct c3_isp_device *isp,
>> +                               struct c3_isp_param_block_header *block)
>> +{
>> +     struct blc_cfg *blc = (struct blc_cfg *)block;
>> +
>> +     if (!block->enabled) {
>> +             c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, 
>> TOP_BEO_CTRL_BLC_EN, false);
>> +             return;
>> +     }
>> +
>> +     c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN,
>> +                        TOP_BEO_CTRL_BLC_EN);
>> +
>> +     c3_isp_write(isp, ISP_FED_BL_OFST_GR, 
>> FED_BL_GR_OFST(blc->fe_bl_ofst[0]));
>> +     c3_isp_write(isp, ISP_FED_BL_OFST_R, 
>> FED_BL_R_OFST(blc->fe_bl_ofst[1]));
>> +     c3_isp_write(isp, ISP_FED_BL_OFST_B, 
>> FED_BL_B_OFST(blc->fe_bl_ofst[2]));
>> +     c3_isp_write(isp, ISP_FED_BL_OFST_GB, 
>> FED_BL_GB_OFST(blc->fe_bl_ofst[3]));
>> +     c3_isp_write(isp, ISP_FED_BL_OFST_IR, 
>> FED_BL_IR_OFST(blc->fe_bl_ofst[4]));
>> +
>> +     c3_isp_write(isp, ISP_LSWB_BLC_OFST0,
>> +                  LSWB_BLC_OFST0(blc->blc_ofst[0]) | 
>> LSWB_BLC_OFST1(blc->blc_ofst[1]));
>> +     c3_isp_write(isp, ISP_LSWB_BLC_OFST1,
>> +                  LSWB_BLC_OFST2(blc->blc_ofst[2]) | 
>> LSWB_BLC_OFST3(blc->blc_ofst[3]));
>> +     c3_isp_write(isp, ISP_LSWB_BLC_OFST2, 
>> LSWB_BLC_OFST4(blc->blc_ofst[4]));
>> +}
>> +
>> +static const struct c3_isp_block_handler c3_isp_block_handlers[] = {
>> +     [C3_ISP_PARAM_BLOCK_WB_CHANGE] = {
>> +             .size = sizeof(struct wb_change_cfg),
>> +             .handler = c3_isp_params_cfg_wb_change,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_WB_LUMA] = {
>> +             .size = sizeof(struct wb_luma_cfg),
>> +             .handler = c3_isp_params_cfg_wb_luma,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_WB_TRIANGLE] = {
>> +             .size = sizeof(struct wb_triangle_cfg),
>> +             .handler = c3_isp_params_cfg_wb_triangle,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_AWB_STATS] = {
>> +             .size = sizeof(struct awb_stats_cfg),
>> +             .handler = c3_isp_params_cfg_awb_stats,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_AE_STATS] = {
>> +             .size = sizeof(struct ae_stats_cfg),
>> +             .handler = c3_isp_params_cfg_ae_stats,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_AF_STATS] = {
>> +             .size = sizeof(struct af_stats_cfg),
>> +             .handler = c3_isp_params_cfg_af_stats,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_PST_GAMMA] = {
>> +             .size = sizeof(struct pst_gamma_cfg),
>> +             .handler = c3_isp_params_cfg_pst_gamma,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_DMSC] = {
>> +             .size = sizeof(struct dmsc_cfg),
>> +             .handler = c3_isp_params_cfg_dmsc,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_CCM] = {
>> +             .size = sizeof(struct ccm_cfg),
>> +             .handler = c3_isp_params_cfg_ccm,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_CSC] = {
>> +             .size = sizeof(struct csc_cfg),
>> +             .handler = c3_isp_params_cfg_csc,
>> +     },
>> +     [C3_ISP_PARAM_BLOCK_BLC] = {
>> +             .size = sizeof(struct blc_cfg),
>> +             .handler = c3_isp_params_cfg_blc,
>> +     },
>> +};
>> +
>> +static enum vb2_buffer_state
>> +c3_isp_params_cfg_blocks(struct c3_isp_params *params)
>> +{
>> +     struct c3_isp_params_buffer *config = params->buff->vaddr;
>> +     enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
>> +     size_t block_offset = 0;
>> +     size_t max_offset = 0;
>> +
>> +     if (config->total_size > C3_ISP_PARAMS_MAX_SIZE) {
>> +             dev_dbg(params->isp->dev, "Invalid parameters buffer 
>> size %lu\n",
>> +                     config->total_size);
>> +             state = VB2_BUF_STATE_ERROR;
>> +             goto err_return_state;
>> +     }
>> +
>> +     /* Ensure config->data has a full struct 
>> c3_isp_param_block_header */
>> +     max_offset = config->total_size - sizeof(struct 
>> c3_isp_param_block_header);
>> +
>> +     while (block_offset <= max_offset) {
>> +             const struct c3_isp_block_handler *block_handler;
>> +             struct c3_isp_param_block_header *block;
>> +
>> +             block = (struct c3_isp_param_block_header *)
>> +                      &config->data[block_offset];
>> +
>> +             if (block->type >= C3_ISP_PARAM_BLOCK_SENTINEL) {
>> +                     dev_dbg(params->isp->dev, "Invalid parameters 
>> block type\n");
>> +                     state = VB2_BUF_STATE_ERROR;
>> +                     goto err_return_state;
>> +             }
>> +
>> +             block_handler = &c3_isp_block_handlers[block->type];
>> +             if (block->size != block_handler->size) {
>> +                     dev_dbg(params->isp->dev, "Invalid parameters 
>> block size\n");
>> +                     state = VB2_BUF_STATE_ERROR;
>> +                     goto err_return_state;
>> +             }
>> +
>> +             block_handler->handler(params->isp, block);
>> +
>> +             block_offset += block->size;
>> +     }
>> +
>> +err_return_state:
>> +     return state;
>> +}
>
>
> The validation in this function can be moved to .buf_queue() so it's 
> not done in interrupt context
>

OK, will move the validation to .buf_queue().

>> +
>> +/* Initialize ISP pipeline */
>> +static int c3_isp_params_start(struct c3_isp_params *params)
>> +{
>> +     enum vb2_buffer_state state;
>> +     unsigned long flags;
>> +
>> +     /* Reset these controllers */
>> +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL0, 
>> TOP_FEO_CTRL0_ALL_DIS);
>> +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_0, 
>> TOP_FEO_CTRL1_0_ALL_DIS);
>> +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_1, 
>> TOP_FEO_CTRL1_1_ALL_DIS);
>> +     c3_isp_write(params->isp, ISP_TOP_FED_CTRL, TOP_FED_CTRL_ALL_DIS);
>> +     c3_isp_write(params->isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_ALL_DIS);
>> +     c3_isp_write(params->isp, ISP_TOP_BED_CTRL, TOP_BED_CTRL_ALL_DIS);
>> +
>> +     spin_lock_irqsave(&params->buff_lock, flags);
>> +
>> +     /* Only use the first buffer to initialize ISP */
>> +     params->buff = list_first_entry_or_null(&params->pending,
>> +                                             struct 
>> c3_isp_vb2_buffer, list);
>> +     if (!params->buff) {
>> +             spin_unlock_irqrestore(&params->buff_lock, flags);
>> +             return -EINVAL;
>> +     }
>
>
> You have min_queued_buffers set to a non-zero figure, so this error is 
> theoretically impossible - I
> would keep the check but add a comment that it ought not to have 
> happened.
>

Will reconsider the impact of this value.

>> +
>> +     state = c3_isp_params_cfg_blocks(params);
>> +
>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>> +
>> +     return 0;
>> +}
>> +
>> +/* V4L2 video operations */
>> +
>> +static void c3_isp_params_return_buffers(struct c3_isp_params *params,
>> +                                      enum vb2_buffer_state state)
>> +{
>> +     unsigned long flags;
>> +     struct c3_isp_vb2_buffer *buff;
>> +
>> +     spin_lock_irqsave(&params->buff_lock, flags);
>> +
>> +     while (!list_empty(&params->pending)) {
>> +             buff = list_first_entry(&params->pending,
>> +                                     struct c3_isp_vb2_buffer, list);
>> +             list_del(&buff->list);
>> +             vb2_buffer_done(&buff->vb.vb2_buf, state);
>> +     }
>> +
>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>> +}
>> +
>> +static int c3_isp_params_querycap(struct file *file, void *fh,
>> +                               struct v4l2_capability *cap)
>> +{
>> +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>> +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_params_enum_fmt(struct file *file, void *fh,
>> +                               struct v4l2_fmtdesc *f)
>> +{
>> +     if (f->index)
>> +             return -EINVAL;
>> +
>> +     f->pixelformat = V4L2_META_FMT_C3ISP_PARAMS;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_params_g_fmt(struct file *file, void *fh,
>> +                            struct v4l2_format *f)
>> +{
>> +     struct c3_isp_params *params = video_drvdata(file);
>> +
>> +     f->fmt.meta = params->vfmt.fmt.meta;
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct v4l2_ioctl_ops isp_params_v4l2_ioctl_ops = {
>> +     .vidioc_querycap                = c3_isp_params_querycap,
>> +     .vidioc_enum_fmt_meta_out       = c3_isp_params_enum_fmt,
>> +     .vidioc_g_fmt_meta_out          = c3_isp_params_g_fmt,
>> +     .vidioc_s_fmt_meta_out          = c3_isp_params_g_fmt,
>> +     .vidioc_try_fmt_meta_out        = c3_isp_params_g_fmt,
>> +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>> +     .vidioc_querybuf                = vb2_ioctl_querybuf,
>> +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
>> +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
>> +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>> +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>> +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>> +     .vidioc_streamon                = vb2_ioctl_streamon,
>> +     .vidioc_streamoff               = vb2_ioctl_streamoff,
>> +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>> +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>> +};
>> +
>> +static const struct v4l2_file_operations isp_params_v4l2_fops = {
>> +     .open = v4l2_fh_open,
>> +     .release = vb2_fop_release,
>> +     .poll = vb2_fop_poll,
>> +     .unlocked_ioctl = video_ioctl2,
>> +     .mmap = vb2_fop_mmap,
>> +};
>> +
>> +static int c3_isp_params_vb2_queue_setup(struct vb2_queue *q,
>> +                                      unsigned int *num_buffers,
>> +                                      unsigned int *num_planes,
>> +                                      unsigned int sizes[],
>> +                                      struct device *alloc_devs[])
>> +{
>> +     if (*num_planes) {
>> +             if (*num_planes != 1)
>> +                     return -EINVAL;
>> +
>> +             if (sizes[0] < sizeof(struct c3_isp_params_buffer))
>> +                     return -EINVAL;
>> +     } else {
>> +             *num_planes = 1;
>> +             sizes[0] = sizeof(struct c3_isp_params_buffer);
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_isp_params_vb2_buf_queue(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_vb2_buffer *buf =
>> +                     container_of(v4l2_buf, struct 
>> c3_isp_vb2_buffer, vb);
>> +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned long flags;
>> +
>> +     spin_lock_irqsave(&params->buff_lock, flags);
>> +
>> +     list_add_tail(&buf->list, &params->pending);
>> +
>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>> +}
>
>
> So as mentioned above, you can validate the contents of the buffer in 
> this function rather than
> during the interrupt. We've also started using memcpy() here to store 
> the buffer in a scratch buffer
> internal to the driver; this is designed to protect against userspace 
> making changes to the buffer
> whilst it's supposed the be being processed by the driver. Check the 
> equivalent function in the
> mali-c55 series:
>
>
> https://lore.kernel.org/linux-media/20241106100534.768400-17-dan.scally@ideasonboard.com/T/#u 
>
>

OK, will refer to your patches.

>> +
>> +static int c3_isp_params_vb2_buf_prepare(struct vb2_buffer *vb)
>> +{
>> +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned int size = params->vfmt.fmt.meta.buffersize;
>> +
>> +     if (vb2_plane_size(vb, 0) < size) {
>
>
> isn't vfmt.fmt.meta.buffersize filled by userspace? If so I think this 
> would mean you'd have to pass
> all of the blocks - we don't want that, the design of the extensible 
> params is to allow only part of
> the config to be passed if that's all that's needed.
>

Will check and modify this operation.

>> + dev_err(params->isp->dev,
>> +                     "User buffer too small (%ld < %u)\n",
>> +                     vb2_plane_size(vb, 0), size);
>> +             return -EINVAL;
>> +     }
>> +
>> +     vb2_set_plane_payload(vb, 0, size);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_params_vb2_buf_init(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_vb2_buffer *buf =
>> +             container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>> +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>> +
>> +     buf->vaddr = vb2_plane_vaddr(vb, 0);
>> +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>> +
>> +     memset(buf->vaddr, 0, params->vfmt.fmt.meta.buffersize);
>> +
> I'm not sure the memset is needed - although with the extensible 
> format not all of the buffer need
> be overwritten, everything up to buffersize would be.


Will remove this memset().

>> +     return 0;
>> +}
>> +
>> +static int c3_isp_params_vb2_start_streaming(struct vb2_queue *q,
>> +                                          unsigned int count)
>> +{
>> +     struct c3_isp_params *params = vb2_get_drv_priv(q);
>> +     int ret;
>> +
>> +     guard(mutex)(&params->isp->lock);
>> +
>> +     ret = pm_runtime_resume_and_get(params->isp->dev);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = video_device_pipeline_start(&params->vdev, 
>> &params->isp->pipe);
>> +     if (ret) {
>> +             dev_err(params->isp->dev,
>> +                     "Failed to start params pipeline: %d\n", ret);
>> +             goto err_pm_put;
>> +     }
>> +
>> +     if (c3_isp_pipeline_ready(params->isp)) {
>> +             ret = v4l2_subdev_enable_streams(&params->isp->core.sd,
>> + C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> +                                              BIT(0));
>> +             if (ret)
>> +                     goto err_pipeline_stop;
>> +     }
>> +
>> +     c3_isp_params_start(params);
>> +
>> +     return 0;
>> +
>> +err_pipeline_stop:
>> +     video_device_pipeline_stop(&params->vdev);
>> +err_pm_put:
>> +     pm_runtime_put(params->isp->dev);
>> +     c3_isp_params_return_buffers(params, VB2_BUF_STATE_QUEUED);
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_params_vb2_stop_streaming(struct vb2_queue *q)
>> +{
>> +     struct c3_isp_params *params = vb2_get_drv_priv(q);
>> +
>> +     guard(mutex)(&params->isp->lock);
>> +
>> +     c3_isp_params_return_buffers(params, VB2_BUF_STATE_ERROR);
>> +
>> +     if (params->isp->pipe.start_count == 1)
>> + v4l2_subdev_disable_streams(&params->isp->core.sd,
>> + C3_ISP_CORE_PAD_SOURCE_VIDEO,
>> +                                         BIT(0));
>> +
>> +     video_device_pipeline_stop(&params->vdev);
>> +     pm_runtime_put(params->isp->dev);
>> +}
>> +
>> +static const struct vb2_ops isp_params_vb2_ops = {
>> +     .queue_setup = c3_isp_params_vb2_queue_setup,
>> +     .buf_queue = c3_isp_params_vb2_buf_queue,
>> +     .buf_prepare = c3_isp_params_vb2_buf_prepare,
>> +     .buf_init = c3_isp_params_vb2_buf_init,
>> +     .wait_prepare = vb2_ops_wait_prepare,
>> +     .wait_finish = vb2_ops_wait_finish,
>> +     .start_streaming = c3_isp_params_vb2_start_streaming,
>> +     .stop_streaming = c3_isp_params_vb2_stop_streaming,
>> +};
>> +
>> +int c3_isp_params_register(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_params *params = &isp->params;
>> +     struct video_device *vdev = &params->vdev;
>> +     struct vb2_queue *vb2_q = &params->vb2_q;
>> +     int ret;
>> +
>> +     memset(params, 0, sizeof(*params));
>> +     params->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_PARAMS;
>> +     params->vfmt.fmt.meta.buffersize = sizeof(struct 
>> c3_isp_params_buffer);
>> +     params->isp = isp;
>> +     INIT_LIST_HEAD(&params->pending);
>> +     spin_lock_init(&params->buff_lock);
>> +     mutex_init(&params->lock);
>> +
>> +     snprintf(vdev->name, sizeof(vdev->name), "isp-params");
>> +     vdev->fops = &isp_params_v4l2_fops;
>> +     vdev->ioctl_ops = &isp_params_v4l2_ioctl_ops;
>> +     vdev->v4l2_dev = &isp->v4l2_dev;
>> +     vdev->lock = &params->lock;
>> +     vdev->minor = -1;
>> +     vdev->queue = vb2_q;
>> +     vdev->release = video_device_release_empty;
>> +     vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING;
>> +     vdev->vfl_dir = VFL_DIR_TX;
>> +     video_set_drvdata(vdev, params);
>> +
>> +     vb2_q->drv_priv = params;
>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>> +     vb2_q->ops = &isp_params_vb2_ops;
>> +     vb2_q->type = V4L2_BUF_TYPE_META_OUTPUT;
>> +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>> +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>> +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>> +     vb2_q->dev = isp->dev;
>> +     vb2_q->lock = &params->lock;
>> +     vb2_q->min_queued_buffers = 1;
>> +
>> +     ret = vb2_queue_init(vb2_q);
>> +     if (ret)
>> +             goto err_detroy;
>> +
>> +     params->pad.flags = MEDIA_PAD_FL_SOURCE;
>> +     ret = media_entity_pads_init(&vdev->entity, 1, &params->pad);
>> +     if (ret)
>> +             goto err_queue_release;
>> +
>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>> +     if (ret < 0) {
>> +             dev_err(isp->dev,
>> +                     "Failed to register %s: %d\n", vdev->name, ret);
>> +             goto err_entity_cleanup;
>> +     }
>> +
>> +     return 0;
>> +
>> +err_entity_cleanup:
>> +     media_entity_cleanup(&vdev->entity);
>> +err_queue_release:
>> +     vb2_queue_release(vb2_q);
>> +err_detroy:
>> +     mutex_destroy(&params->lock);
>> +     return ret;
>> +}
>> +
>> +void c3_isp_params_unregister(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_params *params = &isp->params;
>> +
>> +     vb2_queue_release(&params->vb2_q);
>> +     media_entity_cleanup(&params->vdev.entity);
>> +     video_unregister_device(&params->vdev);
>> +     mutex_destroy(&params->lock);
>> +}
>> +
>> +int c3_isp_params_done(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_params *params = &isp->params;
>> +     enum vb2_buffer_state state;
>> +     unsigned long flags;
>> +
>> +     spin_lock_irqsave(&params->buff_lock, flags);
> Usual comment about guard()


Will use guard().

>> +
>> +     params->buff = list_first_entry_or_null(&params->pending,
>> +                                             struct 
>> c3_isp_vb2_buffer, list);
>> +     if (!params->buff) {
>> +             spin_unlock_irqrestore(&params->buff_lock, flags);
>> +             return -EINVAL;
>> +     }
>> +
>> +     list_del(&params->buff->list);
>> +
>> +     state = c3_isp_params_cfg_blocks(params);
>> +
>> +     params->buff->vb.sequence = params->isp->frm_sequence;
>> +     params->buff->vb.vb2_buf.timestamp = ktime_get();
>> +     params->buff->vb.field = V4L2_FIELD_NONE;
>> +     vb2_buffer_done(&params->buff->vb.vb2_buf, state);
>> +
>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>> +
>> +     return 0;
>> +}
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h 
>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
>> new file mode 100644
>> index 000000000000..de1938f7c354
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
>> @@ -0,0 +1,683 @@
>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#ifndef __C3_ISP_REGS_H__
>> +#define __C3_ISP_REGS_H__
>> +
>> +#define ISP_TOP_INPUT_SIZE                       0x0000
>> +#define TOP_INPUT_HSIZE(x)                       (((x) & 0xffff) << 16)
>> +#define TOP_INPUT_VSIZE(x)                       ((x) & 0xffff)
>> +
>> +#define ISP_TOP_FRM_SIZE                         0x0004
>> +#define TOP_FRM_CORE_IHSIZE(x)                   (((x) & 0xffff) << 16)
>> +#define TOP_FRM_CORE_IVSIZE(x)                   ((x) & 0xffff)
>> +
>> +#define ISP_TOP_HOLD_SIZE                        0x0008
>> +#define TOP_HOLD_HSIZE_MASK                      GENMASK(31, 16)
>> +#define TOP_HOLD_HSIZE_SHIFT                     16
>> +
>> +#define ISP_TOP_PATH_EN                          0x0010
>> +#define TOP_DISP_EN(x)                           BIT((x))
>> +#define TOP_WRMIF_EN(x)                          BIT((x) + 8)
>> +
>> +#define ISP_TOP_PATH_SEL                         0x0014
>> +#define TOP_DATA_PATH_MASK                       GENMASK(18, 16)
>> +#define TOP_PATH_MIPI_TO_CORE                    BIT(16)
>> +
>> +#define ISP_TOP_IRQ_EN                           0x0080
>> +#define TOP_IRQ_FRAME_DONE                       BIT(0)
>> +#define TOP_IRQ_STATS_ERR                        BIT(5)
>> +
>> +#define ISP_TOP_IRQ_CLR                          0x0084
>> +#define ISP_TOP_IRQ_LINE_THRD                    0x0088
>> +#define TOP_IRQ_DIN_HSYNC                        BIT(16)
>> +
>> +#define ISP_TOP_RO_IRQ_STAT                      0x01c4
>> +#define ISP_TOP_MODE_CTRL                        0x0400
>> +#define ISP_TOP_FEO_CTRL0                        0x040c
>> +#define TOP_FEO_CTRL0_ALL_DIS                    0
>> +#define TOP_FEO_INPUT_FMT_EN                     BIT(8)
>> +
>> +#define ISP_TOP_FEO_CTRL1_0                      0x0410
>> +#define TOP_FEO_CTRL1_0_ALL_DIS                  0
>> +
>> +#define ISP_TOP_FEO_CTRL1_1                      0x0414
>> +#define TOP_FEO_CTRL1_1_ALL_DIS                  0
>> +
>> +#define ISP_TOP_FED_CTRL                         0x0418
>> +#define TOP_FED_CTRL_ALL_DIS                     0
>> +
>> +#define ISP_TOP_BEO_CTRL                         0x041c
>> +#define TOP_BEO_CTRL_ALL_DIS                     0
>> +#define TOP_BEO_CTRL_GTM_EN                      BIT(2)
>> +#define TOP_BEO_CTRL_WB_EN                       BIT(6)
>> +#define TOP_BEO_CTRL_BLC_EN                      BIT(7)
>> +#define TOP_BEO_CTRL_IDGAIN_EN                   BIT(8)
>> +#define TOP_BEO_CTRL_EOTF_EN                     BIT(9)
>> +
>> +#define ISP_TOP_BED_CTRL                         0x0420
>> +#define TOP_BED_CTRL_ALL_DIS                     0
>> +#define TOP_BED_CM0_EN                           BIT(14)
>> +#define TOP_BED_GAMMA_EN                         BIT(16)
>> +#define TOP_BED_CCM_EN                           BIT(18)
>> +#define TOP_BED_DMSC_EN                          BIT(19)
>> +
>> +#define ISP_TOP_3A_STAT_CRTL                     0x0424
>> +#define TOP_3A_AE_STAT_EN                        BIT(0)
>> +#define TOP_3A_AWB_STAT_EN                       BIT(1)
>> +#define TOP_3A_AF_STAT_EN                        BIT(2)
>> +#define TOP_3A_AWB_STAT_SWITCH_MASK              GENMASK(6, 4)
>> +#define TOP_3A_AWB_STAT_SWITCH_SHIFT             4
>> +#define TOP_3A_AWB_STAT_SWITCH_BEFORE_WB         2
>> +
>> +#define TOP_3A_AE_STAT_SWITCH_MASK               GENMASK(9, 8)
>> +#define TOP_3A_AE_STAT_SWITCH_SHIFT              8
>> +#define TOP_3A_AE_STAT_SWITCH_FROM_MLS           1
>> +#define TOP_3A_AF_STAT_SWITCH_MASK               GENMASK(13, 12)
>> +#define TOP_3A_AF_STAT_SWITCH_SHIFT              12
>> +#define TOP_3A_AF_STAT_SWITCH_FROM_SNR            0
>> +
>> +#define ISP_FED_BL_OFST_GR                       0x2018
>> +#define FED_BL_GR_OFST(x)                        ((x) & 0x1fffff)
>> +
>> +#define ISP_FED_BL_OFST_R                        0x201c
>> +#define FED_BL_R_OFST(x)                         ((x) & 0x1fffff)
>> +
>> +#define ISP_FED_BL_OFST_B                        0x2020
>> +#define FED_BL_B_OFST(x)                         ((x) & 0x1fffff)
>> +
>> +#define ISP_FED_BL_OFST_GB                       0x2024
>> +#define FED_BL_GB_OFST(x)                        ((x) & 0x1fffff)
>> +
>> +#define ISP_FED_BL_OFST_IR                       0x2028
>> +#define FED_BL_IR_OFST(x)                        ((x) & 0x1fffff)
>> +
>> +#define ISP_LSWB_BLC_OFST0                       0x4028
>> +#define LSWB_BLC_OFST1_MASK                      GENMASK(15, 0)
>> +#define LSWB_BLC_OFST1(x)                        ((x) & 0xffff)
>> +#define LSWB_BLC_OFST1_EXT(x)                    ((x) & 0xffff)
>> +#define LSWB_BLC_OFST0_MASK                      GENMASK(31, 16)
>> +#define LSWB_BLC_OFST0(x)                        (((x) & 0xffff) << 16)
>> +#define LSWB_BLC_OFST0_EXT(x)                    (((x) >> 16) & 0xffff)
>> +
>> +#define ISP_LSWB_BLC_OFST1                       0x402c
>> +#define LSWB_BLC_OFST3_MASK                      GENMASK(15, 0)
>> +#define LSWB_BLC_OFST3(x)                        ((x) & 0xffff)
>> +#define LSWB_BLC_OFST3_EXT(x)                    ((x) & 0xffff)
>> +#define LSWB_BLC_OFST2_MASK                      GENMASK(31, 16)
>> +#define LSWB_BLC_OFST2(x)                        (((x) & 0xffff) << 16)
>> +#define LSWB_BLC_OFST2_EXT(x)                    (((x) >> 16) & 0xffff)
>> +
>> +#define ISP_LSWB_BLC_OFST2                       0x4030
>> +#define LSWB_BLC_OFST4_MASK                      GENMASK(15, 0)
>> +#define LSWB_BLC_OFST4(x)                        ((x) & 0xffff)
>> +#define LSWB_BLC_OFST4_EXT(x)                    ((x) & 0xffff)
>> +
>> +#define ISP_LSWB_BLC_PHSOFST                     0x4034
>> +#define LSWB_BLC_YPHS_OFST_MASK                  GENMASK(1, 0)
>> +#define LSWB_BLC_XPHS_OFST_MASK                  GENMASK(3, 2)
>> +#define LSWB_BLC_XPHS_OFST_SHIFT                 2
>> +
>> +#define ISP_LSWB_WB_GAIN0                        0x4038
>> +#define LSWB_WB_GAIN1_MASK                       GENMASK(11, 0)
>> +#define LSWB_WB_GAIN0_MASK                       GENMASK(27, 16)
>> +#define LSWB_WB_GAIN0_SHIFT                      16
>> +
>> +#define ISP_LSWB_WB_GAIN1                        0x403c
>> +#define LSWB_WB_GAIN3_MASK                       GENMASK(11, 0)
>> +#define LSWB_WB_GAIN2_MASK                       GENMASK(27, 16)
>> +#define LSWB_WB_GAIN2_SHIFT                      16
>> +
>> +#define ISP_LSWB_WB_GAIN2                        0x4040
>> +#define LSWB_WB_GAIN4_MASK                       GENMASK(11, 0)
>> +
>> +#define ISP_LSWB_WB_LIMIT0                       0x4044
>> +#define LSWB_WB_LIMIT1(x)                        ((x) & 0xffff)
>> +#define LSWB_WB_LIMIT0(x)                        (((x) & 0xffff) << 16)
>> +
>> +#define ISP_LSWB_WB_LIMIT1                       0x4048
>> +#define LSWB_WB_LIMIT3(x)                        ((x) & 0xffff)
>> +#define LSWB_WB_LIMIT2(x)                        (((x) & 0xffff) << 16)
>> +
>> +#define ISP_LSWB_WB_LIMIT2                       0x404c
>> +#define LSWB_WB_LIMIT4_MASK                      GENMASK(15, 0)
>> +
>> +#define ISP_LSWB_WB_PHSOFST                      0x4050
>> +#define LSWB_WB_YPHS_OFST_MASK                   GENMASK(1, 0)
>> +#define LSWB_WB_XPHS_OFST_MASK                   GENMASK(3, 2)
>> +#define LSWB_WB_XPHS_OFST_SHIFT                  2
>> +
>> +#define ISP_LSWB_LNS_PHSOFST                     0x4054
>> +#define LSWB_LNS_YPHS_OFST_MASK                  GENMASK(1, 0)
>> +#define LSWB_LNS_XPHS_OFST_MASK                  GENMASK(3, 2)
>> +#define LSWB_LNS_XPHS_OFST_SHIFT                 2
>> +
>> +#define ISP_DMS_COMMON_PARAM0                    0x5000
>> +#define DMS_COMMON_YPHS_OFST_MASK                GENMASK(1, 0)
>> +#define DMS_COMMON_XPHS_OFST_MASK                GENMASK(3, 2)
>> +#define DMS_COMMON_XPHS_OFST_SHIFT               2
>> +
>> +#define ISP_CM0_INP_OFST01                       0x6040
>> +#define CM0_INP_OFST0_MASK                       GENMASK(12, 0)
>> +#define CM0_INP_OFST1_MASK                       GENMASK(28, 16)
>> +#define CM0_INP_OFST1_SHIFT                      16
>> +
>> +#define ISP_CM0_INP_OFST2                        0x6044
>> +#define CM0_INP_OFST2_MASK                       GENMASK(12, 0)
>> +
>> +#define ISP_CM0_COEF00_01                        0x6048
>> +#define CM0_MTX_00_MASK                          GENMASK(12, 0)
>> +#define CM0_MTX_01_MASK                          GENMASK(28, 16)
>> +#define CM0_MTX_01_SHIFT                         16
>> +
>> +#define ISP_CM0_COEF02_10                        0x604c
>> +#define CM0_MTX_02_MASK                          GENMASK(12, 0)
>> +#define CM0_MTX_10_MASK                          GENMASK(28, 16)
>> +#define CM0_MTX_10_SHIFT                         16
>> +
>> +#define ISP_CM0_COEF11_12                        0x6050
>> +#define CM0_MTX_11_MASK                          GENMASK(12, 0)
>> +#define CM0_MTX_12_MASK                          GENMASK(28, 16)
>> +#define CM0_MTX_12_SHIFT                         16
>> +
>> +#define ISP_CM0_COEF20_21                        0x6054
>> +#define CM0_MTX_20_MASK                          GENMASK(12, 0)
>> +#define CM0_MTX_21_MASK                          GENMASK(28, 16)
>> +#define CM0_MTX_21_SHIFT                         16
>> +
>> +#define ISP_CM0_COEF22_OUP_OFST0                 0x6058
>> +#define CM0_MTX_22_MASK                          GENMASK(12, 0)
>> +#define CM0_OFST_OUP0_MASK                       GENMASK(28, 16)
>> +#define CM0_OFST_OUP0_SHIFT                      16
>> +
>> +#define ISP_CM0_OUP_OFST12_RS                    0x605c
>> +#define CM0_OFST_OUP1_MASK                       GENMASK(12, 0)
>> +#define CM0_OFST_OUP2_MASK                       GENMASK(28, 16)
>> +#define CM0_OFST_OUP2_SHIFT                      16
>> +#define CM0_MTX_RS_MASK                          GENMASK(31, 30)
>> +#define CM0_MTX_RS_SHIFT                         30
>> +
>> +#define ISP_CCM_MTX_00_01                        0x6098
>> +#define CCM_MTX_00_MASK                          GENMASK(12, 0)
>> +#define CCM_MTX_01_MASK                          GENMASK(28, 16)
>> +#define CCM_MTX_01_SHIFT                         16
>> +
>> +#define ISP_CCM_MTX_02_03                        0x609c
>> +#define CCM_MTX_02_MASK                          GENMASK(12, 0)
>> +#define CCM_MTX_03_MASK                          GENMASK(28, 16)
>> +#define CCM_MTX_03_SHIFT                         16
>> +
>> +#define ISP_CCM_MTX_10_11                        0x60A0
>> +#define CCM_MTX_10_MASK                          GENMASK(12, 0)
>> +#define CCM_MTX_11_MASK                          GENMASK(28, 16)
>> +#define CCM_MTX_11_SHIFT                         16
>> +
>> +#define ISP_CCM_MTX_12_13                        0x60A4
>> +#define CCM_MTX_12_MASK                          GENMASK(12, 0)
>> +#define CCM_MTX_13_MASK                          GENMASK(28, 16)
>> +#define CCM_MTX_13_SHIFT                         16
>> +
>> +#define ISP_CCM_MTX_20_21                        0x60A8
>> +#define CCM_MTX_20_MASK                          GENMASK(12, 0)
>> +#define CCM_MTX_21_MASK                          GENMASK(28, 16)
>> +#define CCM_MTX_21_SHIFT                         16
>> +
>> +#define ISP_CCM_MTX_22_23_RS                     0x60Ac
>> +#define CCM_MTX_22_MASK                          GENMASK(12, 0)
>> +#define CCM_MTX_23_MASK                          GENMASK(28, 16)
>> +#define CCM_MTX_23_SHIFT                         16
>> +
>> +#define ISP_PST_GAMMA_MODE                       0x60C0
>> +#define PST_GAMMA_MODE                           BIT(0)
>> +#define PST_GAMMA_MODE_EXT(x)                    ((x) & 0x1)
>> +
>> +#define ISP_PST_GAMMA_LUT_ADDR                   0x60cc
>> +#define PST_GAMMA_LUT_ADDR(x)                    ((x) << 7)
>> +
>> +#define ISP_PST_GAMMA_LUT_DATA                   0x60d0
>> +#define PST_GAMMA_LUT_DATA0(x)                   ((x) & 0xffff)
>> +#define PST_GAMMA_LUT_DATA1(x)                   (((x) & 0xffff) << 16)
>> +
>> +#define DISP0_TOP_TOP_CTRL                       0x8000
>> +#define DISP_CRP2_EN                             BIT(5)
>> +
>> +#define DISP0_TOP_CRP2_START                     0x8004
>> +#define DISP_CRP2_VSTART_MASK                    GENMASK(15, 0)
>> +#define DISP_CRP2_VSTART(x)                      ((x) & 0xffff)
>> +#define DISP_CRP2_HSTART_MASK                    GENMASK(31, 16)
>> +#define DISP_CRP2_HSTART(x)                      (((x) & 0xffff) << 16)
>> +
>> +#define DISP0_TOP_CRP2_SIZE                      0x8008
>> +#define DISP_CRP2_VSIZE_MASK                     GENMASK(15, 0)
>> +#define DISP_CRP2_VSIZE(x)                       ((x) & 0xffff)
>> +#define DISP_CRP2_HSIZE_MASK                     GENMASK(31, 16)
>> +#define DISP_CRP2_HSIZE(x)                       (((x) & 0xffff) << 16)
>> +
>> +#define DISP0_TOP_OUT_SIZE                       0x800c
>> +#define DISP_OUT_VSIZE_MASK                      GENMASK(12, 0)
>> +#define DISP_OUT_HSIZE_MASK                      GENMASK(28, 16)
>> +#define DISP_OUT_HSIZE_SHIFT                     16
>> +
>> +#define ISP_DISP0_TOP_IN_SIZE                    0x804c
>> +#define DISP_TOP_IN_VSIZE(x)                     ((x) & 0x1fff)
>> +#define DISP_TOP_IN_HSIZE(x)                     (((x) & 0x1fff) << 16)
>> +
>> +#define DISP0_PPS_SCALE_EN                       0x8200
>> +#define PPS_VSC_TAP_NUM_MASK                     GENMASK(3, 0)
>> +#define PPS_HSC_TAP_NUM_MASK                     GENMASK(7, 4)
>> +#define PPS_HSC_TAP_NUM_SHIFT                    4
>> +#define PPS_HSC_TAP_NUM_INIT                     4
>> +#define PPS_PREVSC_FLT_NUM_MASK                  GENMASK(11, 8)
>> +#define PPS_PREVSC_FLT_NUM_SHIFT                 8
>> +#define PPS_PREHSC_FLT_NUM_MASK                  GENMASK(15, 12)
>> +#define PPS_PREHSC_FLT_NUM_SHIFT                 12
>> +#define PPS_PREHSC_FLT_NUM_INIT                  8
>> +#define PPS_PREVSC_RATE_MASK                     GENMASK(17, 16)
>> +#define PPS_PREVSC_RATE_SHIFT                    16
>> +#define PPS_PREHSC_RATE_MASK                     GENMASK(19, 18)
>> +#define PPS_PREHSC_RATE_SHIFT                    18
>> +#define PPS_HSC_EN_MASK                          BIT(20)
>> +#define PPS_HSC_EN_SHIFT                         20
>> +#define PPS_VSC_EN_MASK                          BIT(21)
>> +#define PPS_VSC_EN_SHIFT                         21
>> +#define PPS_PREVSC_EN_MASK                       BIT(22)
>> +#define PPS_PREVSC_EN_SHIFT                      22
>> +#define PPS_PREHSC_EN_MASK                       BIT(23)
>> +#define PPS_PREHSC_EN_SHIFT                      23
>> +#define PPS_HSC_NOR_RS_BITS_MASK                 GENMASK(27, 24)
>> +#define PPS_HSC_NOR_RS_BITS_SHIFT                24
>> +#define PPS_HSC_NOR_RS_BITS_INIT                 9
>> +#define PPS_VSC_NOR_RS_BITS_MASK                 GENMASK(31, 28)
>> +#define PPS_VSC_NOR_RS_BITS_SHIFT                28
>> +#define PPS_VSC_NOR_RS_BITS_INIT                 9
>> +
>> +#define DISP0_PPS_PRE_HSCALE_COEF_0              0x8204
>> +#define PPS_PREHSC_LUMA_COEF0_MASK               GENMASK(9, 0)
>> +#define PPS_PREHSC_LUMA_COEF0_INIT               128
>> +#define PPS_PREHSC_LUMA_COEF1_MASK               GENMASK(25, 16)
>> +#define PPS_PREHSC_LUMA_COEF1_SHIFT              16
>> +#define PPS_PREHSC_LUMA_COEF1_INIT               128
>> +
>> +#define DISP0_PPS_PRE_HSCALE_COEF_1              0x8208
>> +#define PPS_PREHSC_LUMA_COEF2_MASK               GENMASK(9, 0)
>> +#define PPS_PREHSC_LUMA_COEF2_INIT               32
>> +#define PPS_PREHSC_LUMA_COEF3_MASK               GENMASK(25, 16)
>> +#define PPS_PREHSC_LUMA_COEF3_SHIFT              16
>> +#define PPS_PREHSC_LUMA_COEF3_INIT               32
>> +
>> +#define DISP0_PPS_VSC_START_PHASE_STEP           0x8224
>> +#define PPS_VSC_FRACTION_PART_MASK               GENMASK(23, 0)
>> +#define PPS_VSC_INTEGER_PART_MASK                GENMASK(27, 24)
>> +#define PPS_VSC_INTEGER_PART_SHIFT               24
>> +
>> +#define DISP0_PPS_HSC_START_PHASE_STEP           0x8230
>> +#define PPS_HSC_FRACTION_PART_MASK               GENMASK(23, 0)
>> +#define PPS_HSC_INTEGER_PART_MASK                GENMASK(27, 24)
>> +#define PPS_HSC_INTEGER_PART_SHIFT               24
>> +
>> +#define DISP0_PPS_444TO422                       0x823c
>> +#define PPS_444TO422_EN_MASK                     BIT(0)
>> +
>> +#define ISP_SCALE0_COEF_IDX_LUMA                 0x8240
>> +#define SCALE_LUMA_COEF_S11_MODE                 BIT(9)
>> +#define SCALE_LUMA_CTYPE(x)                      (((x) & 0x7) << 10)
>> +
>> +#define ISP_SCALE0_COEF_LUMA                     0x8244
>> +#define SCALE_COEF_LUMA_DATA1(x)                 ((x) & 0x7ff)
>> +#define SCALE_COEF_LUMA_DATA0(x)                 (((x) & 0x7ff) << 16)
>> +
>> +#define ISP_SCALE0_COEF_IDX_CHRO                 0x8248
>> +#define SCALE_CHRO_COEF_S11_MODE                 BIT(9)
>> +#define SCALE_CHRO_CTYPE(x)                      (((x) & 0x7) << 10)
>> +
>> +#define ISP_SCALE0_COEF_CHRO                     0x824c
>> +#define SCALE_COEF_CHRO_DATA1(x)                 ((x) & 0x7ff)
>> +#define SCALE_COEF_CHRO_DATA0(x)                 (((x) & 0x7ff) << 16)
>> +
>> +#define ISP_AF_ROI0_WIN01                        0xa00c
>> +#define AF_ROI_XYWIN_00_INIT                     (50 & 0xffff)
>> +#define AF_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
>> +
>> +#define ISP_AF_ROI1_WIN01                        0xa010
>> +#define AF_ROI_XYWIN_10_INIT                     (100 & 0xffff)
>> +#define AF_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
>> +
>> +#define ISP_AF_ROI0_WIN23                        0xa014
>> +#define AF_ROI_XYWIN_02_INIT                     (80 & 0xffff)
>> +#define AF_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
>> +
>> +#define ISP_AF_ROI1_WIN23                        0xa018
>> +#define AF_ROI_XYWIN_12_INIT                     (80 & 0xffff)
>> +#define AF_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
>> +
>> +#define ISP_AF_CTRL                              0xa044
>> +#define AF_CTRL_YPHS_OFST_MASK                   GENMASK(15, 14)
>> +#define AF_CTRL_YPHS_OFST_SHIFT                  14
>> +#define AF_CTRL_XPHS_OFST_MASK                   GENMASK(17, 16)
>> +#define AF_CTRL_XPHS_OFST_SHIFT                  16
>> +
>> +#define ISP_AF_HV_SIZE                           0xa04c
>> +#define AF_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
>> +#define AF_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AF_HV_BLKNUM                         0xa050
>> +#define AF_HV_STAT_VBLK_NUM_MASK                 GENMASK(5, 0)
>> +#define AF_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x3f)
>> +#define AF_HV_STAT_HBLK_NUM_MASK                 GENMASK(21, 16)
>> +#define AF_HV_STAT_HBLK_NUM_SHIFT                16
>> +
>> +#define ISP_AF_EN_CTRL                           0xa054
>> +#define AF_STAT_SELECT                           BIT(21)
>> +#define AF_STAT_SELECT_SHIFT                     21
>> +
>> +#define ISP_RO_AF_GLB_STAT_PCK0                  0xa0e0
>> +#define ISP_RO_AF_GLB_STAT_PCK1                  0xa0e4
>> +#define ISP_RO_AF_GLB_STAT_PCK2                  0xa0e8
>> +#define ISP_RO_AF_GLB_STAT_PCK3                  0xa0eC
>> +#define ISP_AF_IDX_ADDR                          0xa1c0
>> +#define ISP_AF_IDX_DATA                          0xa1c4
>> +#define AF_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
>> +#define AF_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AE_ROI0_WIN01                        0xa40c
>> +#define AE_ROI_XYWIN_00_INIT                     (50 & 0xffff)
>> +#define AE_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
>> +
>> +#define ISP_AE_ROI1_WIN01                        0xa410
>> +#define AE_ROI_XYWIN_10_INIT                     (100 & 0xffff)
>> +#define AE_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
>> +
>> +#define ISP_AE_ROI0_WIN23                        0xa414
>> +#define AE_ROI_XYWIN_02_INIT                     (80 & 0xffff)
>> +#define AE_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
>> +
>> +#define ISP_AE_ROI1_WIN23                        0xa418
>> +#define AE_ROI_XYWIN_12_INIT                     (80 & 0xffff)
>> +#define AE_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
>> +
>> +#define ISP_RO_AE_ROI_STAT_PCK0_0                0xa424
>> +#define ISP_RO_AE_ROI_STAT_PCK1_0                0xa428
>> +#define ISP_RO_AE_ROI_STAT_PCK0_1                0xa42c
>> +#define ISP_RO_AE_ROI_STAT_PCK1_1                0xa430
>> +#define ISP_AE_CTRL                              0xa448
>> +#define AE_CTRL_AE_STAT_LOCAL_MODE               GENMASK(3, 2)
>> +#define AE_CTRL_AE_STAT_LOCAL_MODE_EXT(x)        (((x) >> 2) & 0x3)
>> +#define AE_CTRL_INPUT_2LINE_TOGETHER             BIT(7)
>> +#define AE_CTRL_LUMA_MODE_MASK                   GENMASK(9, 8)
>> +#define AE_CTRL_LUMA_MODE_SHIFT                  8
>> +#define AE_CTRL_LUMA_MODE_FILTER                 2
>> +#define AE_CTRL_YPHS_OFST_MASK                   GENMASK(25, 24)
>> +#define AE_CTRL_YPHS_OFST_SHIFT                  24
>> +#define AE_CTRL_XPHS_OFST_MASK                   GENMASK(27, 26)
>> +#define AE_CTRL_XPHS_OFST_SHIFT                  26
>> +
>> +#define ISP_AE_CRTL2_0                           0xa44c
>> +#define AE_CRTL2_BL12_GRBGI0_MASK                GENMASK(11, 0)
>> +#define AE_CRTL2_GAIN_GRBGI0_MASK                GENMASK(23, 16)
>> +#define AE_CRTL2_GAIN_GRBGI0_SHIFT               16
>> +
>> +#define ISP_AE_CRTL2_1                           0xa450
>> +#define AE_CRTL2_BL12_GRBGI1_MASK                GENMASK(11, 0)
>> +#define AE_CRTL2_GAIN_GRBGI1_MASK                GENMASK(23, 16)
>> +#define AE_CRTL2_GAIN_GRBGI1_SHIFT               16
>> +
>> +#define ISP_AE_CRTL2_2                           0xa454
>> +#define AE_CRTL2_BL12_GRBGI2_MASK                GENMASK(11, 0)
>> +#define AE_CRTL2_GAIN_GRBGI2_MASK                GENMASK(23, 16)
>> +#define AE_CRTL2_GAIN_GRBGI2_SHIFT               16
>> +
>> +#define ISP_AE_CRTL2_3                           0xa458
>> +#define AE_CRTL2_BL12_GRBGI3_MASK                GENMASK(11, 0)
>> +#define AE_CRTL2_GAIN_GRBGI3_MASK                GENMASK(23, 16)
>> +#define AE_CRTL2_GAIN_GRBGI3_SHIFT               16
>> +
>> +#define ISP_AE_CRTL2_4                           0xa45C
>> +#define AE_CRTL2_BL12_GRBGI4_MASK                GENMASK(11, 0)
>> +#define AE_CRTL2_GAIN_GRBGI4_MASK                GENMASK(23, 16)
>> +#define AE_CRTL2_GAIN_GRBGI4_SHIFT               16
>> +
>> +#define ISP_AE_HV_SIZE                           0xa464
>> +#define AE_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
>> +#define AE_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AE_HV_BLKNUM                         0xa468
>> +#define AE_HV_STAT_VBLK_NUM_MASK                 GENMASK(6, 0)
>> +#define AE_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x7f)
>> +#define AE_HV_STAT_HBLK_NUM_MASK                 GENMASK(22, 16)
>> +#define AE_HV_STAT_HBLK_NUM_SHIFT                16
>> +
>> +#define ISP_AE_STAT_THD01                        0xa46c
>> +#define AE_STAT_THRD0_INIT                       (13107 & 0xffff)
>> +#define AE_STAT_THRD1_INIT                       ((26214 & 0xffff) 
>> << 16)
>> +
>> +#define ISP_AE_STAT_THD23                        0xa470
>> +#define AE_STAT_THRD2_INIT                       (39321 & 0xffff)
>> +#define AE_STAT_THRD3_INIT                       ((52428 & 0xffff) 
>> << 16)
>> +
>> +#define ISP_RO_AE_STAT_GPNUM                     0xa49c
>> +#define ISP_AE_IDX_ADDR                          0xa600
>> +#define ISP_AE_IDX_DATA                          0xa604
>> +#define AE_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
>> +#define AE_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AE_BLK_WT_ADDR                       0xa608
>> +#define ISP_AE_BLK_WT_DATA                       0xa60c
>> +#define AE_BLK_WT_DATA0(x)                       ((x) & 0xf)
>> +#define AE_BLK_WT_DATA1(x)                       (((x) & 0xf) << 4)
>> +#define AE_BLK_WT_DATA2(x)                       (((x) & 0xf) << 8)
>> +#define AE_BLK_WT_DATA3(x)                       (((x) & 0xf) << 12)
>> +#define AE_BLK_WT_DATA4(x)                       (((x) & 0xf) << 16)
>> +#define AE_BLK_WT_DATA5(x)                       (((x) & 0xf) << 20)
>> +#define AE_BLK_WT_DATA6(x)                       (((x) & 0xf) << 24)
>> +#define AE_BLK_WT_DATA7(x)                       (((x) & 0xf) << 28)
>> +
>> +#define ISP_AWB_CTRL                             0xa834
>> +#define AWB_CTRL_YPHS_OFST_MASK                  GENMASK(1, 0)
>> +#define AWB_CTRL_XPHS_OFST_MASK                  GENMASK(3, 2)
>> +#define AWB_CTRL_XPHS_OFST_SHIFT                 2
>> +#define AWB_CTRL_STAT_RATIO_MODE                 BIT(4)
>> +
>> +#define ISP_AWB_HV_SIZE                          0xa83c
>> +#define AWB_HV_STAT_VSIZE(x)                     ((x) & 0xffff)
>> +#define AWB_HV_STAT_HSIZE(x)                     (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AWB_STAT_ROI0_WIN01                  0xa80c
>> +#define AWB_ROI_XYWIN_00_INIT                    (32 & 0xffff)
>> +#define AWB_ROI_XYWIN_01_INIT                    ((62 & 0xffff) << 16)
>> +
>> +#define ISP_AWB_STAT_ROI1_WIN01                  0xa810
>> +#define AWB_ROI_XYWIN_10_INIT                    (32 & 0xffff)
>> +#define AWB_ROI_XYWIN_11_INIT                    ((82 & 0xffff) << 16)
>> +
>> +#define ISP_AWB_STAT_ROI0_WIN23                  0xa814
>> +#define AWB_ROI_XYWIN_02_INIT                    (32 & 0xffff)
>> +#define AWB_ROI_XYWIN_03_INIT                    ((42 & 0xffff) << 16)
>> +
>> +#define ISP_AWB_STAT_ROI1_WIN23                  0xa818
>> +#define AWB_ROI_XYWIN_12_INIT                    (64 & 0xffff)
>> +#define AWB_ROI_XYWIN_13_INIT                    ((52 & 0xffff) << 16)
>> +
>> +#define ISP_AWB_HV_BLKNUM                        0xa840
>> +#define AWB_HV_STAT_VBLK_NUM_MASK                GENMASK(5, 0)
>> +#define AWB_HV_STAT_VBLK_NUM_EXT(x)              ((x) & 0x3f)
>> +#define AWB_HV_STAT_HBLK_NUM_MASK                GENMASK(21, 16)
>> +#define AWB_HV_STAT_HBLK_NUM_SHIFT               16
>> +
>> +#define ISP_AWB_STAT_RG                          0xa848
>> +#define AWB_STAT_RG_MIN_MASK                     GENMASK(11, 0)
>> +#define AWB_STAT_RG_MAX_MASK                     GENMASK(27, 16)
>> +#define AWB_STAT_RG_MAX_SHIFT                    16
>> +
>> +#define ISP_AWB_STAT_BG                          0xa84c
>> +#define AWB_STAT_BG_MIN_MASK                     GENMASK(11, 0)
>> +#define AWB_STAT_BG_MAX_MASK                     GENMASK(27, 16)
>> +#define AWB_STAT_BG_MAX_SHIFT                    16
>> +
>> +#define ISP_AWB_STAT_RG_HL                       0xa850
>> +#define AWB_STAT_RG_LOW_MASK                     GENMASK(11, 0)
>> +#define AWB_STAT_RG_HIGH_MASK                    GENMASK(27, 16)
>> +#define AWB_STAT_RG_HIGH_SHIFT                   16
>> +
>> +#define ISP_AWB_STAT_BG_HL                       0xa854
>> +#define AWB_STAT_BG_LOW_MASK                     GENMASK(11, 0)
>> +#define AWB_STAT_BG_HIGH_MASK                    GENMASK(27, 16)
>> +#define AWB_STAT_BG_HIGH_SHIFT                   16
>> +
>> +#define ISP_AWB_STAT_CTRL2                       0xa858
>> +#define AWB_STAT_SATUR_VALID_MASK                BIT(0)
>> +#define AWB_STAT_LOCAL_MODE                      BIT(2)
>> +#define AWB_STAT_LOCAL_MODE_EXT(x)               (((x) >> 2) & 0x1)
>> +#define AWB_STAT_LUMA_DIV_MODE                   GENMASK(4, 3)
>> +#define AWB_STAT_LUMA_DIV_MODE_EXT(x)            (((x) >> 3) & 0x3)
>> +
>> +#define ISP_AWB_STAT_BLC20_0                     0xa85c
>> +#define AWB_STAT_BLC20_GR_MASK                   GENMASK(19, 0)
>> +
>> +#define ISP_AWB_STAT_BLC20_1                     0xa860
>> +#define AWB_STAT_BLC20_R_MASK                    GENMASK(19, 0)
>> +
>> +#define ISP_AWB_STAT_BLC20_2                     0xa864
>> +#define AWB_STAT_BLC20_B_MASK                    GENMASK(19, 0)
>> +
>> +#define ISP_AWB_STAT_BLC20_3                     0xa868
>> +#define AWB_STAT_BLC20_GB_MASK                   GENMASK(19, 0)
>> +
>> +#define ISP_AWB_STAT_GAIN10_0                    0xa86c
>> +#define AWB_STAT_GAIN10_GR_MASK                  GENMASK(9, 0)
>> +
>> +#define ISP_AWB_STAT_GAIN10_1                    0xa870
>> +#define AWB_STAT_GAIN10_R_MASK                   GENMASK(9, 0)
>> +
>> +#define ISP_AWB_STAT_GAIN10_2                    0xa874
>> +#define AWB_STAT_GAIN10_B_MASK                   GENMASK(9, 0)
>> +
>> +#define ISP_AWB_STAT_GAIN10_3                    0xa878
>> +#define AWB_STAT_GAIN10_GB_MASK                  GENMASK(9, 0)
>> +
>> +#define ISP_AWB_STAT_SATUR_CTRL                  0xa884
>> +#define AWB_STAT_SATUR_LOW(x)                    ((x) & 0xffff)
>> +#define AWB_STAT_SATUR_HIGH(x)                   (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AWB_IDX_ADDR                         0xaa00
>> +#define ISP_AWB_IDX_DATA                         0xaa04
>> +#define AWB_IDX_VIDX_DATA(x)                     ((x) & 0xffff)
>> +#define AWB_IDX_HIDX_DATA(x)                     (((x) & 0xffff) << 16)
>> +
>> +#define ISP_AWB_BLK_WT_ADDR                      0xaa08
>> +#define ISP_AWB_BLK_WT_DATA                      0xaa0c
>> +#define AWB_BLK_WT_DATA0(x)                      ((x) & 0xf)
>> +#define AWB_BLK_WT_DATA1(x)                      (((x) & 0xf) << 4)
>> +#define AWB_BLK_WT_DATA2(x)                      (((x) & 0xf) << 8)
>> +#define AWB_BLK_WT_DATA3(x)                      (((x) & 0xf) << 12)
>> +#define AWB_BLK_WT_DATA4(x)                      (((x) & 0xf) << 16)
>> +#define AWB_BLK_WT_DATA5(x)                      (((x) & 0xf) << 20)
>> +#define AWB_BLK_WT_DATA6(x)                      (((x) & 0xf) << 24)
>> +#define AWB_BLK_WT_DATA7(x)                      (((x) & 0xf) << 28)
>> +
>> +#define ISP_WRMIFX3_0_CH0_CTRL0                  0xc400
>> +#define WRMIFX3_CH0_STRIDE_MASK                  GENMASK(28, 16)
>> +#define WRMIFX3_CH0_STRIDE_SHIFT                 16
>> +
>> +#define ISP_WRMIFX3_0_CH0_CTRL1                  0xc404
>> +#define WRMIFX3_CH0_PIX_BITS_MODE_MASK           GENMASK(30, 27)
>> +#define WRMIFX3_CH0_PIX_BITS_MODE_SHIFT          27
>> +#define WRMIFX3_CH0_PIX_BITS_8BITS               1
>> +#define WRMIFX3_CH0_PIX_BITS_16BITS              2
>> +
>> +#define ISP_WRMIFX3_0_CH1_CTRL0                  0xc408
>> +#define WRMIFX3_CH1_STRIDE_MASK                  GENMASK(28, 16)
>> +#define WRMIFX3_CH1_STRIDE_SHIFT                 16
>> +
>> +#define ISP_WRMIFX3_0_CH1_CTRL1                  0xc40c
>> +#define WRMIFX3_CH1_PIX_BITS_MODE_MASK           GENMASK(30, 27)
>> +#define WRMIFX3_CH1_PIX_BITS_MODE_SHIFT          27
>> +#define WRMIFX3_CH1_PIX_BITS_16BITS              2
>> +#define WRMIFX3_CH1_PIX_BITS_32BITS              3
>> +
>> +#define ISP_WRMIFX3_0_CH0_BADDR                  0xc440
>> +/* WRMIF base address need 16 bits alignment */
>> +#define WRMIFX3_CH0_BADDR(x)                     (((x) >> 4) & 
>> 0xffffffff)
>> +
>> +#define ISP_WRMIFX3_0_CH1_BADDR                  0xc444
>> +#define WRMIFX3_CH1_BADDR(x)                     (((x) >> 4) & 
>> 0xffffffff)
>> +
>> +#define ISP_WRMIFX3_0_FMT_SIZE                   0xc464
>> +#define WRMIFX3_FMT_HSIZE(x)                     ((x) & 0xffff)
>> +#define WRMIFX3_FMT_VSIZE(x)                     (((x) & 0xffff) << 16)
>> +
>> +#define ISP_WRMIFX3_0_FMT_CTRL                   0xc468
>> +#define WRMIFX3_FMT_MTX_IBITS_MASK               GENMASK(1, 0)
>> +#define WRMIFX3_FMT_MTX_IBITS_8BIT               0
>> +#define WRMIFX3_FMT_MTX_IBITS_10BIT              1
>> +#define WRMIFX3_FMT_MTX_IBITS_12BIT              2
>> +#define WRMIFX3_FMT_MTX_IBITS_16BIT              3
>> +#define WRMIFX3_FMT_MTX_UV_SWAP_MASK             BIT(2)
>> +#define WRMIFX3_FMT_MTX_UV_SWAP_SHIFT            2
>> +#define WRMIFX3_FMT_MTX_UV_SWAP_VU               0
>> +#define WRMIFX3_FMT_MTX_UV_SWAP_UV               1
>> +#define WRMIFX3_FMT_MTX_PLANE_MASK               GENMASK(5, 4)
>> +#define WRMIFX3_FMT_MTX_PLANE_SHIFT              4
>> +#define WRMIFX3_FMT_MTX_PLANE_X1                 0
>> +#define WRMIFX3_FMT_MTX_PLANE_X2                 1
>> +#define WRMIFX3_FMT_MODE_OUT_MASK                GENMASK(18, 16)
>> +#define WRMIFX3_FMT_MODE_OUT_SHIFT               16
>> +#define WRMIFX3_FMT_MODE_OUT_YUV422              1
>> +#define WRMIFX3_FMT_MODE_OUT_YUV420              2
>> +#define WRMIFX3_FMT_MODE_OUT_Y_ONLY              3
>> +
>> +#define ISP_WRMIFX3_0_WIN_LUMA_H                 0xc420
>> +#define WRMIFX3_WIN_LUMA_HEND_MASK               GENMASK(28, 16)
>> +#define WRMIFX3_WIN_LUMA_HEND_SHIFT              16
>> +#define WRMIFX3_WIN_LUMA_HEND(x)                 ((x) - 1)
>> +
>> +#define ISP_WRMIFX3_0_WIN_LUMA_V                 0xc424
>> +#define WRMIFX3_WIN_LUMA_VEND_MASK               GENMASK(28, 16)
>> +#define WRMIFX3_WIN_LUMA_VEND_SHIFT              16
>> +#define WRMIFX3_WIN_LUMA_VEND(x)                 ((x) - 1)
>> +
>> +#define ISP_WRMIFX3_0_WIN_CHROM_H                0xc428
>> +#define WRMIFX3_WIN_CHROM_HEND_MASK              GENMASK(28, 16)
>> +#define WRMIFX3_WIN_CHROM_HEND_SHIFT             16
>> +#define WRMIFX3_WIN_CHROM_HEND(x)                (((x) >> 1) - 1)
>> +
>> +#define ISP_WRMIFX3_0_WIN_CHROM_V                0xc42c
>> +#define WRMIFX3_WIN_CHROM_VEND_MASK              GENMASK(28, 16)
>> +#define WRMIFX3_WIN_CHROM_VEND_SHIFT             16
>> +#define WRMIFX3_WIN_CHROM_VEND(x)                (((x) >> 1) - 1)
>> +
>> +#define ISP_WRMIFX3_0_CRP_HSIZE                  0xc48c
>> +#define WRMIFX3_CROP_HEND_MASK                   GENMASK(31, 16)
>> +#define WRMIFX3_CROP_HEND_SHIFT                  16
>> +#define WRMIFX3_CROP_HEND(x)                     ((x) - 1)
>> +
>> +#define ISP_WRMIFX3_0_CRP_VSIZE                  0xc490
>> +#define WRMIFX3_CROP_VEND_MASK                   GENMASK(31, 16)
>> +#define WRMIFX3_CROP_VEND_SHIFT                  16
>> +#define WRMIFX3_CROP_VEND(x)                     ((x) - 1)
>> +
>> +#define VIU_DMAWR_BADDR0                         0xc840
>> +#define VIU_DMAWR_AF_BADDR_MASK                  GENMASK(27, 0)
>> +/* AF base address need 16 bits alignment */
>> +#define VIU_DMAWR_AF_BADDR(x)                    ((x) >> 4)
>> +
>> +#define VIU_DMAWR_BADDR1                         0xc844
>> +#define VIU_DMAWR_AWB_BADDR_MASK                  GENMASK(27, 0)
>> +/* AWB base address need 16 bits alignment */
>> +#define VIU_DMAWR_AWB_BADDR(x)                    ((x) >> 4)
>> +
>> +#define VIU_DMAWR_BADDR2                         0xc848
>> +#define VIU_DMAWR_AE_BADDR_MASK                  GENMASK(27, 0)
>> +/* AE base address need 16 bits alignment */
>> +#define VIU_DMAWR_AE_BADDR(x)                    ((x) >> 4)
>> +
>> +#define VIU_DMAWR_SIZE0                          0xc854
>> +#define VIU_DMAWR_SIZE_AF_MASK                   GENMASK(15, 0)
>> +#define VIU_DMAWR_SIZE_AWB_MASK                  GENMASK(31, 16)
>> +#define VIU_DMAWR_SIZE_AWB_SHIFT                 16
>> +
>> +#define VIU_DMAWR_SIZE1                          0xc858
>> +#define VIU_DMAWR_SIZE_AE_MASK                   GENMASK(15, 0)
>> +
>> +#endif
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c 
>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
>> new file mode 100644
>> index 000000000000..01d99b66cb32
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
>> @@ -0,0 +1,768 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/pm_runtime.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +
>> +static const struct c3_isp_mbus_format_info 
>> c3_isp_rsz_mbus_formats[] = {
>> +     /* YUV formats */
>> +     {
>> +             .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
>> +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>> +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_1_5X8,
>> +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>> +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +     }, {
>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_2X8,
>> +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>> +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>> +     },
>> +};
>> +
>> +/* The normal parameters of pps module */
>> +static const int pps_lut_tap4_s11[C3_PPS_TAP4_S11_H_NUM][4] = {
>> +     {  0, 511,   0,   0}, { -5, 511,   5,   0}, {-10, 511, 11,   0},
>> +     {-14, 510,  17,  -1}, {-18, 508,  23,  -1}, {-22, 506, 29,  -1},
>> +     {-25, 503,  36,  -2}, {-28, 500,  43,  -3}, {-32, 496, 51,  -3},
>> +     {-34, 491,  59,  -4}, {-37, 487,  67,  -5}, {-39, 482, 75,  -6},
>> +     {-41, 476,  84,  -7}, {-42, 470,  92,  -8}, {-44, 463, 102,  -9},
>> +     {-45, 456, 111, -10}, {-45, 449, 120, -12}, {-47, 442, 130, -13},
>> +     {-47, 434, 140, -15}, {-47, 425, 151, -17}, {-47, 416, 161, -18},
>> +     {-47, 407, 172, -20}, {-47, 398, 182, -21}, {-47, 389, 193, -23},
>> +     {-46, 379, 204, -25}, {-45, 369, 215, -27}, {-44, 358, 226, -28},
>> +     {-43, 348, 237, -30}, {-43, 337, 249, -31}, {-41, 326, 260, -33},
>> +     {-40, 316, 271, -35}, {-39, 305, 282, -36}, {-37, 293, 293, -37}
>> +};
>> +
>> +static const struct c3_isp_mbus_format_info
>> +*rsz_find_format_by_code(u32 code, u32 pad)
>> +{
>> +     int i;
>> +
>> +     for (i = 0; i < ARRAY_SIZE(c3_isp_rsz_mbus_formats); i++) {
>> +             const struct c3_isp_mbus_format_info *info =
>> +                     &c3_isp_rsz_mbus_formats[i];
>> +
>> +             if (info->mbus_code == code && info->pads & BIT(pad))
>> +                     return info;
>> +     }
>> +
>> +     return NULL;
>> +}
>> +
>> +static void c3_isp_rsz_cfg_fmt(struct c3_isp_resizer *rsz,
>> +                            struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +
>> +     fmt = v4l2_subdev_state_get_format(state, 
>> C3_ISP_RESIZER_PAD_SINK);
>> +
>> +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_DISP0_TOP_IN_SIZE, 
>> rsz->id),
>> +                  DISP_TOP_IN_HSIZE(fmt->width) | 
>> DISP_TOP_IN_VSIZE(fmt->height));
>> +}
>> +
>> +static void c3_isp_rsz_crop_enable(struct c3_isp_resizer *rsz,
>> +                                struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_rect *crop;
>> +
>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>> +
>> +     c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_START, rsz->id),
>> +                  DISP_CRP2_VSTART(crop->top) | 
>> DISP_CRP2_HSTART(crop->left));
>> +     c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_SIZE, rsz->id),
>> +                  DISP_CRP2_VSIZE(crop->height) | 
>> DISP_CRP2_HSIZE(crop->width));
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, 
>> rsz->id),
>> +                        DISP_CRP2_EN, DISP_CRP2_EN);
>> +}
>> +
>> +static void c3_isp_rsz_pps_size(struct c3_isp_resizer *rsz,
>> +                             struct c3_isp_pps_io_size *io_size)
>> +{
>> +     int thsize = io_size->thsize;
>> +     int tvsize = io_size->tvsize;
>> +     u32 ohsize = io_size->ohsize;
>> +     u32 ovsize = io_size->ovsize;
>> +     u32 ihsize = io_size->ihsize;
>> +     u32 max_hsize = io_size->max_hsize;
>> +     int step_h_integer, step_v_integer;
>> +     int step_h_fraction, step_v_fraction;
>> +     int yuv444to422_en;
>> +
>> +     /* Calculate the integer part of horizonal scaler step */
>> +     step_h_integer = thsize / ohsize;
>> +
>> +     /* Calculate the vertical part of horizonal scaler step */
>> +     step_v_integer = tvsize / ovsize;
>> +
>> +     /*
>> +      * Calculate the fraction part of horizonal scaler step.
>> +      * step_h_fraction = (source / dest) * 2^24,
>> +      * so step_h_fraction = ((source << 12) / dest) << 12.
>> +      */
>> +     step_h_fraction = ((thsize << 12) / ohsize) << 12;
>> +
>> +     /*
>> +      * Calculate the fraction part of vertical scaler step
>> +      * step_v_fraction = (source / dest) * 2^24,
>> +      * so step_v_fraction = ((source << 12) / dest) << 12.
>> +      */
>> +     step_v_fraction = ((tvsize << 12) / ovsize) << 12;
>> +
>> +     yuv444to422_en = ihsize > (max_hsize / 2) ? 1 : 0;
>> +
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_444TO422, 
>> rsz->id),
>> +                        PPS_444TO422_EN_MASK, yuv444to422_en);
>> +
>> +     c3_isp_update_bits(rsz->isp, 
>> C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>> +                        PPS_VSC_FRACTION_PART_MASK, step_v_fraction);
>> +     c3_isp_update_bits(rsz->isp, 
>> C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>> +                        PPS_VSC_INTEGER_PART_MASK,
>> +                        step_v_integer << PPS_VSC_INTEGER_PART_SHIFT);
>> +
>> +     c3_isp_update_bits(rsz->isp, 
>> C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>> +                        PPS_HSC_FRACTION_PART_MASK, step_h_fraction);
>> +     c3_isp_update_bits(rsz->isp, 
>> C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>> +                        PPS_HSC_INTEGER_PART_MASK,
>> +                        step_h_integer << PPS_HSC_INTEGER_PART_SHIFT);
>> +
>> +     c3_isp_update_bits(rsz->isp, 
>> C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
>> +                        PPS_PREHSC_LUMA_COEF0_MASK, 
>> PPS_PREHSC_LUMA_COEF0_INIT);
>> +     c3_isp_update_bits(rsz->isp, 
>> C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
>> +                        PPS_PREHSC_LUMA_COEF1_MASK,
>> +                        PPS_PREHSC_LUMA_COEF1_INIT << 
>> PPS_PREHSC_LUMA_COEF1_SHIFT);
>> +
>> +     c3_isp_update_bits(rsz->isp, 
>> C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
>> +                        PPS_PREHSC_LUMA_COEF2_MASK, 
>> PPS_PREHSC_LUMA_COEF2_INIT);
>> +     c3_isp_update_bits(rsz->isp, 
>> C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
>> +                        PPS_PREHSC_LUMA_COEF3_MASK,
>> +                        PPS_PREHSC_LUMA_COEF3_INIT << 
>> PPS_PREHSC_LUMA_COEF3_SHIFT);
>> +}
>> +
>> +static void c3_isp_rsz_pps_lut(struct c3_isp_resizer *rsz, u32 ctype)
>> +{
>> +     int i;
>> +
>> +     /*
>> +      * Default value of this register is 0,
>> +      * so only need to set SCALE_LUMA_COEF_S11_MODE
>> +      * and SCALE_LUMA_CTYPE.
>> +      * This register needs to be written in one time.
>> +      */
>> +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_LUMA, 
>> rsz->id),
>> +                  SCALE_LUMA_COEF_S11_MODE | SCALE_LUMA_CTYPE(ctype));
>> +
>> +     for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
>> +             c3_isp_write(rsz->isp, 
>> C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>> + SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
>> + SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
>> +             c3_isp_write(rsz->isp, 
>> C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>> + SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
>> + SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][3]));
>> +     }
>> +
>> +     /*
>> +      * Default value of this register is 0,
>> +      * so only need to set SCALE_CHRO_COEF_S11_MODE
>> +      * and SCALE_CHRO_CTYPE.
>> +      * This register needs to be written in one time.
>> +      */
>> +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_CHRO, 
>> rsz->id),
>> +                  SCALE_CHRO_COEF_S11_MODE | SCALE_CHRO_CTYPE(ctype));
>> +
>> +     for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
>> +             c3_isp_write(rsz->isp, 
>> C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>> + SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
>> + SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
>> +             c3_isp_write(rsz->isp, 
>> C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>> + SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
>> + SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][3]));
>> +     }
>> +}
>> +
>> +static void c3_isp_rsz_pps_disable(struct c3_isp_resizer *rsz)
>> +{
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_HSC_EN_MASK, 0);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_VSC_EN_MASK, 0);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_PREVSC_EN_MASK, 0);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_PREHSC_EN_MASK, 0);
>> +}
>> +
>> +static int c3_isp_rsz_pps_enable(struct c3_isp_resizer *rsz,
>> +                              struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_rect *crop;
>> +     struct v4l2_rect *cmps;
>> +     int max_hsize;
>> +     int hsc_en, vsc_en;
>> +     int preh_en, prev_en;
>> +     u32 reg_prehsc_rate;
>> +     u32 reg_prevsc_flt_num;
>> +     int pre_vscale_max_hsize;
>> +     u32 ihsize_after_pre_hsc;
>> +     u32 ihsize_after_pre_hsc_alt;
>> +     u32 reg_vsc_tap_num_alt;
>> +     u32 ihsize;
>> +     u32 ivsize;
>> +     struct c3_isp_pps_io_size io_size;
>> +
>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>> +     cmps = v4l2_subdev_state_get_compose(state, 
>> C3_ISP_RESIZER_PAD_SINK);
>> +
>> +     ihsize = crop->width;
>> +     ivsize = crop->height;
>> +
>> +     hsc_en = (ihsize == cmps->width) ? C3_SCALE_DIS : C3_SCALE_EN;
>> +     vsc_en = (ivsize == cmps->height) ? C3_SCALE_DIS : C3_SCALE_EN;
>> +
>> +     /* Disable pps when there no need to use pps */
>> +     if (!hsc_en && !vsc_en) {
>> +             c3_isp_rsz_pps_disable(rsz);
>> +             return 0;
>> +     }
>> +
>> +     /*
>> +      * Pre-scale needs to be enable
>> +      * if the down scaling factor exceeds 4.
>> +      */
>> +     preh_en = (ihsize > cmps->width * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
>> +     prev_en = (ivsize > cmps->height * 4) ? C3_SCALE_EN : 
>> C3_SCALE_DIS;
>> +
>> +     if (rsz->id == C3_ISP_RSZ_2) {
>> +             max_hsize = C3_ISP_MAX_WIDTH;
>> +             /*
>> +              * Set vertical tap number and
>> +              * the max hsize of pre-vertical scale.
>> +              */
>> +             reg_prevsc_flt_num = 4;
>> +             pre_vscale_max_hsize = max_hsize / 2;
>> +     } else {
>> +             max_hsize = C3_ISP_DEFAULT_WIDTH;
>> +             preh_en  = (ihsize > max_hsize) ? C3_SCALE_EN : 
>> C3_SCALE_DIS;
>> +             /*
>> +              * Set vertical tap number and
>> +              * the max hsize of pre-vertical scale.
>> +              */
>> +             if (ihsize > (max_hsize / 2) &&
>> +                 ihsize <= max_hsize && prev_en) {
>> +                     reg_prevsc_flt_num = 2;
>> +                     pre_vscale_max_hsize = max_hsize;
>> +             } else {
>> +                     reg_prevsc_flt_num = 4;
>> +                     pre_vscale_max_hsize = max_hsize / 2;
>> +             }
>> +     }
>> +
>> +     /*
>> +      * Set pre-horizonal scale rate and
>> +      * the hsize of after pre-horizonal scale.
>> +      */
>> +     if (preh_en) {
>> +             reg_prehsc_rate = 1;
>> +             ihsize_after_pre_hsc = DIV_ROUND_UP(ihsize, 2);
>> +     } else {
>> +             reg_prehsc_rate = 0;
>> +             ihsize_after_pre_hsc = ihsize;
>> +     }
>> +
>> +     /* Change pre-horizonal scale rate */
>> +     if (prev_en && ihsize_after_pre_hsc >= pre_vscale_max_hsize)
>> +             reg_prehsc_rate += 1;
>> +
>> +     /* Set the actual hsize of after pre-horizonal scale */
>> +     if (preh_en)
>> +             ihsize_after_pre_hsc_alt =
>> +                     DIV_ROUND_UP(ihsize, 1 << reg_prehsc_rate);
>> +     else
>> +             ihsize_after_pre_hsc_alt = ihsize;
>> +
>> +     /* Set vertical scaler bank length */
>> +     if (ihsize_after_pre_hsc_alt <= (max_hsize / 2))
>> +             reg_vsc_tap_num_alt = 4;
>> +     else if (ihsize_after_pre_hsc_alt <= max_hsize)
>> +             reg_vsc_tap_num_alt = prev_en ? 2 : 4;
>> +     else
>> +             reg_vsc_tap_num_alt = prev_en ? 4 : 2;
>> +
>> +     io_size.thsize = ihsize_after_pre_hsc_alt;
>> +     io_size.tvsize = prev_en ? DIV_ROUND_UP(ivsize, 2) : ivsize;
>> +     io_size.ohsize = cmps->width;
>> +     io_size.ovsize = cmps->height;
>> +     io_size.ihsize = ihsize;
>> +     io_size.max_hsize = max_hsize;
>> +
>> +     c3_isp_rsz_pps_size(rsz, &io_size);
>> +     c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_0);
>> +     c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_2);
>> +
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_VSC_TAP_NUM_MASK, reg_vsc_tap_num_alt);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_HSC_TAP_NUM_MASK,
>> +                        PPS_HSC_TAP_NUM_INIT << PPS_HSC_TAP_NUM_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_PREVSC_FLT_NUM_MASK,
>> +                        reg_prevsc_flt_num << 
>> PPS_PREVSC_FLT_NUM_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_PREHSC_FLT_NUM_MASK,
>> +                        PPS_PREHSC_FLT_NUM_INIT << 
>> PPS_PREHSC_FLT_NUM_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_PREVSC_RATE_MASK, prev_en << 
>> PPS_PREVSC_RATE_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_PREHSC_RATE_MASK, reg_prehsc_rate << 
>> PPS_PREHSC_RATE_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_HSC_EN_MASK, hsc_en << PPS_HSC_EN_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_VSC_EN_MASK, vsc_en << PPS_VSC_EN_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_PREVSC_EN_MASK, prev_en << 
>> PPS_PREVSC_EN_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_PREHSC_EN_MASK, preh_en << 
>> PPS_PREHSC_EN_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_HSC_NOR_RS_BITS_MASK,
>> +                        PPS_HSC_NOR_RS_BITS_INIT << 
>> PPS_HSC_NOR_RS_BITS_SHIFT);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, 
>> rsz->id),
>> +                        PPS_VSC_NOR_RS_BITS_MASK,
>> +                        PPS_VSC_NOR_RS_BITS_INIT << 
>> PPS_VSC_NOR_RS_BITS_SHIFT);
>> +
>> +     return 0;
>> +}
>> +
>> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz)
>> +{
>> +     struct v4l2_subdev_state *state;
>> +
>> +     state = v4l2_subdev_lock_and_get_active_state(&rsz->sd);
>> +
>> +     c3_isp_rsz_cfg_fmt(rsz, state);
>> +     c3_isp_rsz_crop_enable(rsz, state);
>> +     c3_isp_rsz_pps_enable(rsz, state);
>> +
>> +     c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN,
>> +                        TOP_DISP_EN(rsz->id), TOP_DISP_EN(rsz->id));
>> +
>> +     v4l2_subdev_unlock_state(state);
>> +}
>> +
>> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
>> +{
>> +     c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, 
>> TOP_DISP_EN(rsz->id), 0);
>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, 
>> rsz->id),
>> +                        DISP_CRP2_EN, 0x0);
>> +
>> +     c3_isp_rsz_pps_disable(rsz);
>
>
> I would suggest enable_streams() / disable_streams() for these 
> subdevices too, which gives you the
> state already
>

OK, will add enable_streams() and disable_streams() interfaces.

>> +}
>> +
>> +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *state,
>> +                               struct v4l2_subdev_krouting *routing)
>> +{
>> +     static const struct v4l2_mbus_framefmt format = {
>> +             .width = C3_ISP_DEFAULT_WIDTH,
>> +             .height = C3_ISP_DEFAULT_HEIGHT,
>> +             .code = C3_ISP_RSZ_DEF_PAD_FMT,
>> +             .field = V4L2_FIELD_NONE,
>> +             .colorspace = V4L2_COLORSPACE_SRGB,
>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>> +             .xfer_func = V4L2_XFER_FUNC_SRGB,
>> +     };
>> +     int ret;
>> +
>> +     ret = v4l2_subdev_routing_validate(sd, routing, 
>> V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, 
>> &format);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_subdev_route routes;
>> +     struct v4l2_subdev_krouting routing;
>> +
>> +     routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
>> +     routes.sink_stream = 0;
>> +     routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
>> +     routes.source_stream = 0;
>> +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>> +
>> +     routing.num_routes = 1;
>> +     routing.routes = &routes;
>> +
>> +     return c3_isp_rsz_cfg_routing(sd, state, &routing);
>> +}
>> +
>> +static int c3_isp_rsz_set_routing(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *state,
>> +                               enum v4l2_subdev_format_whence which,
>> +                               struct v4l2_subdev_krouting *routing)
>> +{
>> +     bool is_streaming = v4l2_subdev_is_streaming(sd);
>> +
>> +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>> +             return -EBUSY;
>> +
>> +     return c3_isp_rsz_cfg_routing(sd, state, routing);
>> +}
>> +
>> +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
>> +                                  struct v4l2_subdev_state *state,
>> +                                  struct v4l2_subdev_mbus_code_enum 
>> *code)
>> +{
>> +     if (code->index >= ARRAY_SIZE(c3_isp_rsz_mbus_formats))
>> +             return -EINVAL;
>> +
>> +     code->code = c3_isp_rsz_mbus_formats[code->index].mbus_code;
>> +
>> +     return 0;
>> +}
>> +
> If there's only a single sink and source pad I don't think the routing 
> functions are needed


Will remove this function.

>> +static void c3_isp_rsz_set_sink_fmt(struct v4l2_subdev_state *state,
>> +                                 struct v4l2_subdev_format *format)
>> +{
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>> +     struct v4l2_rect *sink_crop;
>> +     struct v4l2_rect *sink_cmps;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +     sink_crop = v4l2_subdev_state_get_crop(state, format->pad);
>> +     sink_cmps = v4l2_subdev_state_get_compose(state, format->pad);
>> +
>> +     isp_fmt = rsz_find_format_by_code(format->format.code, 
>> format->pad);
>> +     if (!isp_fmt)
>> +             sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>> +     else
>> +             sink_fmt->code = format->format.code;
>> +
>> +     sink_fmt->width = clamp_t(u32, format->format.width,
>> +                               C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>> +     sink_fmt->height = clamp_t(u32, format->format.height,
>> +                                C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>> +
>> +     sink_crop->width = sink_fmt->width;
>> +     sink_crop->height = sink_fmt->height;
>> +     sink_crop->left = 0;
>> +     sink_crop->top = 0;
>> +
>> +     sink_cmps->width = sink_crop->width;
>> +     sink_cmps->height = sink_crop->height;
>> +     sink_cmps->left = 0;
>> +     sink_cmps->top = 0;
>> +
>> +     format->format = *sink_fmt;
>> +}
> You should propagate to the source pad here too


Will set the source pad here.

>> +
>> +static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state,
>> +                                   struct v4l2_subdev_format *format)
>> +{
>> +     const struct c3_isp_mbus_format_info *rsz_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
>> +     struct v4l2_rect *sink_crop;
>> +     struct v4l2_rect *sink_cmps;
>> +
>> +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>> +     sink_cmps = v4l2_subdev_state_get_compose(state, 
>> C3_ISP_RESIZER_PAD_SINK);
>> +     sink_crop = v4l2_subdev_state_get_crop(state, 
>> C3_ISP_RESIZER_PAD_SINK);
>> +
>> +     rsz_fmt = rsz_find_format_by_code(format->format.code, 
>> format->pad);
>> +     if (!rsz_fmt)
>> +             src_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>> +     else
>> +             src_fmt->code = format->format.code;
> Can the ISP convert between the YUV formats? If not I'd expect it the 
> source pad to take the same
> mbus code as the sink pad format


ISP can't convert between the YUV formats.

Will set the same mbus code as the sink pad format.

>> +
>> +     src_fmt->width = clamp_t(u32, format->format.width,
>> +                              C3_ISP_MIN_WIDTH, sink_crop->width);
>> +     src_fmt->height = clamp_t(u32, format->format.height,
>> +                               C3_ISP_MIN_HEIGHT, sink_crop->height);
>> +
>> +     /* The sink compose size must be same with the source size. */
>> +     sink_cmps->width = src_fmt->width;
>> +     sink_cmps->height = src_fmt->height;
> It should work the other way round - the .set_fmt() for the sink pad 
> should update the source pad
> format instead


Will solve this issue.

>> +
>> +     format->format = *src_fmt;
>> +}
>> +
>> +static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd,
>> +                           struct v4l2_subdev_state *state,
>> +                           struct v4l2_subdev_format *format)
>> +{
>> +     if (format->pad == C3_ISP_RESIZER_PAD_SINK) {
>> +             c3_isp_rsz_set_sink_fmt(state, format);
>> +     } else if (format->pad == C3_ISP_RESIZER_PAD_SOURCE) {
>> +             c3_isp_rsz_set_source_fmt(state, format);
>> +     } else {
>> +             dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
>> +             return -ENOTTY;
>> +     }
> v4l2-core should already have checked that I believe


Will remove the "else {}" branch.

>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd,
>> +                                 struct v4l2_subdev_state *state,
>> +                                 struct v4l2_subdev_selection *sel)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +     struct v4l2_rect *crop;
>> +     struct v4l2_rect *cmps;
>> +
>> +     if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
>> +             return -EINVAL;
>> +
>> +     switch (sel->target) {
>> +     case V4L2_SEL_TGT_CROP_BOUNDS:
>> +             fmt = v4l2_subdev_state_get_format(state, sel->pad);
>> +             sel->r.width = fmt->width;
>> +             sel->r.height = fmt->height;
>> +             sel->r.left = 0;
>> +             sel->r.top = 0;
>> +             break;
>> +     case V4L2_SEL_TGT_COMPOSE_BOUNDS:
>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>> +             sel->r.width = crop->width;
>> +             sel->r.height = crop->height;
>> +             sel->r.left = 0;
>> +             sel->r.top = 0;
>> +             break;
>> +     case V4L2_SEL_TGT_CROP:
>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>> +             sel->r = *crop;
>> +             break;
>> +     case V4L2_SEL_TGT_COMPOSE:
>> +             cmps = v4l2_subdev_state_get_compose(state, sel->pad);
>> +             sel->r = *cmps;
>> +             break;
>> +     default:
>> +             return -EINVAL;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd,
>> +                                 struct v4l2_subdev_state *state,
>> +                                 struct v4l2_subdev_selection *sel)
>> +{
>> +     struct v4l2_mbus_framefmt *fmt;
>> +     struct v4l2_rect *crop;
>> +     struct v4l2_rect *cmps;
>> +
>> +     if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
>> +             return -EINVAL;
>> +
>> +     switch (sel->target) {
>> +     case V4L2_SEL_TGT_CROP:
>> +             fmt = v4l2_subdev_state_get_format(state, sel->pad);
>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>> +
>> +             sel->r.left = clamp_t(s32, sel->r.left, 0, fmt->width - 
>> 1);
>> +             sel->r.top = clamp_t(s32, sel->r.top, 0, fmt->height - 1);
>> +             sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH,
>> +                                  fmt->width - sel->r.left);
>> +             sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT,
>> +                                   fmt->height - sel->r.top);
>> +
>> +             crop->width = ALIGN(sel->r.width, 2);
>> +             crop->height = ALIGN(sel->r.height, 2);
>> +             crop->left = sel->r.left;
>> +             crop->top = sel->r.top;
>> +
>> +             sel->r = *crop;
> The size should be propagated to the compose rectangle and source 
> format too.


Will  set the compose rectangle and source format with this size.

>> +             break;
>> +     case V4L2_SEL_TGT_COMPOSE:
>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>> +             cmps = v4l2_subdev_state_get_compose(state, sel->pad);
>> +
>> +             sel->r.left = 0;
>> +             sel->r.top = 0;
>> +             sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH, 
>> crop->width);
>> +             sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT, 
>> crop->height);
>> +
>> +             cmps->width = ALIGN(sel->r.width, 2);
>> +             cmps->height = ALIGN(sel->r.height, 2);
>> +             cmps->left = sel->r.left;
>> +             cmps->top = sel->r.top;
>> +
>> +             sel->r = *cmps;
>> +
>> +             fmt = v4l2_subdev_state_get_format(state, 
>> C3_ISP_RESIZER_PAD_SOURCE);
>> +             fmt->width = cmps->width;
>> +             fmt->height = cmps->height;
>> +             break;
> This looks ok


Thanks.

>> +     default:
>> +             return -EINVAL;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_rsz_init_state(struct v4l2_subdev *sd,
>> +                              struct v4l2_subdev_state *state)
>> +{
>> +     struct v4l2_mbus_framefmt *sink_fmt;
>> +     struct v4l2_mbus_framefmt *src_fmt;
>> +     struct v4l2_rect *crop;
>> +     struct v4l2_rect *cmps;
>> +
>> +     sink_fmt = v4l2_subdev_state_get_format(state, 
>> C3_ISP_RESIZER_PAD_SINK);
>> +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>> +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>> +     sink_fmt->field = V4L2_FIELD_NONE;
>> +     sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>> +     sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>> +     sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>> +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>> +     sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>> +
>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>> +     crop->width = C3_ISP_DEFAULT_WIDTH;
>> +     crop->height = C3_ISP_DEFAULT_HEIGHT;
>> +     crop->left = 0;
>> +     crop->top = 0;
>> +
>> +     cmps = v4l2_subdev_state_get_compose(state, 
>> C3_ISP_RESIZER_PAD_SINK);
>> +     cmps->width = C3_ISP_DEFAULT_WIDTH;
>> +     cmps->height = C3_ISP_DEFAULT_HEIGHT;
>> +     cmps->left = 0;
>> +     cmps->top = 0;
>> +
>> +     src_fmt = v4l2_subdev_state_get_format(state, 
>> C3_ISP_RESIZER_PAD_SOURCE);
>> +     *src_fmt = *sink_fmt;
>> +
>> +     return c3_isp_rsz_init_routing(sd, state);
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops c3_isp_rsz_pad_ops = {
>> +     .enum_mbus_code = c3_isp_rsz_enum_mbus_code,
>> +     .get_fmt = v4l2_subdev_get_fmt,
>> +     .set_fmt = c3_isp_rsz_set_fmt,
>> +     .get_selection = c3_isp_rsz_get_selection,
>> +     .set_selection = c3_isp_rsz_set_selection,
>> +     .set_routing = c3_isp_rsz_set_routing,
>> +};
>> +
>> +static const struct v4l2_subdev_ops c3_isp_rsz_subdev_ops = {
>> +     .pad = &c3_isp_rsz_pad_ops,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops c3_isp_rsz_internal_ops 
>> = {
>> +     .init_state = c3_isp_rsz_init_state,
>> +};
>> +
>> +/* Media entity operations */
>> +static const struct media_entity_operations c3_isp_rsz_entity_ops = {
>> +     .link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +static int c3_isp_rsz_register(struct c3_isp_resizer *rsz)
>> +{
>> +     struct v4l2_subdev *sd = &rsz->sd;
>> +     int ret;
>> +
>> +     v4l2_subdev_init(sd, &c3_isp_rsz_subdev_ops);
>> +     sd->owner = THIS_MODULE;
>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +     sd->internal_ops = &c3_isp_rsz_internal_ops;
>> +     snprintf(sd->name, sizeof(sd->name), "isp-resizer%u", rsz->id);
>> +
>> +     sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
>> +     sd->entity.ops = &c3_isp_rsz_entity_ops;
>> +
>> +     sd->dev = rsz->isp->dev;
>> +     v4l2_set_subdevdata(sd, rsz);
>> +
>> +     rsz->pads[C3_ISP_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>> +     rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>> +     ret = media_entity_pads_init(&sd->entity, 
>> C3_ISP_RESIZER_PAD_MAX, rsz->pads);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = v4l2_subdev_init_finalize(sd);
>> +     if (ret)
>> +             goto err_entity_cleanup;
>> +
>> +     ret = v4l2_device_register_subdev(&rsz->isp->v4l2_dev, sd);
>> +     if (ret)
>> +             goto err_subdev_cleanup;
>> +
>> +     return 0;
>> +
>> +err_subdev_cleanup:
>> +     v4l2_subdev_cleanup(sd);
>> +err_entity_cleanup:
>> +     media_entity_cleanup(&sd->entity);
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_rsz_unregister(struct c3_isp_resizer *rsz)
>> +{
>> +     struct v4l2_subdev *sd = &rsz->sd;
>> +
>> +     v4l2_device_unregister_subdev(sd);
>> +     v4l2_subdev_cleanup(sd);
>> +     media_entity_cleanup(&sd->entity);
>> +}
>> +
>> +int c3_isp_resizers_register(struct c3_isp_device *isp)
>> +{
>> +     u32 i;
>> +     int ret;
>> +
>> +     for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
>> +             struct c3_isp_resizer *rsz = &isp->resizers[i];
>> +
>> +             rsz->id = i;
>> +             rsz->isp = isp;
>> +
>> +             if (rsz->id == C3_ISP_RSZ_0)
>> +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_0];
>> +             else if (rsz->id == C3_ISP_RSZ_1)
>> +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_1];
>> +             else
>> +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_2];
>> +
>> +             ret = c3_isp_rsz_register(rsz);
>> +             if (ret) {
>> +                     rsz->isp = NULL;
>> +                     c3_isp_resizers_unregister(isp);
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +void c3_isp_resizers_unregister(struct c3_isp_device *isp)
>> +{
>> +     u32 i;
>> +
>> +     for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
>> +             struct c3_isp_resizer *rsz = &isp->resizers[i];
>> +
>> +             if (rsz->isp)
>> +                     c3_isp_rsz_unregister(rsz);
>> +     };
>> +}
>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c 
>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
>> new file mode 100644
>> index 000000000000..72024442d48f
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
>> @@ -0,0 +1,488 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/cleanup.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-event.h>
>> +#include <media/v4l2-ioctl.h>
>> +#include <media/v4l2-mc.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +
>> +#include "c3-isp-common.h"
>> +#include "c3-isp-regs.h"
>> +#include "include/uapi/c3-isp-config.h"
>> +
>> +/* Hardware configuration */
>> +
>> +static void c3_isp_stats_af_init(struct c3_isp_stats *stats)
>> +{
>> +     c3_isp_write(stats->isp, ISP_AF_ROI0_WIN01,
>> +                  AF_ROI_XYWIN_00_INIT | AF_ROI_XYWIN_01_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AF_ROI0_WIN23,
>> +                  AF_ROI_XYWIN_02_INIT | AF_ROI_XYWIN_03_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AF_ROI1_WIN01,
>> +                  AF_ROI_XYWIN_10_INIT | AF_ROI_XYWIN_11_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AF_ROI1_WIN23,
>> +                  AF_ROI_XYWIN_12_INIT | AF_ROI_XYWIN_13_INIT);
>> +
>> +     /* 0: old statistics output, 1: new statistics output. */
>> +     c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL,
>> +                        AF_STAT_SELECT, 0x1 << AF_STAT_SELECT_SHIFT);
>> +}
>> +
>> +static void c3_isp_stats_ae_init(struct c3_isp_stats *stats)
>> +{
>> +     c3_isp_write(stats->isp, ISP_AE_ROI0_WIN01,
>> +                  AE_ROI_XYWIN_00_INIT | AE_ROI_XYWIN_01_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AE_ROI0_WIN23,
>> +                  AE_ROI_XYWIN_02_INIT | AE_ROI_XYWIN_03_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AE_ROI1_WIN01,
>> +                  AE_ROI_XYWIN_10_INIT | AE_ROI_XYWIN_11_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AE_ROI1_WIN23,
>> +                  AE_ROI_XYWIN_12_INIT | AE_ROI_XYWIN_13_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AE_STAT_THD01,
>> +                  AE_STAT_THRD0_INIT | AE_STAT_THRD1_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AE_STAT_THD23,
>> +                  AE_STAT_THRD2_INIT | AE_STAT_THRD3_INIT);
>> +
>> +     /* Set 0 when ae_stat_switch is not 0 */
>> +     c3_isp_update_bits(stats->isp, ISP_AE_CTRL,
>> +                        AE_CTRL_INPUT_2LINE_TOGETHER, 0);
>> +
>> +     /* Configure ae luma mode */
>> +     c3_isp_update_bits(stats->isp, ISP_AE_CTRL, 
>> AE_CTRL_LUMA_MODE_MASK,
>> +                        AE_CTRL_LUMA_MODE_FILTER << 
>> AE_CTRL_LUMA_MODE_SHIFT);
>> +}
>> +
>> +static void c3_isp_stats_awb_init(struct c3_isp_stats *stats)
>> +{
>> +     /* Initialize the awb statistics rectangle of image */
>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN01,
>> +                  AWB_ROI_XYWIN_00_INIT | AWB_ROI_XYWIN_01_INIT);
>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN23,
>> +                  AWB_ROI_XYWIN_02_INIT | AWB_ROI_XYWIN_03_INIT);
>> +
>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN01,
>> +                  AWB_ROI_XYWIN_10_INIT | AWB_ROI_XYWIN_11_INIT);
>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN23,
>> +                  AWB_ROI_XYWIN_12_INIT | AWB_ROI_XYWIN_13_INIT);
>> +}
>> +
>> +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
>> +{
>> +     struct c3_isp_device *isp = stats->isp;
>> +     struct c3_isp_stats_info *stats_info = stats->buff->vaddr;
>> +     u32 awb_dma_size = sizeof(stats_info->awb_stats);
>> +     u32 ae_dma_size = sizeof(stats_info->ae_stats);
> You could just use sizeof(struct awb_stats_info) and not bother with 
> the vaddr at all...but then
> again perhaps doing it this way is less prone to future mistakes from 
> expansion of struct
> c3_isp_stats_info. I vacillate a little, so I leave it up to you.


OK, Will check this.

>> +     u32 awb_dma_addr = stats->buff->paddr;
>> +     u32 af_dma_addr;
>> +     u32 ae_dma_addr;
>> +
>> +     ae_dma_addr = awb_dma_addr + awb_dma_size;
>> +     af_dma_addr = ae_dma_addr + ae_dma_size;
>> +
>> +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR0, VIU_DMAWR_AF_BADDR_MASK,
>> +                        VIU_DMAWR_AF_BADDR(af_dma_addr));
>> +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR1, 
>> VIU_DMAWR_AWB_BADDR_MASK,
>> +                        VIU_DMAWR_AWB_BADDR(awb_dma_addr));
>> +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR2, VIU_DMAWR_AE_BADDR_MASK,
>> +                        VIU_DMAWR_AE_BADDR(ae_dma_addr));
>> +}
>> +
>> +static void c3_isp_stats_enable(struct c3_isp_stats *stats)
>> +{
>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>> +                        TOP_3A_AE_STAT_EN, TOP_3A_AE_STAT_EN);
>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>> +                        TOP_3A_AWB_STAT_EN, TOP_3A_AWB_STAT_EN);
>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>> +                        TOP_3A_AF_STAT_EN, TOP_3A_AF_STAT_EN);
>> +}
>> +
>> +static void c3_isp_stats_disable(struct c3_isp_stats *stats)
>> +{
>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>> +                        TOP_3A_AE_STAT_EN, 0);
>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>> +                        TOP_3A_AWB_STAT_EN, 0);
>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>> +                        TOP_3A_AF_STAT_EN, 0);
>> +}
>> +
>> +/* The unit of dma_size is 16 bytes */
>> +static void c3_isp_stats_cfg_dmawr_size(struct c3_isp_stats *stats)
>> +{
>> +     u32 dma_size;
>> +
>> +     dma_size = sizeof(struct af_stats_info) / 16;
>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0,
>> +                        VIU_DMAWR_SIZE_AF_MASK, dma_size);
>> +
>> +     dma_size = sizeof(struct awb_stats_info) / 16;
>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, 
>> VIU_DMAWR_SIZE_AWB_MASK,
>> +                        dma_size << VIU_DMAWR_SIZE_AWB_SHIFT);
>> +
>> +     dma_size = sizeof(struct ae_stats_info) / 16;
>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1,
>> +                        VIU_DMAWR_SIZE_AE_MASK, dma_size);
>> +}
>> +
>> +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
>> +{
>> +     stats->buff = list_first_entry_or_null(&stats->pending,
>> +                                            struct 
>> c3_isp_vb2_buffer, list);
>> +     if (stats->buff) {
>> +             c3_isp_stats_cfg_dmawr_addr(stats);
>> +             list_del(&stats->buff->list);
>> +     }
>> +}
>
> Same locking comment as with the capture code.


Will add lock.

>> +
>> +static void c3_isp_stats_start(struct c3_isp_stats *stats)
>> +{
>> +     c3_isp_stats_af_init(stats);
>> +     c3_isp_stats_ae_init(stats);
>> +     c3_isp_stats_awb_init(stats);
>> +
>> +     c3_isp_stats_cfg_dmawr_size(stats);
>> +     c3_isp_stats_cfg_buff(stats);
>> +     c3_isp_stats_enable(stats);
>> +
>> +     stats->is_streaming = true;
> And similarly I'd drop this variable and rely on the state of the list 
> and stats->buff


Will try to use c3_isp_stats_return_buffers().

>> +}
>> +
>> +static void c3_isp_stats_stop(struct c3_isp_stats *stats)
>> +{
>> +     stats->is_streaming = false;
>> +
>> +     c3_isp_stats_disable(stats);
>> +}
>> +
>> +static void c3_isp_stats_return_buffers(struct c3_isp_stats *stats,
>> +                                     enum vb2_buffer_state state)
>> +{
>> +     unsigned long flags;
>> +     struct c3_isp_vb2_buffer *buff;
>> +
>> +     spin_lock_irqsave(&stats->buff_lock, flags);
> Usual comment about the use of guard()


Will use guard().

>> +
>> +     if (stats->buff) {
>> + vb2_buffer_done(&stats->buff->vb.vb2_buf, state);
>> +             stats->buff = NULL;
>> +     }
>> +
>> +     while (!list_empty(&stats->pending)) {
>> +             buff = list_first_entry(&stats->pending,
>> +                                     struct c3_isp_vb2_buffer, list);
>> +             list_del(&buff->list);
>> +             vb2_buffer_done(&buff->vb.vb2_buf, state);
>> +     }
>> +
>> +     spin_unlock_irqrestore(&stats->buff_lock, flags);
>> +}
>> +
>> +static int c3_isp_stats_querycap(struct file *file, void *fh,
>> +                              struct v4l2_capability *cap)
>> +{
>> +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>> +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_stats_enum_fmt(struct file *file, void *fh,
>> +                              struct v4l2_fmtdesc *f)
>> +{
>> +     struct c3_isp_stats *stats = video_drvdata(file);
>> +
>> +     if (f->index > 0 || f->type != stats->vb2_q.type)
>> +             return -EINVAL;
>> +
>> +     f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_stats_g_fmt(struct file *file, void *fh,
>> +                           struct v4l2_format *f)
>> +{
>> +     struct c3_isp_stats *stats = video_drvdata(file);
>> +
>> +     f->fmt.meta = stats->vfmt.fmt.meta;
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
>> +     .vidioc_querycap                = c3_isp_stats_querycap,
>> +     .vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
>> +     .vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
>> +     .vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
>> +     .vidioc_try_fmt_meta_cap        = c3_isp_stats_g_fmt,
>> +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>> +     .vidioc_querybuf                = vb2_ioctl_querybuf,
>> +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
>> +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
>> +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>> +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>> +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>> +     .vidioc_streamon                = vb2_ioctl_streamon,
>> +     .vidioc_streamoff               = vb2_ioctl_streamoff,
>> +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>> +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>> +};
>> +
>> +static const struct v4l2_file_operations isp_stats_v4l2_fops = {
>> +     .open = v4l2_fh_open,
>> +     .release = vb2_fop_release,
>> +     .poll = vb2_fop_poll,
>> +     .unlocked_ioctl = video_ioctl2,
>> +     .mmap = vb2_fop_mmap,
>> +};
>> +
>> +static int c3_isp_stats_vb2_queue_setup(struct vb2_queue *q,
>> +                                     unsigned int *num_buffers,
>> +                                     unsigned int *num_planes,
>> +                                     unsigned int sizes[],
>> +                                     struct device *alloc_devs[])
>> +{
>> +     if (*num_planes) {
>> +             if (*num_planes != 1)
>> +                     return -EINVAL;
>> +
>> +             if (sizes[0] < sizeof(struct c3_isp_stats_info))
>> +                     return -EINVAL;
>> +     } else {
>> +             *num_planes = 1;
>> +             sizes[0] = sizeof(struct c3_isp_stats_info);
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_vb2_buffer *buf =
>> +                     container_of(v4l2_buf, struct 
>> c3_isp_vb2_buffer, vb);
>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned long flags;
>> +
>> +     spin_lock_irqsave(&stats->buff_lock, flags);
>> +
>> +     list_add_tail(&buf->list, &stats->pending);
>> +
>> +     spin_unlock_irqrestore(&stats->buff_lock, flags);
>> +}
>> +
>> +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
>> +{
>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>> +     unsigned int size = stats->vfmt.fmt.meta.buffersize;
>> +
>> +     if (vb2_plane_size(vb, 0) < size) {
>> +             dev_err(stats->isp->dev,
>> +                     "User buffer too small (%ld < %u)\n",
>> +                     vb2_plane_size(vb, 0), size);
>> +             return -EINVAL;
>> +     }
>> +
>> +     vb2_set_plane_payload(vb, 0, size);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
>> +{
>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>> +     struct c3_isp_vb2_buffer *buf =
>> +                     container_of(v4l2_buf, struct 
>> c3_isp_vb2_buffer, vb);
>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>> +
>> +     buf->vaddr = vb2_plane_vaddr(vb, 0);
>> +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>> +
>> +     memset(buf->vaddr, 0, stats->vfmt.fmt.meta.buffersize);
>> +
>> +     return 0;
>> +}
>> +
>> +static int c3_isp_stats_vb2_start_streaming(struct vb2_queue *q,
>> +                                         unsigned int count)
>> +{
>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(q);
>> +     int ret;
>> +
>> +     guard(mutex)(&stats->isp->lock);
>> +
>> +     ret = pm_runtime_resume_and_get(stats->isp->dev);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = video_device_pipeline_start(&stats->vdev, 
>> &stats->isp->pipe);
>> +     if (ret) {
>> +             dev_err(stats->isp->dev,
>> +                     "Failed to start stats pipeline: %d\n", ret);
>> +             goto err_pm_put;
>> +     }
>> +
>> +     if (c3_isp_pipeline_ready(stats->isp)) {
>> +             ret = v4l2_subdev_enable_streams(&stats->isp->core.sd,
>> + C3_ISP_CORE_PAD_SOURCE_STATS,
>> +                                              BIT(0));
>> +             if (ret)
>> +                     goto err_pipeline_stop;
>> +     }
>> +
>> +     c3_isp_stats_start(stats);
>> +
>> +     return 0;
>> +
>> +err_pipeline_stop:
>> +     video_device_pipeline_stop(&stats->vdev);
>> +err_pm_put:
>> +     pm_runtime_put(stats->isp->dev);
>> +     c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_QUEUED);
>> +     return ret;
>> +}
>> +
>> +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
>> +{
>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(q);
>> +
>> +     guard(mutex)(&stats->isp->lock);
>> +
>> +     c3_isp_stats_stop(stats);
>> +     c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_ERROR);
>> +
>> +     if (stats->isp->pipe.start_count == 1)
>> + v4l2_subdev_disable_streams(&stats->isp->core.sd,
>> + C3_ISP_CORE_PAD_SOURCE_STATS,
>> +                                         BIT(0));
>> +
>> +     video_device_pipeline_stop(&stats->vdev);
>> +     pm_runtime_put(stats->isp->dev);
>> +}
>> +
>> +static const struct vb2_ops isp_stats_vb2_ops = {
>> +     .queue_setup = c3_isp_stats_vb2_queue_setup,
>> +     .buf_queue = c3_isp_stats_vb2_buf_queue,
>> +     .buf_prepare = c3_isp_stats_vb2_buf_prepare,
>> +     .buf_init = c3_isp_stats_vb2_buf_init,
>> +     .wait_prepare = vb2_ops_wait_prepare,
>> +     .wait_finish = vb2_ops_wait_finish,
>> +     .start_streaming = c3_isp_stats_vb2_start_streaming,
>> +     .stop_streaming = c3_isp_stats_vb2_stop_streaming,
>> +};
>> +
>> +int c3_isp_stats_register(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_stats *stats = &isp->stats;
>> +     struct video_device *vdev = &stats->vdev;
>> +     struct vb2_queue *vb2_q = &stats->vb2_q;
>> +     int ret;
>> +
>> +     memset(stats, 0, sizeof(*stats));
>> +     stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
>> +     stats->vfmt.fmt.meta.buffersize = sizeof(struct 
>> c3_isp_stats_info);
>> +     stats->isp = isp;
>> +     INIT_LIST_HEAD(&stats->pending);
>> +     spin_lock_init(&stats->buff_lock);
>> +
>> +     mutex_init(&stats->lock);
>> +
>> +     snprintf(vdev->name, sizeof(vdev->name), "isp-stats");
>> +     vdev->fops = &isp_stats_v4l2_fops;
>> +     vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
>> +     vdev->v4l2_dev = &isp->v4l2_dev;
>> +     vdev->lock = &stats->lock;
>> +     vdev->minor = -1;
>> +     vdev->queue = vb2_q;
>> +     vdev->release = video_device_release_empty;
>> +     vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
>> +     vdev->vfl_dir = VFL_DIR_RX;
>> +     video_set_drvdata(vdev, stats);
>> +
>> +     vb2_q->drv_priv = stats;
>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>> +     vb2_q->ops = &isp_stats_vb2_ops;
>> +     vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
>> +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>> +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>> +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>> +     vb2_q->dev = isp->dev;
>> +     vb2_q->lock = &stats->lock;
>> +     vb2_q->min_queued_buffers = 2;
>> +
>> +     ret = vb2_queue_init(vb2_q);
>> +     if (ret)
>> +             goto err_destroy;
>> +
>> +     stats->pad.flags = MEDIA_PAD_FL_SINK;
>> +     ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
>> +     if (ret)
>> +             goto err_queue_release;
>> +
>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>> +     if (ret) {
>> +             dev_err(isp->dev,
>> +                     "Failed to register %s: %d\n", vdev->name, ret);
>> +             goto err_entity_cleanup;
>> +     }
>> +
>> +     return 0;
>> +
>> +err_entity_cleanup:
>> +     media_entity_cleanup(&vdev->entity);
>> +err_queue_release:
>> +     vb2_queue_release(vb2_q);
>> +err_destroy:
>> +     mutex_destroy(&stats->lock);
>> +     return ret;
>> +}
>> +
>> +void c3_isp_stats_unregister(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_stats *stats = &isp->stats;
>> +
>> +     vb2_queue_release(&stats->vb2_q);
>> +     media_entity_cleanup(&stats->vdev.entity);
>> +     video_unregister_device(&stats->vdev);
>> +     mutex_destroy(&stats->lock);
>> +}
>> +
>> +int c3_isp_stats_done(struct c3_isp_device *isp)
>> +{
>> +     struct c3_isp_stats *stats = &isp->stats;
>> +     struct c3_isp_vb2_buffer *buff = stats->buff;
>> +     unsigned long flags;
>> +
>> +     if (!stats->is_streaming)
>> +             return -EINVAL;
>> +
>> +     spin_lock_irqsave(&stats->buff_lock, flags);
>> +
>> +     if (buff) {
>> +             buff->vb.sequence = stats->isp->frm_sequence;
>> +             buff->vb.vb2_buf.timestamp = ktime_get();
>> +             buff->vb.field = V4L2_FIELD_NONE;
>> +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
>> +     }
>> +
>> +     c3_isp_stats_cfg_buff(stats);
>> +
>> +     spin_unlock_irqrestore(&stats->buff_lock, flags);
>> +
>> +     return 0;
>> +}
>> diff --git 
>> a/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h 
>> b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
>> new file mode 100644
>> index 000000000000..84ff5741357a
>> --- /dev/null
>> +++ b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
>> @@ -0,0 +1,537 @@
>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#ifndef __C3_ISP_CONFIG_H__
>> +#define __C3_ISP_CONFIG_H__
>> +
>> +#define AF_STAT_BLKH_NUM             17
>> +#define AF_STAT_BLKV_NUM             15
>> +#define AF_STAT_BLK_NUM              (AF_STAT_BLKH_NUM * 
>> AF_STAT_BLKV_NUM)
>> +/* AF stats block size need to be aligned with 2 */
>> +#define AF_STAT_BLK_SIZE             ALIGN(AF_STAT_BLK_NUM, 2)
>> +#define AE_HISTOGRAM_SIZE         1024
>> +#define AE_STAT_BLKH_NUM             17
>> +#define AE_STAT_BLKV_NUM             15
>> +#define AE_STAT_BLK_NUM              (AE_STAT_BLKH_NUM * 
>> AE_STAT_BLKV_NUM)
>> +/* AE stats block size need to be aligned with 2 */
>> +#define AE_STAT_BLK_SIZE             ALIGN(AE_STAT_BLK_NUM, 2)
>> +#define AE_BLOCK_WT_NUM              255
>> +#define AE_BLK_WT_DATA_NUM_OF_GROUP  8
>> +#define AWB_STAT_BLKH_NUM            32
>> +#define AWB_STAT_BLKV_NUM            24
>> +#define AWB_STAT_BLK_NUM             (AWB_STAT_BLKH_NUM * 
>> AWB_STAT_BLKV_NUM)
>> +/* AWB stats block size need to be aligned with 2 */
>> +#define AWB_STAT_BLK_SIZE            ALIGN(AWB_STAT_BLK_NUM, 2)
>> +#define AWB_BLOCK_WT_NUM             768
>> +#define AWB_BLK_WT_DATA_NUM_OF_GROUP 8
>> +#define AWB_STAT_BLC20_NUM           4
>> +#define AWB_STAT_GAIN10_NUM          4
>> +#define BLC_OFFSET_NUM               5
>> +#define GAMMA_LUT_GROUP_NUM          4
>> +#define GAMMA_LUT_POINT_NUM          129
>> +#define GAMMA_LUT_DATA_NUM_OF_GROUP  2
>> +
>> +/**
>> + * struct awb_zone_stats - AWB statistics of a block
>> + *
>> + * AWB zone stats is aligned with 8 bytes
>> + *
>> + * @rg: the ratio of R / G in a zone
>> + * @bg: the ratio of B / G in a zone
>> + * @pixel_sum: the total number of pixels used in a zone
>> + */
> The documentation throughout this file is much improved


Thanks.

>> +struct awb_zone_stats {
>> +     u16 rg;
>> +     u16 bg;
>> +     u32 pixel_sum;
>> +};
>
>
> __u8/__u16/__u32 for userspace API headers
>

Will add "include <linux/types.h>" and use __8/__u16/__u32.

>> +
>> +/**
>> + * struct awb_stats_info - Auto white balance statistics information.
>> + *
>> + * AWB statistical information of all blocks.
>> + *
>> + * @awb_stats: array of auto white balance statistics
>> + */
>> +struct awb_stats_info {
>> +     struct awb_zone_stats awb_stats[AWB_STAT_BLK_SIZE];
>> +};
>> +
>> +/**
>> + * struct ae_zone_stats - AE statistics of a block
>> + *
>> + * AE zone stats is aligned with 8 bytes.
>> + * This is a 5-bin histogram and the total sum is
>> + * normalized to 0xffff.
>> + * So hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4)
>> + *
>> + * @hist0: the global normalized pixel count for bin 0
>> + * @hist1: the global normalized pixel count for bin 1
>> + * @hist3: the global normalized pixel count for bin 3
>> + * @hist4: the global normalized pixel count for bin 4
>> + */
>> +struct ae_zone_stats {
>> +     u16 hist0;
>> +     u16 hist1;
>> +     u16 hist3;
>> +     u16 hist4;
>> +};
>> +
>> +/**
>> + * struct ae_stats_info - Exposure statistics information
>> + *
>> + * AE statistical information consists of
>> + * all blocks information and a 1024-bin histogram.
>> + *
>> + * @ae_stats: array of auto exposure block statistics
>> + * @hist: a 1024-bin histogram for the entire image
>> + */
>> +struct ae_stats_info {
>> +     struct ae_zone_stats ae_stats[AE_STAT_BLK_SIZE];
>> +     u32 hist[AE_HISTOGRAM_SIZE];
>> +};
>> +
>> +/**
>> + * struct af_zone_stats - AF statistics of a block
>> + *
>> + * AF block stats is aligned with 8 bytes.
>> + * The zonal accumulated contrast metrics are stored
>> + * in floating point format with 16 bits mantissa and
>> + * 5 or 6 bits exponent.
>> + * Apart from contrast metrics we accumulate squared image and
>> + * quartic image data over the zone.
>> + *
>> + * @i2_mat: the mantissa of zonal squared image pixel sum
>> + * @i4_mat: the mantissa of zonal quartic image pixel sum
>> + * @e4_mat: the mantissa of zonal multi-directional quartic edge sum
>> + * @i2_exp: the exponent of zonal squared image pixel sum
>> + * @i4_exp: the exponent of zonal quartic image pixel sum
>> + * @e4_exp: the exponent of zonal multi-directional quartic edge sum
>> + */
>> +struct af_zone_stats {
>> +     u16 i2_mat;
>> +     u16 i4_mat;
>> +     u16 e4_mat;
>> +     u16 i2_exp: 5;
>> +     u16 i4_exp: 6;
>> +     u16 e4_exp: 5;
>> +};
>> +
>> +/**
>> + * struct af_stats_info - Auto Focus statistics information
>> + *
>> + * AF statistical information of each block
>> + *
>> + * @af_stats: array of auto focus block statistics
>> + */
>> +struct af_stats_info {
>> +     struct af_zone_stats af_stats[AF_STAT_BLK_SIZE];
>> +};
>> +
>> +/**
>> + * struct c3_isp_stats_info - V4L2_META_FMT_C3ISP_STATS
>> + *
>> + * Contains ISP statistics
>> + *
>> + * @awb_stats: auto white balance stats
>> + * @ae_stats: auto exposure stats
>> + * @af_stats: auto focus stats
>> + */
>> +struct c3_isp_stats_info {
>> +     struct awb_stats_info awb_stats;
>> +     struct ae_stats_info ae_stats;
>> +     struct af_stats_info af_stats;
>> +};
>> +
>> +/**
>> + * enum c3_isp_param_buffer_version -  C3 ISP parameters block 
>> versioning
>> + *
>> + * @C3_ISP_PARAM_BUFFER_V0: First version of C3 ISP parameters block
>> + */
>> +enum c3_isp_param_buffer_version {
>> +     C3_ISP_PARAM_BUFFER_V0,
>> +};
>> +
>> +/**
>> + * enum c3_isp_param_block_type - Enumeration of C3 ISP parameter 
>> blocks
>> + *
>> + * Each block configures a specific processing block of the C3 ISP.
>> + * The block type allows the driver to correctly interpret
>> + * the parameters block data.
>> + *
>> + * @C3_ISP_PARAM_BLOCK_WB_CHANGE: White balance change parameters
>> + * @C3_ISP_PARAM_BLOCK_WB_LUMA: White balance Luma-based parameters
>> + * @C3_ISP_PARAM_BLOCK_WB_TRIANGLE: White balance triangle parameters
>> + * @C3_ISP_PARAM_BLOCK_AWB_STATS: AWB statistics parameters
>> + * @C3_ISP_PARAM_BLOCK_AE_STATS: AE statistics parameters
>> + * @C3_ISP_PARAM_BLOCK_AF_STATS: AF statistics parameters
>> + * @C3_ISP_PARAM_BLOCK_PST_GAMMA: post gamma parameters
>> + * @C3_ISP_PARAM_BLOCK_DMSC: demosaic parameters
>> + * @C3_ISP_PARAM_BLOCK_CCM: Color correction matrix parameters
>> + * @C3_ISP_PARAM_BLOCK_CSC: Color space conversion parameters
>> + * @C3_ISP_PARAM_BLOCK_BLC: Black level correction parameters
>> + * @C3_ISP_PARAM_BLOCK_SENTINEL: First non-valid block index
>> + */
>> +enum c3_isp_param_block_type {
>> +     C3_ISP_PARAM_BLOCK_WB_CHANGE,
>> +     C3_ISP_PARAM_BLOCK_WB_LUMA,
>> +     C3_ISP_PARAM_BLOCK_WB_TRIANGLE,
>> +     C3_ISP_PARAM_BLOCK_AWB_STATS,
>> +     C3_ISP_PARAM_BLOCK_AE_STATS,
>> +     C3_ISP_PARAM_BLOCK_AF_STATS,
>> +     C3_ISP_PARAM_BLOCK_PST_GAMMA,
>> +     C3_ISP_PARAM_BLOCK_DMSC,
>> +     C3_ISP_PARAM_BLOCK_CCM,
>> +     C3_ISP_PARAM_BLOCK_CSC,
>> +     C3_ISP_PARAM_BLOCK_BLC,
>> +     C3_ISP_PARAM_BLOCK_SENTINEL
>> +};
>> +
>> +/**
>> + * struct c3_isp_param_block_header - C3 ISP parameter block header
>> + *
>> + * This structure represents the common part of all the ISP 
>> configuration
>> + * blocks. Each parameters block shall embed an instance of this 
>> structure type
>> + * as its first member, followed by the block-specific configuration 
>> data. The
>> + * driver inspects this common header to discern the block type and 
>> its size and
>> + * properly handle the block content by casting it to the correct 
>> block-specific
>> + * type.
>> + *
>> + * @type: The parameters block type (enum c3_isp_param_block_type)
>> + * @enabled: Block enabled/disabled flag
>> + * @size: Size (in bytes) of parameters block
>> + */
>> +
>> +struct c3_isp_param_block_header {
>> +     enum c3_isp_param_block_type type;
>> +     bool enabled;
>> +     size_t size;
>> +};
> Stick to types with strict size definitions; __u16 type, __u32 size 
> and so on. Some feedback from
> the mali-c55 series was to switch to a "flags" field rather than an 
> enabled bool and use BIT(0) as
> the enable flag; more flexibility for future expansion.


Will refer to the mali-c55 patches.

>> +
>> +/**
>> + * struct wb_change_cfg - White Balance configuration
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @wb_gain: white balance gain of each color
>> + *   wb_gain[0]: Gr gain, range 0~0xfff
>> + *   wb_gain[1]: R gain, range 0~0xfff
>> + *   wb_gain[2]: B gain, range 0~0xfff
>> + *   wb_gain[3]: Gb gain, range 0~0xfff
>> + *   wb_gain[4]: Ir gain, range 0~0xfff
>> + * @wb_limit: white balance limit of each color
>> + *   wb_limit[0]: Gr limit, 16 bits float
>> + *   wb_limit[1]: R limit, 16 bits float
>> + *   wb_limit[2]: B limit, 16 bits float
>> + *   wb_limit[3]: Gb limit, 16 bits float
>> + *   wb_limit[4]: Ir limit, 16 bits float
>> + * @ae_gain_grbgi: Gain of each color before blending to luma
>> + *   ae_gain_grbgi[0]: Gr gain, range 0~255
>> + *   ae_gain_grbgi[1]: R gain, range 0~255
>> + *   ae_gain_grbgi[2]: B gain, range 0~255
>> + *   ae_gain_grbgi[3]: Gb gain, range 0~255
>> + *   ae_gain_grbgi[4]: Ir gain, range 0~255
>> + * @ae_bl12_grbgi: Offset of each color before ae_gain_grbgi
>> + *   ae_bl12_grbgi[0]: Gr offset, range 0~4095
>> + *   ae_bl12_grbgi[1]: R offset, range 0~4095
>> + *   ae_bl12_grbgi[2]: B offset, range 0~4095
>> + *   ae_bl12_grbgi[3]: Gb offset, range 0~4095
>> + *   ae_bl12_grbgi[4]: Ir offset, range 0~4095
>> + */
> Much nicer. Could you mention which value from enum 
> c3_isp_param_block_type this struct pertains to
> though please?


OK, will add the block type of this struct.

>> +struct wb_change_cfg {
> c3_isp_ prefix please


Will add "c3_isp_" prefix

>> +     struct c3_isp_param_block_header header;
>> +     u32 wb_gain[5];
>> +     u32 wb_limit[5];
>> +     u32 ae_gain_grbgi[5];
>> +     u32 ae_bl12_grbgi[5];
>> +};
>> +
>> +/**
>> + * struct wb_luma_cfg - White Balance Luma-based configuration
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @awb_stat_blc20: BLC in AWB statistic
>> + *   awb_stat_blc20[0]: Gr blc, range 0~0xfffff
>> + *   awb_stat_blc20[1]: R blc, range 0~0xfffff
>> + *   awb_stat_blc20[2]: B blc, range 0~0xfffff
>> + *   awb_stat_blc20[3]: Gb blc, range 0~0xfffff
>> + * @awb_stat_gain10: Gain in AWB statistic
>> + *   awb_stat_gain10[0]: Gr gain, range 0~1023
>> + *   awb_stat_gain10[1]: R gain, range 0~1023
>> + *   awb_stat_gain10[2]: B gain, range 0~1023
>> + *   awb_stat_gain10[3]: Gb gain, range 0~1023
>> + * @awb_stat_satur_low: AWB statistic under-saturation threshold
>> + *   value: range 0~65535
>> + * @awb_stat_satur_high: AWB statistic over-saturation threshold
>> + *   value: range 0~65535
>> + */
>> +struct wb_luma_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u32 awb_stat_blc20[AWB_STAT_BLC20_NUM];
>> +     u32 awb_stat_gain10[AWB_STAT_GAIN10_NUM];
>> +     u32 awb_stat_satur_low;
>> +     u32 awb_stat_satur_high;
>> +};
>> +
>> +/**
>> + * struct wb_triangle_cfg - White Balance Triangle
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @awb_stat_satur_vald: AWB statistic over saturation control
>> + *   value: 0: disable, 1: enable
>> + * @awb_stat_rg_min: min value of r/g
>> + *   value: 0~4095
>> + * @awb_stat_rg_max: max value of r/g
>> + *   value: 0~4095
>> + * @awb_stat_bg_min: min value of b/g
>> + *   value: 0~4095
>> + * @awb_stat_bg_max: max value of b/g
>> + *   value: 0~4095
>> + * @awb_stat_rg_low: low value of r/g
>> + *   value: 0~4095
>> + * @awb_stat_rg_high: high value of r/g
>> + *   value: 0~4095
>> + * @awb_stat_bg_low: low value of b/g
>> + *   value: 0~4095
>> + * @awb_stat_bg_high: high value of b/g
>> + *   value: 0~4095
>> + */
>> +struct wb_triangle_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u32 awb_stat_satur_vald;
>> +     u32 awb_stat_rg_min;
>> +     u32 awb_stat_rg_max;
>> +     u32 awb_stat_bg_min;
>> +     u32 awb_stat_bg_max;
>> +     u32 awb_stat_rg_low;
>> +     u32 awb_stat_rg_high;
>> +     u32 awb_stat_bg_low;
>> +     u32 awb_stat_bg_high;
>> +};
>> +
>> +/**
>> + * struct awb_stats_cfg - AWB statistics configuration
>> + *
>> + * This structure contains AWB statistics control information.
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @awb_stat_switch: the switch of AWB statistics
>> + *   value: 0~7
> I assume this switch controls where in the ISP pipeline the values are 
> drawn from? Could there be an
> enumeration that documents those 8 values? See for example "enum 
> mali_c55_aexp_hist_tap_points".
> Same comments for the other switches below



OK, will add an enumeration and refer to "enum 
mali_c55_aexp_hist_tap_points".

>> + * @awb_stat_blk_weight: Array of weights for AWB statistics blocks
>> + *   value: 0~15
>> + */
>> +struct awb_stats_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u8 awb_stat_switch;
>> +     u32 awb_stat_blk_weight[AWB_BLOCK_WT_NUM];
>> +};
>> +
>> +/**
>> + * struct ae_stats_cfg - AE statistics configuration
>> + *
>> + * This structure contains AE statistics control information.
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @ae_stat_switch: the switch of AE statistics
>> + *   value: 0~3
>> + * @ae_stat_blk_weight: Array of weights for AE statistics blocks
>> + *   value: 0~15
>> + */
>> +struct ae_stats_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u8 ae_stat_switch;
>> +     u32 ae_stat_blk_weight[AE_BLOCK_WT_NUM];
>> +};
>> +
>> +/**
>> + * struct af_stats_cfg - AF statistics configuration
>> + *
>> + * This structure contains AF statistics control information.
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @af_stat_switch: the switch of AF statistics
>> + *   value: 0~3
>> + */
>> +struct af_stats_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u8 af_stat_switch;
>> +};
>> +
>> +/**
>> + * struct pst_gamma_cfg - Post gamma configuration
>> + *
>> + * This structure contains post gamma parameters
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @pst_gamma_lut: LUT for P-Stitch gamma
>> + *   value: 0~65535
>> + */
>> +struct pst_gamma_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u32 pst_gamma_lut[GAMMA_LUT_GROUP_NUM][GAMMA_LUT_POINT_NUM];
>> +};
>> +
>> +/**
>> + * struct dmsc_cfg - Demosaic configuration
>> + *
>> + * This structure contains demosaic parameters
> You don't need the empty struct if all you're really after is the 
> enable / disable field (or ideally
> flag) - just pass in a header block and ensure that the driver handles 
> that correctly


OK, will remove this empty struct and only use a header block.

>> + *
>> + * @header: The C3 ISP parameters block header
>> + */
>> +struct dmsc_cfg {
>> +     struct c3_isp_param_block_header header;
>> +};
>> +
>> +/**
>> + * struct ccm_cfg - ISP CCM configuration
>> + *
>> + * This structure holds the parameters for configuring the CCM,
>> + * which is used for color correction.
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @ccm_4x3matrix: A 3x4 matrix used for color correction
>> + *   value: 0~8191
>> + */
>> +struct ccm_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u32 ccm_4x3matrix[3][4];
>> +};
>> +
>> +/**
>> + * struct csc_cfg - ISP Color Space Conversion configuration
>> + *
>> + * This structure contains settings for color space conversion.
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @cm0_offset_inp: Input offset values for the 0-order color matrix
>> + *   value: 0~8191
>> + * @cm0_offset_oup: Output offset values for the 0-order color matrix
>> + *   value: 0~8191
>> + * @cm0_3x3mtrx_rs: matrix right shift for cm0
>> + *   value: 0~3
>> + * @cm0_3x3matrix: A 3x3 matrix used for the color matrix
>> + *   value: 0~8191
>> + */
>> +struct csc_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u32 cm0_offset_inp[3];
>> +     u32 cm0_offset_oup[3];
>> +     u32 cm0_3x3mtrx_rs;
>> +     u32 cm0_3x3matrix[3][3];
>> +};
>> +
>> +/**
>> + * struct blc_cfg - ISP Black Level Correction (BLC) configuration
>> + *
>> + * This structure holds the parameters for BLC in image processing.
>> + *
>> + * @header: The C3 ISP parameters block header
>> + * @fe_bl_ofst: Array of front-end BLC offsets for each color each 
>> channels
>> + *   fe_bl_ofst[0]: Gr blc offset, range 0~0x1fffff
>> + *   fe_bl_ofst[1]: R blc offset, range 0~0x1fffff
>> + *   fe_bl_ofst[2]: B blc offset, range 0~0x1fffff
>> + *   fe_bl_ofst[3]: Gb blc offset, range 0~0x1fffff
>> + *   fe_bl_ofst[4]: Ir blc offset, range 0~0x1fffff
>> + * @blc_ofst: Array of LSWB BLC offsets
> LSWB?


LSWB: Lens shading and white balance

This blc offset takes effect before lens shading.

Will add new explanation.

>> + *   blc_ofst[0]: Gr blc offset, 16 bits float
>> + *   blc_ofst[1]: R blc offset, 16 bits float
>> + *   blc_ofst[2]: B blc offset, 16 bits float
>> + *   blc_ofst[3]: Gb blc offset, 16 bits float
>> + *   blc_ofst[4]: Ir blc offset, 16 bits float
>> + */
> If it's a 16 bit field, how come the member is a u32 array?


Will use a 16 bit field.

>> +struct blc_cfg {
>> +     struct c3_isp_param_block_header header;
>> +     u32 fe_bl_ofst[BLC_OFFSET_NUM];
>> +     u32 blc_ofst[BLC_OFFSET_NUM];
>> +};
>> +
>> +/**
>> + * define C3_ISP_PARAMS_MAX_SIZE - Maximum size of all C3 ISP 
>> Parameters
>> + *
>> + * Though the parameters for the C3 ISP are passed as optional 
>> blocks, the
>> + * driver still needs to know the absolute maximum size so that it 
>> can allocate
>> + * a buffer sized appropriately to accommodate userspace attempting 
>> to set all
>> + * possible parameters in a single frame.
>> + */
>> +#define C3_ISP_PARAMS_MAX_SIZE                 \
>> +     (sizeof(struct wb_change_cfg) +        \
>> +     sizeof(struct wb_luma_cfg)   +         \
>> +     sizeof(struct wb_triangle_cfg) +       \
>> +     sizeof(struct awb_stats_cfg) +          \
>> +     sizeof(struct ae_stats_cfg) +           \
>> +     sizeof(struct af_stats_cfg) +           \
>> +     sizeof(struct pst_gamma_cfg) +         \
>> +     sizeof(struct dmsc_cfg) +              \
>> +     sizeof(struct ccm_cfg) +               \
>> +     sizeof(struct csc_cfg) +               \
>> +     sizeof(struct blc_cfg))
>> +
>> +/**
>> + * struct c3_isp_params_buffer - C3 ISP configuration parameters
>> + *
>> + * This struct contains the configuration parameters of the C3 ISP
>> + * algorithms, serialized by userspace into an opaque data buffer. Each
>> + * configuration parameter block is represented by a block-specific 
>> structure
>> + * which contains a :c:type:`c3_isp_param_block_header` entry as first
>> + * member. Userspace populates the @data buffer with configuration 
>> parameters
>> + * for the blocks that it intends to configure. As a consequence, 
>> the data
>> + * buffer effective size changes according to the number of ISP 
>> blocks that
>> + * userspace intends to configure.
>> + *
>> + * The parameters buffer is versioned by the @version field to allow 
>> modifying
>> + * and extending its definition. Userspace should populate the 
>> @version field to
>> + * inform the driver about the version it intends to use. The driver 
>> will parse
>> + * and handle the @data buffer according to the data layout specific 
>> to the
>> + * indicated revision and return an error if the desired revision is 
>> not
>> + * supported.
>> + *
>> + * For each ISP block that userspace wants to configure, a 
>> block-specific
>> + * structure is appended to the @data buffer, one after the other 
>> without gaps
>> + * in between nor overlaps. Userspace shall populate the @total_size 
>> field with
>> + * the effective size, in bytes, of the @data buffer.
>> + *
>> + * The expected memory layout of the parameters buffer is::
>> + *
>> + *   +-------------------- struct c3_isp_params_buffer 
>> ------------------+
>> + *   | version = 
>> C3_ISP_PARAM_BUFFER_V0;                                   |
>> + *   | total_size = sizeof(sizeof(struct 
>> wb_change_cfg))                   |
>> + *   |              sizeof(sizeof(struct 
>> wb_luma_cfg));                    |
>> + *   | +------------------------- data 
>> ---------------------------------+ |
>> + *   | | +------------------ struct wb_change_cfg) 
>> --------------------+ | |
>> + *   | | | +---------  struct c3_isp_param_block_header header 
>> -----+ | | |
>> + *   | | | | type = C3_ISP_PARAM_BLOCK_WB_CHANGE;                    
>> | | | |
>> + *   | | | | enabled = 
>> true;                                            | | | |
>> + *   | | | | size =                                                  
>> | | | |
>> + *   | | | |    sizeof(struct c3_isp_param_block_header header);     
>> | | | |
>> + *   | | | 
>> +---------------------------------------------------------+ | | |
>> + *   | | | wb_gain[5] = 
>> ...;                                           | | |
>> + *   | | | wb_limit[5] = 
>> ...;                                          | | |
>> + *   | | | ae_gain_grbgi[5] = 
>> ...;                                     | | |
>> + *   | | | ae_bl12_grbgi[5] = 
>> ...;                                     | | |
>> + *   | | +------------------ struct wb_luma_cfg 
>> -----------------------+ | |
>> + *   | | | +---------- struct c3_isp_param_block_header header 
>> ------+ | | |
>> + *   | | | | type = C3_ISP_PARAM_BLOCK_WB_LUMA;                      
>> | | | |
>> + *   | | | | enabled = 
>> true;                                            | | | |
>> + *   | | | | size = sizeof(struct wb_luma_cfg);                      
>> | | | |
>> + *   | | | 
>> +---------------------------------------------------------+ | | |
>> + *   | | | awb_stat_blc20[4] = 
>> ...;                                    | | |
>> + *   | | | awb_stat_gain10[4] = 
>> ...;                                   | | |
>> + *   | | | awb_stat_satur_low = 
>> ...;                                   | | |
>> + *   | | | awb_stat_satur_high = 
>> ...;                                  | | |
>> + *   | | 
>> +-------------------------------------------------------------+ | |
>> + *   | 
>> +-----------------------------------------------------------------+ |
>> + * 
>> +---------------------------------------------------------------------+
>> + *
>> + * @version: The C3 ISP parameters buffer version
>> + * @total_size: The C3 ISP configuration data effective size,
>> + *           excluding this header
>> + * @data: The C3 ISP configuration blocks data
>> + */
>> +struct c3_isp_params_buffer {
>> +     enum c3_isp_param_buffer_version version;
>> +     size_t total_size;
>> +     u8 data[C3_ISP_PARAMS_MAX_SIZE];
>> +};
>> +
>> +#endif
>>

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

* Re: [PATCH v3 7/9] media: platform: Add c3 ISP driver
  2024-11-11  9:05     ` Keke Li
@ 2024-11-12 11:41       ` Dan Scally
  0 siblings, 0 replies; 37+ messages in thread
From: Dan Scally @ 2024-11-12 11:41 UTC (permalink / raw)
  To: Keke Li, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-media, devicetree, linux-kernel, kieran.bingham,
	laurent.pinchart

Hi Keke

On 11/11/2024 09:05, Keke Li wrote:
> Hi Dan
>
> Thanks very much for your reply.
>
> On 2024/11/8 18:47, Dan Scally wrote:
>> [ EXTERNAL EMAIL ]
>>
>> Hi Keke - sorry for the delay in review
>>
>> On 18/09/2024 07:07, Keke Li via B4 Relay wrote: 


< snip >

>> +
>> +int c3_isp_captures_register(struct c3_isp_device *isp)
>> +{
>> +     int ret;
>> +     unsigned int i;
>> +     struct c3_isp_capture *cap;
>> +
>> +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>> +             cap = &isp->caps[i];
>> +             memset(cap, 0, sizeof(*cap));
>> +
>> +             cap->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH;
>> +             cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT;
>> +             cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE;
>> +             cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
>> +             cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
>> +
>> +             c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix);
>> +
>> +             cap->id = i;
>> +             if (cap->id == C3_ISP_CAP_DEV_0)
>> +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_0];
>> +             else if (cap->id == C3_ISP_CAP_DEV_1)
>> +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_1];
>> +             else
>> +                     cap->rsz = &isp->resizers[C3_ISP_RSZ_2];
>> I think cap->rsz = &isp->resizers[i]; is fine personally, but it's up to you
>
>
> The  index "i" is C3_ISP_CAP_DEV_X, but the index of isp->resizers[] is C3_ISP_RSZ_X.
>
> Can I use the index "i" in  isp->resizers[] ?

I think it's fine, on the grounds there's a 1:1 relationship between them, but it is up to you - 
leave it like this if you prefer it this way.
>
>>> +
>>> +             cap->isp = isp;
>>> +             INIT_LIST_HEAD(&cap->pending);
>>> +             spin_lock_init(&cap->buff_lock);
>>> +             mutex_init(&cap->lock);
>>> +
>>> +             ret = c3_isp_register_capture(cap);
>>> +             if (ret) {
>>> +                     cap->isp = NULL;
>>> +                     mutex_destroy(&cap->lock);
>>> +                     c3_isp_captures_unregister(isp);
>>> +                     return ret;
>>> +             }
>>> +     }
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +void c3_isp_captures_unregister(struct c3_isp_device *isp)
>>> +{
>>> +     unsigned int i;
>>> +     struct c3_isp_capture *cap;
>>> +
>>> +     for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) {
>>> +             cap = &isp->caps[i];
>>> +
>>> +             if (!cap->isp)
>>> +                     continue;
>>> +             vb2_queue_release(&cap->vb2_q);
>>> +             media_entity_cleanup(&cap->vdev.entity);
>>> +             video_unregister_device(&cap->vdev);
>>> +             mutex_destroy(&cap->lock);
>>> +     }
>>> +}
>>> +
>>> +void c3_isp_captures_done(struct c3_isp_device *isp)
>>> +{
>>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]);
>>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]);
>>> +     c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]);
>>> +}
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h 
>>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
>>> new file mode 100644
>>> index 000000000000..19f2a3bc29c9
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h
>>> @@ -0,0 +1,327 @@
>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#ifndef __C3_ISP_COMMON_H__
>>> +#define __C3_ISP_COMMON_H__
>>> +
>>> +#include <linux/clk.h>
>>> +
>>> +#include <media/media-device.h>
>>> +#include <media/videobuf2-core.h>
>>> +#include <media/v4l2-device.h>
>>> +#include <media/v4l2-subdev.h>
>>> +#include <media/videobuf2-v4l2.h>
>>> +
>>> +#define C3_ISP_DRIVER_NAME            "c3-isp"
>>> +#define C3_ISP_CLOCK_NUM_MAX          3
>>> +
>>> +#define C3_ISP_CORE_DEF_SINK_PAD_FMT MEDIA_BUS_FMT_SRGGB10_1X10
>>> +#define C3_ISP_CORE_DEF_SRC_PAD_FMT MEDIA_BUS_FMT_YUYV8_1_5X8
>>> +#define C3_ISP_RSZ_DEF_PAD_FMT MEDIA_BUS_FMT_YUYV8_1_5X8
>>> +#define C3_ISP_DEFAULT_WIDTH          1920
>>> +#define C3_ISP_DEFAULT_HEIGHT         1080
>>> +#define C3_ISP_MAX_WIDTH              2888
>>> +#define C3_ISP_MAX_HEIGHT             2240
>>> +#define C3_ISP_MIN_WIDTH              160
>>> +#define C3_ISP_MIN_HEIGHT             120
>>> +
>>> +#define C3_DISP_INTER                 0x400
>>> +#define C3_DISP_REG(base, id)         ((base) + (id) * C3_DISP_INTER)
>>> +#define C3_WRMIFX3_INTER              0x100
>>> +#define C3_WRMIFX3_REG(base, id)      ((base) + (id) * C3_WRMIFX3_INTER)
>>> +#define C3_PPS_TAP4_S11_H_NUM         33
>>> +#define C3_PPS_LUT_CTYPE_0            0
>>> +#define C3_PPS_LUT_CTYPE_2            2
>>> +#define C3_SCALE_EN                   1
>>> +#define C3_SCALE_DIS                  0
>>> +
>>> +#define C3_ISP_PHASE_OFFSET_0         0
>>> +#define C3_ISP_PHASE_OFFSET_1         1
>>> +#define C3_ISP_PHASE_OFFSET_NONE      0xff
>>> +
>>> +enum c3_isp_core_pads {
>>> +     C3_ISP_CORE_PAD_SINK_VIDEO,
>>> +     C3_ISP_CORE_PAD_SINK_PARAMS,
>>> +     C3_ISP_CORE_PAD_SOURCE_STATS,
>>> +     C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>> +     C3_ISP_CORE_PAD_MAX
>>> +};
>>> +
>>> +enum c3_isp_resizer_ids {
>>> +     C3_ISP_RSZ_0,
>>> +     C3_ISP_RSZ_1,
>>> +     C3_ISP_RSZ_2,
>>> +     C3_ISP_NUM_RSZ
>>> +};
>>> +
>>> +enum c3_isp_resizer_pads {
>>> +     C3_ISP_RESIZER_PAD_SINK,
>>> +     C3_ISP_RESIZER_PAD_SOURCE,
>>> +     C3_ISP_RESIZER_PAD_MAX
>>> +};
>>> +
>>> +enum c3_isp_cap_devs {
>>> +     C3_ISP_CAP_DEV_0,
>>> +     C3_ISP_CAP_DEV_1,
>>> +     C3_ISP_CAP_DEV_2,
>>> +     C3_ISP_NUM_CAP_DEVS
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_pps_io_size - isp scaler input and output size
>>> + *
>>> + * @thsize: input horizontal size of after preprocessing
>>> + * @tvsize: input vertical size of after preprocessing
>>> + * @ohsize: output horizontal size
>>> + * @ovsize: output vertical size
>>> + * @ihsize: input horizontal size
>>> + * @max_hsize: maximum horizontal size
>>> + */
>>> +struct c3_isp_pps_io_size {
>>> +     u32 thsize;
>>> +     u32 tvsize;
>>> +     u32 ohsize;
>>> +     u32 ovsize;
>>> +     u32 ihsize;
>>> +     u32 max_hsize;
>>> +};
>>> +
>>> +/**
>>> + * @mbus_code: the mbus code
>>> + * @pads: save the pad flag of this mbus_code
>> "bitmask detailing valid pads for this mbus_code" perhaps?
>
>
> OK, will do this.
>
>>> + * @xofst: horizontal phase offset of hardware
>>> + * @yofst: vertical phase offset of hardware
>>> + */
>>> +struct c3_isp_mbus_format_info {
>>> +     u32 mbus_code;
>>> +     u32 pads;
>>> +     u8 xofst;
>>> +     u8 yofst;
>>> +};
>>> +
>>> +/**
>>> + * @mbus_code: the mbus code
>>> + * @fourcc: pixel format
>>> + * @depth: pixel width
>>> + */
>>> +struct c3_isp_capture_format {
>>> +     u32 mbus_code;
>>> +     u32 fourcc;
>>> +     u8 depth;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_vb2_buffer - A container of vb2 buffer
>>> + *
>>> + * @vb: vb2 buffer
>>> + * @vaddr: buffer virtual address
>>> + * @paddr: buffer physical address
>>> + * @list: entry of the buffer in the queue
>>> + */
>>> +struct c3_isp_vb2_buffer {
>>> +     struct vb2_v4l2_buffer vb;
>>> +     void *vaddr;
>>> +     dma_addr_t paddr;
>>> +     struct list_head list;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_core - ISP core subdev
>>> + *
>>> + * @sd: ISP sub-device
>>> + * @pads: ISP sub-device pads
>>> + * @src_sd: source sub-device
>>> + * @isp: pointer to c3_isp_device
>>> + * @src_sd_pad: source sub-device pad
>>> + */
>>> +struct c3_isp_core {
>>> +     struct v4l2_subdev sd;
>>> +     struct media_pad pads[C3_ISP_CORE_PAD_MAX];
>>> +     struct v4l2_subdev *src_sd;
>>> +     u16 src_sd_pad;
>>> +     struct c3_isp_device *isp;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_resizer - ISP resizer subdev
>>> + *
>>> + * @id: resizer id
>>> + * @sd: resizer sub-device
>>> + * @pads: resizer sub-device pads
>>> + * @isp: pointer to c3_isp_device
>>> + * @cap: pointer to c3_isp_capture
>>> + */
>>> +struct c3_isp_resizer {
>>> +     enum c3_isp_resizer_ids id;
>>> +     struct v4l2_subdev sd;
>>> +     struct media_pad pads[C3_ISP_RESIZER_PAD_MAX];
>>> +     struct c3_isp_device *isp;
>>> +     struct c3_isp_capture *cap;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_stats - ISP statistics device
>>> + *
>>> + * @vb2_q: vb2 buffer queue
>>> + * @vdev: video node
>>> + * @vfmt: v4l2_format of the metadata format
>>> + * @pad: media pad
>>> + * @lock: protects vb2_q, vdev
>>> + * @is_streaming: stats status
>>> + * @isp: pointer to c3_isp_device
>>> + * @buff: in use buffer
>>> + * @buff_lock: protects stats buffer
>>> + * @pending: stats buffer list head
>>> + */
>>> +struct c3_isp_stats {
>>> +     struct vb2_queue vb2_q;
>>> +     struct video_device vdev;
>>> +     struct v4l2_format vfmt;
>>> +     struct media_pad pad;
>>> +
>>> +     struct mutex lock; /* Protects vb2_q, vdev */
>>> +     bool is_streaming;
>>> +     struct c3_isp_device *isp;
>>> +
>>> +     struct c3_isp_vb2_buffer *buff;
>>> +     spinlock_t buff_lock; /* Protects stream buffer */
>>> +     struct list_head pending;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_params - ISP parameters device
>>> + *
>>> + * @vb2_q: vb2 buffer queue
>>> + * @vdev: video node
>>> + * @vfmt: v4l2_format of the metadata format
>>> + * @pad: media pad
>>> + * @lock: protects vb2_q, vdev
>>> + * @isp: pointer to c3_isp_device
>>> + * @buff: in use buffer
>>> + * @buff_lock: protects stats buffer
>>> + * @pending: stats buffer list head
>>> + */
>>> +struct c3_isp_params {
>>> +     struct vb2_queue vb2_q;
>>> +     struct video_device vdev;
>>> +     struct v4l2_format vfmt;
>>> +     struct media_pad pad;
>>> +
>>> +     struct mutex lock; /* Protects vb2_q, vdev */
>>> +     struct c3_isp_device *isp;
>>> +
>>> +     struct c3_isp_vb2_buffer *buff;
>>> +     spinlock_t buff_lock; /* Protects stream buffer */
>>> +     struct list_head pending;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_capture - ISP capture device
>>> + *
>>> + * @id: capture device ID
>>> + * @vb2_q: vb2 buffer queue
>>> + * @vdev: video node
>>> + * @vfmt: v4l2_format of the capture format
>>> + * @pad: media pad
>>> + * @lock: protects vb2_q, vdev
>>> + * @is_streaming: capture device status
>>> + * @isp: pointer to c3_isp_device
>>> + * @rsz: pointer to c3_isp_resizer
>>> + * @buff: in use buffer
>>> + * @buff_lock: protects capture buffer
>>> + * @pending: capture buffer list head
>>> + */
>>> +struct c3_isp_capture {
>>> +     enum c3_isp_cap_devs id;
>>> +     struct vb2_queue vb2_q;
>>> +     struct video_device vdev;
>>> +     struct v4l2_format vfmt;
>>> +     struct media_pad pad;
>>> +
>>> +     struct mutex lock; /* Protects vb2_q, vdev */
>>> +     bool is_streaming;
>>> +     struct c3_isp_device *isp;
>>> +     struct c3_isp_resizer *rsz;
>>> +
>>> +     struct c3_isp_vb2_buffer *buff;
>>> +     spinlock_t buff_lock; /* Protects stream buffer */
>>> +     struct list_head pending;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_info - ISP information
>>> + *
>>> + * @clocks: array of ISP clock names
>>> + * @clock_rates: array of ISP clock rate
>>> + * @clock_num: actual clock number
>>> + */
>>> +struct c3_isp_info {
>>> +     char *clocks[C3_ISP_CLOCK_NUM_MAX];
>>> +     u32 clock_rates[C3_ISP_CLOCK_NUM_MAX];
>>> +     u32 clock_num;
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_device - ISP platform device
>>> + *
>>> + * @dev: pointer to the struct device
>>> + * @base: base register address
>>> + * @clks: array of clocks
>>> + * @notifier: notifier to register on the v4l2-async API
>>> + * @v4l2_dev: v4l2_device variable
>>> + * @media_dev: media device variable
>>> + * @pipe: media pipeline
>>> + * @core: ISP core subdev
>>> + * @resizer: ISP resizer subdev
>>> + * @stats: ISP stats device
>>> + * @params: ISP params device
>>> + * @caps: array of ISP capture device
>>> + * @frm_sequence: used to record frame id
>>> + * @lock: protect ISP device
>>> + * @info: version-specific ISP information
>>> + */
>>> +struct c3_isp_device {
>>> +     struct device *dev;
>>> +     void __iomem *base;
>>> +     struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX];
>>> +
>>> +     struct v4l2_async_notifier notifier;
>>> +     struct v4l2_device v4l2_dev;
>>> +     struct media_device media_dev;
>>> +     struct media_pipeline pipe;
>>> +
>>> +     struct c3_isp_core core;
>>> +     struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ];
>>> +     struct c3_isp_stats stats;
>>> +     struct c3_isp_params params;
>>> +     struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS];
>>> +
>>> +     u32 frm_sequence;
>>> +     struct mutex lock; /* Protect ISP device */
>>> +     const struct c3_isp_info *info;
>>> +};
>>> +
>>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg);
>>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val);
>>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val);
>>> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp);
>>> +
>>> +int c3_isp_core_register(struct c3_isp_device *isp);
>>> +void c3_isp_core_unregister(struct c3_isp_device *isp);
>>> +int c3_isp_resizers_register(struct c3_isp_device *isp);
>>> +void c3_isp_resizers_unregister(struct c3_isp_device *isp);
>>> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz);
>>> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz);
>>> +int c3_isp_captures_register(struct c3_isp_device *isp);
>>> +void c3_isp_captures_unregister(struct c3_isp_device *isp);
>>> +void c3_isp_captures_done(struct c3_isp_device *isp);
>>> +int c3_isp_stats_register(struct c3_isp_device *isp);
>>> +void c3_isp_stats_unregister(struct c3_isp_device *isp);
>>> +int c3_isp_stats_done(struct c3_isp_device *isp);
>>> +int c3_isp_params_register(struct c3_isp_device *isp);
>>> +void c3_isp_params_unregister(struct c3_isp_device *isp);
>>> +int c3_isp_params_done(struct c3_isp_device *isp);
>>> +
>>> +#endif
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c 
>>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
>>> new file mode 100644
>>> index 000000000000..d3672aff9fd2
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c
>>> @@ -0,0 +1,675 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/pm_runtime.h>
>>> +
>>> +#include "c3-isp-common.h"
>>> +#include "c3-isp-regs.h"
>>> +#include "include/uapi/c3-isp-config.h"
>>> +
>>> +#define C3_ISP_CORE_SUBDEV_NAME        "isp-core"
>>> +
>>> +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = {
>>> +     /* RAW formats */
>>> +     {
>>> +             .mbus_code      = MEDIA_BUS_FMT_SBGGR10_1X10,
>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>>> +     }, {
>>> +             .mbus_code      = MEDIA_BUS_FMT_SGBRG10_1X10,
>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>>> +     }, {
>>> +             .mbus_code      = MEDIA_BUS_FMT_SGRBG10_1X10,
>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>>> +     }, {
>>> +             .mbus_code      = MEDIA_BUS_FMT_SRGGB10_1X10,
>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>>> +     }, {
>>> +             .mbus_code      = MEDIA_BUS_FMT_SBGGR12_1X12,
>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>>> +     }, {
>>> +             .mbus_code      = MEDIA_BUS_FMT_SGBRG12_1X12,
>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_1,
>>> +     }, {
>>> +             .mbus_code      = MEDIA_BUS_FMT_SGRBG12_1X12,
>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_0,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>>> +     }, {
>>> +             .mbus_code      = MEDIA_BUS_FMT_SRGGB12_1X12,
>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SINK_VIDEO),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_1,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_0,
>>> +     },
>>> +     /* YUV formats */
>>> +     {
>>> +             .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +     }, {
>>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_1_5X8,
>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +     }, {
>>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_2X8,
>>> +             .pads           = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +     },
>>> +};
>>> +
>>> +static const struct c3_isp_mbus_format_info
>>> +*core_find_format_by_code(u32 code, u32 pad)
>>> +{
>>> +     int i;
>>> +
>>> +     for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>>> +             const struct c3_isp_mbus_format_info *info =
>>> +                     &c3_isp_core_mbus_formats[i];
>>> +
>>> +             if (info->mbus_code == code && info->pads & BIT(pad))
>>> +                     return info;
>>> +     }
>>> +
>>> +     return NULL;
>>> +}
>>> +
>>> +static const struct c3_isp_mbus_format_info
>>> +*core_find_format_by_index(u32 index, u32 pad)
>>> +{
>>> +     int i;
>>> +
>>> +     for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) {
>>> +             const struct c3_isp_mbus_format_info *info =
>>> +                     &c3_isp_core_mbus_formats[i];
>>> +
>>> +             if (!(info->pads & BIT(pad)))
>>> +                     continue;
>>> +
>>> +             if (!index)
>>> +                     return info;
>>> +
>>> +             index--;
>>> +     }
>>> +
>>> +     return NULL;
>>> +}
>>> +
>>> +static void c3_isp_core_enable(struct c3_isp_device *isp)
>>> +{
>>> +     /* Select the line sync signal */
>>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_LINE_THRD,
>>> +                        TOP_IRQ_DIN_HSYNC, TOP_IRQ_DIN_HSYNC);
>>> +
>>> +     /* Enable frame done and stats error irq */
>>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
>>> +                        TOP_IRQ_FRAME_DONE, TOP_IRQ_FRAME_DONE);
>>> +     c3_isp_update_bits(isp, ISP_TOP_IRQ_EN,
>>> +                        TOP_IRQ_STATS_ERR, TOP_IRQ_STATS_ERR);
>>> +
>>> +     /* Enable image data to ISP core */
>>> +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
>>> +                        TOP_DATA_PATH_MASK, TOP_PATH_MIPI_TO_CORE);
>>> +}
>>> +
>>> +static void c3_isp_core_disable(struct c3_isp_device *isp)
>>> +{
>>> +     /* Disable image data to ISP core */
>>> +     c3_isp_update_bits(isp, ISP_TOP_PATH_SEL,
>>> +                        TOP_DATA_PATH_MASK, 0x0);
>>> +
>>> +     /* Disable all irq */
>>> +     c3_isp_write(isp, ISP_TOP_IRQ_EN, 0x0);
>>> +}
>>> +
>>> +/* Set the phase offset of blc, wb and lns */
>>> +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp,
>>> +                               u8 xofst, u8 yofst)
>>> +{
>>> +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
>>> +                        LSWB_BLC_XPHS_OFST_MASK,
>>> +                        xofst << LSWB_BLC_XPHS_OFST_SHIFT);
>>> +     c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST,
>>> +                        LSWB_BLC_YPHS_OFST_MASK, yofst);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
>>> +                        LSWB_WB_XPHS_OFST_MASK,
>>> +                        xofst << LSWB_WB_XPHS_OFST_SHIFT);
>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST,
>>> +                        LSWB_WB_YPHS_OFST_MASK, yofst);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
>>> +                        LSWB_LNS_XPHS_OFST_MASK,
>>> +                        xofst << LSWB_LNS_XPHS_OFST_SHIFT);
>>> +     c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST,
>>> +                        LSWB_LNS_YPHS_OFST_MASK, yofst);
>>> +}
>>> +
>>> +/* Set the phase offset of af, ae and awb */
>>> +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp,
>>> +                             u8 xofst, u8 yofst)
>>> +{
>>> +     c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_XPHS_OFST_MASK,
>>> +                        xofst << AF_CTRL_XPHS_OFST_SHIFT);
>>> +     c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_YPHS_OFST_MASK,
>>> +                        yofst << AF_CTRL_YPHS_OFST_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_XPHS_OFST_MASK,
>>> +                        xofst << AE_CTRL_XPHS_OFST_SHIFT);
>>> +     c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_YPHS_OFST_MASK,
>>> +                        yofst << AE_CTRL_YPHS_OFST_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_XPHS_OFST_MASK,
>>> +                        xofst << AWB_CTRL_XPHS_OFST_SHIFT);
>>> +     c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_YPHS_OFST_MASK, yofst);
>>> +}
>>> +
>>> +/* Set the phase offset of demosaic */
>>> +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp,
>>> +                              u8 xofst, u8 yofst)
>>> +{
>>> +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_XPHS_OFST_MASK,
>>> +                        xofst << DMS_COMMON_XPHS_OFST_SHIFT);
>>> +     c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_YPHS_OFST_MASK, yofst);
>>> +}
>>> +
>>> +static void c3_isp_core_cfg_ofst(struct c3_isp_device *isp,
>>> +                              struct v4l2_mbus_framefmt *fmt)
>>> +{
>>> +     const struct c3_isp_mbus_format_info *isp_fmt =
>>> +                     core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO);
>>> +
>>> +     c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>>> +     c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>>> +     c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst);
>>> +}
>>> +
>>> +/* Set format of the hardware control module */
>>> +static void c3_isp_core_top_fmt(struct c3_isp_device *isp,
>>> +                             struct v4l2_mbus_framefmt *fmt)
>>> +{
>>> +     c3_isp_write(isp, ISP_TOP_INPUT_SIZE,
>>> +                  TOP_INPUT_HSIZE(fmt->width) | TOP_INPUT_VSIZE(fmt->height));
>>> +
>>> +     c3_isp_write(isp, ISP_TOP_FRM_SIZE,
>>> +                  TOP_FRM_CORE_IHSIZE(fmt->width) | TOP_FRM_CORE_IVSIZE(fmt->height));
>>> +
>>> +     c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE, TOP_HOLD_HSIZE_MASK,
>>> +                        fmt->width << TOP_HOLD_HSIZE_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_core_af_fmt(struct c3_isp_device *isp,
>>> +                            struct v4l2_mbus_framefmt *fmt)
>>> +{
>>> +     u32 hidx;
>>> +     u32 vidx;
>>> +     int i;
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, AF_HV_STAT_HBLK_NUM_MASK,
>>> +                        AF_STAT_BLKH_NUM << AF_HV_STAT_HBLK_NUM_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM,
>>> +                        AF_HV_STAT_VBLK_NUM_MASK, AF_STAT_BLKV_NUM);
>>> +
>>> +     c3_isp_write(isp, ISP_AF_HV_SIZE,
>>> +                  AF_HV_STAT_HSIZE(fmt->width) | AF_HV_STAT_VSIZE(fmt->height));
>>> +
>>> +     /* Set the index address to 0 position */
>>> +     c3_isp_write(isp, ISP_AF_IDX_ADDR, 0);
>> blank line please
>
>
> Will add a blank line.
>
>>> +     /*
>>> +      * Calculate and set the coordinates of points in the grid.
>>> +      * hidx and vidx need to be aligned with 2.
>>> +      */
>>> +     for (i = 0; i <= AF_STAT_BLKH_NUM; i++) {
>>> +             hidx = i * fmt->width / AF_STAT_BLKH_NUM;
>>> +             hidx = ALIGN_DOWN(hidx, 2);
>>> +
>>> +             vidx = i * fmt->height / AF_STAT_BLKV_NUM;
>>> +             vidx = min(vidx, fmt->height);
>>> +             vidx = ALIGN_DOWN(vidx, 2);
>>> +             c3_isp_write(isp, ISP_AF_IDX_DATA,
>>> +                          AF_IDX_HIDX_DATA(hidx) | AF_IDX_VIDX_DATA(vidx));
>>> +     }
>>> +}
>>> +
>>> +static void c3_isp_core_ae_fmt(struct c3_isp_device *isp,
>>> +                            struct v4l2_mbus_framefmt *fmt)
>>> +{
>>> +     u32 hidx;
>>> +     u32 vidx;
>>> +     int i;
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, AE_HV_STAT_HBLK_NUM_MASK,
>>> +                        AE_STAT_BLKH_NUM << AE_HV_STAT_HBLK_NUM_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM,
>>> +                        AE_HV_STAT_VBLK_NUM_MASK, AE_STAT_BLKV_NUM);
>>> +
>>> +     c3_isp_write(isp, ISP_AE_HV_SIZE,
>>> +                  AE_HV_STAT_HSIZE(fmt->width) | AE_HV_STAT_VSIZE(fmt->height));
>>> +
>>> +     /* Set the index address to 0 position */
>>> +     c3_isp_write(isp, ISP_AE_IDX_ADDR, 0);
>>> +     /*
>>> +      * Calculate and set the coordinates of points in the grid.
>>> +      * hidx and vidx need to be aligned with 2.
>>> +      */
>>> +     for (i = 0; i <= AE_STAT_BLKH_NUM; i++) {
>>> +             hidx = i * fmt->width / AE_STAT_BLKH_NUM;
>>> +             hidx = ALIGN_DOWN(hidx, 2);
>>> +
>>> +             vidx = i * fmt->height / AE_STAT_BLKV_NUM;
>>> +             vidx = min(vidx, fmt->height);
>>> +             vidx = ALIGN_DOWN(vidx, 2);
>>> +
>>> +             c3_isp_write(isp, ISP_AE_IDX_DATA,
>>> +                          AE_IDX_HIDX_DATA(hidx) | AE_IDX_VIDX_DATA(vidx));
>>> +     }
>>> +}
>>> +
>>> +static void c3_isp_core_awb_fmt(struct c3_isp_device *isp,
>>> +                             struct v4l2_mbus_framefmt *fmt)
>>> +{
>>> +     u32 hidx;
>>> +     u32 vidx;
>>> +     int i;
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, AWB_HV_STAT_HBLK_NUM_MASK,
>>> +                        AWB_STAT_BLKH_NUM << AWB_HV_STAT_HBLK_NUM_SHIFT);
>>> +     c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM,
>>> +                        AWB_HV_STAT_VBLK_NUM_MASK, AWB_STAT_BLKV_NUM);
>>> +
>>> +     c3_isp_write(isp, ISP_AWB_HV_SIZE,
>>> +                  AWB_HV_STAT_HSIZE(fmt->width) | AWB_HV_STAT_VSIZE(fmt->height));
>>> +
>>> +     /* Set the index address to 0 position */
>>> +     c3_isp_write(isp, ISP_AWB_IDX_ADDR, 0);
>>> +     /*
>>> +      * Calculate and set the coordinates of points in the grid.
>>> +      * hidx and vidx need to be aligned with 2.
>>> +      */
>>> +     for (i = 0; i <= AWB_STAT_BLKH_NUM; i++) {
>>> +             hidx = i * fmt->width / AWB_STAT_BLKH_NUM;
>>> +             hidx = ALIGN_DOWN(hidx, 2);
>>> +
>>> +             vidx = i * fmt->height / AWB_STAT_BLKV_NUM;
>>> +             vidx = min(vidx, fmt->height);
>>> +             vidx = ALIGN_DOWN(vidx, 2);
>>> +
>>> +             c3_isp_write(isp, ISP_AWB_IDX_DATA,
>>> +                          AWB_IDX_HIDX_DATA(hidx) | AWB_IDX_VIDX_DATA(vidx));
>>> +     }
>>> +}
>>> +
>>> +static void c3_isp_core_cfg_format(struct c3_isp_device *isp,
>>> +                                struct v4l2_subdev_state *state)
>>> +{
>>> +     struct v4l2_mbus_framefmt *fmt;
>>> +
>>> +     fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>>> +
>>> +     c3_isp_core_cfg_ofst(isp, fmt);
>>> +     c3_isp_core_top_fmt(isp, fmt);
>>> +     c3_isp_core_af_fmt(isp, fmt);
>>> +     c3_isp_core_ae_fmt(isp, fmt);
>>> +     c3_isp_core_awb_fmt(isp, fmt);
>>> +}
>>> +
>>> +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd,
>>> +                                   struct v4l2_subdev_state *state,
>>> +                                   u32 pad, u64 streams_mask)
>>> +{
>>> +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>>> +     u64 sink_streams;
>>> +     int ret;
>>> +
>>> +     core->isp->frm_sequence = 0;
>>> +     c3_isp_core_cfg_format(core->isp, state);
>>> +     c3_isp_core_enable(core->isp);
>>> +
>>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>> + C3_ISP_CORE_PAD_SINK_VIDEO,
>>> + &streams_mask);
>>> +     ret = v4l2_subdev_enable_streams(core->src_sd,
>>> +                                      core->src_sd_pad, sink_streams);
>>> +     if (ret) {
>>> +             c3_isp_core_disable(core->isp);
>>> +             return ret;
>>> +     }
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd,
>>> +                                    struct v4l2_subdev_state *state,
>>> +                                    u32 pad, u64 streams_mask)
>>> +{
>>> +     struct c3_isp_core *core = v4l2_get_subdevdata(sd);
>>> +     u64 sink_streams;
>>> +     int ret;
>>> +
>>> +     sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>>> + C3_ISP_CORE_PAD_SINK_VIDEO,
>>> + &streams_mask);
>>> +     ret = v4l2_subdev_disable_streams(core->src_sd,
>>> +                                       core->src_sd_pad, sink_streams);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     c3_isp_core_disable(core->isp);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd,
>>> +                                struct v4l2_subdev_state *state,
>>> +                                struct v4l2_subdev_krouting *routing)
>>> +{
>>> +     static const struct v4l2_mbus_framefmt format = {
>>> +             .width = C3_ISP_DEFAULT_WIDTH,
>>> +             .height = C3_ISP_DEFAULT_HEIGHT,
>>> +             .code = C3_ISP_CORE_DEF_SRC_PAD_FMT,
>>> +             .field = V4L2_FIELD_NONE,
>>> +             .colorspace = V4L2_COLORSPACE_SRGB,
>>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>>> +             .xfer_func = V4L2_XFER_FUNC_SRGB,
>>> +     };
>>> +     int ret;
>>> +
>>> +     ret = v4l2_subdev_routing_validate(sd, routing,
>>> + V4L2_SUBDEV_ROUTING_NO_SOURCE_STREAM_MIX);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_core_init_routing(struct v4l2_subdev *sd,
>>> +                                 struct v4l2_subdev_state *state)
>>> +{
>>> +     struct v4l2_subdev_route routes[2];
>>> +     struct v4l2_subdev_krouting routing;
>>> +
>>> +     routes[0].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
>>> +     routes[0].sink_stream = 0;
>>> +     routes[0].source_pad = C3_ISP_CORE_PAD_SOURCE_STATS;
>>> +     routes[0].source_stream = 0;
>>> +     routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>> This doesn't look right - I wouldn't expect a route between the video input and the sink. I wouldn't
>> really expect routes at all for this subdevice to be honest
>
>
> Will remove this route.
>
>>> +
>>> +     routes[1].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO;
>>> +     routes[1].sink_stream = 0;
>>> +     routes[1].source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO;
>>> +     routes[1].source_stream = 0;
>>> +     routes[1].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>>> +
>>> +     routing.num_routes = ARRAY_SIZE(routes);
>>> +     routing.routes = routes;
>>> +
>>> +     return c3_isp_core_cfg_routing(sd, state, &routing);
>>> +}
>>> +
>>> +static int c3_isp_core_set_routing(struct v4l2_subdev *sd,
>>> +                                struct v4l2_subdev_state *state,
>>> +                                enum v4l2_subdev_format_whence which,
>>> +                                struct v4l2_subdev_krouting *routing)
>>> +{
>>> +     bool is_streaming = v4l2_subdev_is_streaming(sd);
>>> +
>>> +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>>> +             return -EBUSY;
>>> +
>>> +     return c3_isp_core_cfg_routing(sd, state, routing);
>>> +}
>>> +
>>> +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd,
>>> +                                   struct v4l2_subdev_state *state,
>>> +                                   struct v4l2_subdev_mbus_code_enum *code)
>>> +{
>>> +     const struct c3_isp_mbus_format_info *info;
>>> +     int ret = 0;
>>> +
>>> +     switch (code->pad) {
>>> +     case C3_ISP_CORE_PAD_SINK_VIDEO:
>>> +     case C3_ISP_CORE_PAD_SOURCE_VIDEO:
>>> +             info = core_find_format_by_index(code->index, code->pad);
>>> +             if (!info)
>>> +                     ret = -EINVAL;
>>> +             else
>>> +                     code->code = info->mbus_code;
>>> +
>>> +             break;
>>> +     case C3_ISP_CORE_PAD_SINK_PARAMS:
>>> +     case C3_ISP_CORE_PAD_SOURCE_STATS:
>>> +             if (code->index)
>>> +                     ret = -EINVAL;
>>> +             else
>>> +                     code->code = MEDIA_BUS_FMT_METADATA_FIXED;
>>> +
>>> +             break;
>>> +     default:
>>> +             ret = -EINVAL;
>>> +             break;
>>> +     }
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static void c3_isp_core_set_sink_fmt(struct v4l2_subdev_state *state,
>>> +                                  struct v4l2_subdev_format *format)
>>> +{
>>> +     struct v4l2_mbus_framefmt *sink_fmt;
>>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>>> +
>>> +     sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>> +
>>> +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
>>> +     if (!isp_fmt)
>>> +             sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>>> +     else
>>> +             sink_fmt->code = format->format.code;
>>> +
>>> +     sink_fmt->width = clamp_t(u32, format->format.width,
>>> +                               C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>>> +     sink_fmt->height = clamp_t(u32, format->format.height,
>>> +                                C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>>> +
>>> +     format->format = *sink_fmt;
>>> +}
>>> +
>>> +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state,
>>> +                                    struct v4l2_subdev_format *format)
>>> +{
>>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>>> +     struct v4l2_mbus_framefmt *sink_fmt;
>>> +     struct v4l2_mbus_framefmt *src_fmt;
>>> +
>>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>>> +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>> +
>>> +     isp_fmt = core_find_format_by_code(format->format.code, format->pad);
>>> +     if (!isp_fmt)
>>> +             src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>>> +     else
>>> +             src_fmt->code = format->format.code;
>>> +
>>> +     /* The source size must be same with the sink size. */
>>> +     src_fmt->width  = sink_fmt->width;
>>> +     src_fmt->height = sink_fmt->height;
>> The source size should be propagated when the sink format is set then rather than picking it up on a
>> subsequent .set_fmt() call for the source pad
>
>
> Will set the source size when the sink format is set.
>
>>> +
>>> +     format->format = *src_fmt;
>>> +}
>>> +
>>> +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd,
>>> +                            struct v4l2_subdev_state *state,
>>> +                            struct v4l2_subdev_format *format)
>>> +{
>>> +     struct v4l2_mbus_framefmt *fmt;
>>> +
>>> +     switch (format->pad) {
>>> +     case C3_ISP_CORE_PAD_SINK_VIDEO:
>>> +             c3_isp_core_set_sink_fmt(state, format);
>>> +             break;
>>> +     case C3_ISP_CORE_PAD_SINK_PARAMS:
>>> +     case C3_ISP_CORE_PAD_SOURCE_STATS:
>>> +             fmt = v4l2_subdev_state_get_format(state, format->pad);
>>> +             format->format = *fmt;
>>> +             break;
>>> +     case C3_ISP_CORE_PAD_SOURCE_VIDEO:
>>> +             c3_isp_core_set_source_fmt(state, format);
>>> +             break;
>>> +     default:
>>> +             dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
>>> +             return -ENOTTY;
>>> +     }
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_core_init_state(struct v4l2_subdev *sd,
>>> +                               struct v4l2_subdev_state *state)
>>> +{
>>> +     struct v4l2_mbus_framefmt *sink_fmt;
>>> +     struct v4l2_mbus_framefmt *src_fmt;
>>> +
>>> +     /* Video sink pad */
>>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO);
>>> +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>>> +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>>> +     sink_fmt->field = V4L2_FIELD_NONE;
>>> +     sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT;
>>> +     sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>>> +     sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
>>> +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>> +     sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
>>> +
>>> +     /* Video source pad */
>>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO);
>>> +     src_fmt->width = C3_ISP_DEFAULT_WIDTH;
>>> +     src_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>>> +     src_fmt->field = V4L2_FIELD_NONE;
>>> +     src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT;
>>> +     src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>> +     src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>>> +     src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>> +     src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>>> +
>>> +     /* Parameters pad */
>>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS);
>>> +     sink_fmt->width = 0;
>>> +     sink_fmt->height = 0;
>>> +     sink_fmt->field = V4L2_FIELD_NONE;
>>> +     sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>>> +
>>> +     /* Statistics pad */
>>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS);
>>> +     src_fmt->width = 0;
>>> +     src_fmt->height = 0;
>>> +     src_fmt->field = V4L2_FIELD_NONE;
>>> +     src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
>>> +
>>> +     return c3_isp_core_init_routing(sd, state);
>>> +}
>>> +
>>> +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = {
>>> +     .enum_mbus_code = c3_isp_core_enum_mbus_code,
>>> +     .get_fmt = v4l2_subdev_get_fmt,
>>> +     .set_fmt = c3_isp_core_set_fmt,
>>> +     .enable_streams = c3_isp_core_enable_streams,
>>> +     .disable_streams = c3_isp_core_disable_streams,
>>> +     .set_routing = c3_isp_core_set_routing,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = {
>>> +     .pad = &c3_isp_core_pad_ops,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = {
>>> +     .init_state = c3_isp_core_init_state,
>>> +};
>>> +
>>> +static int c3_isp_core_link_validate(struct media_link *link)
>>> +{
>>> +     if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS)
>>> +             return 0;
>>> +
>>> +     return v4l2_subdev_link_validate(link);
>>> +}
>>> +
>>> +/* Media entity operations */
>>> +static const struct media_entity_operations c3_isp_core_entity_ops = {
>>> +     .link_validate = c3_isp_core_link_validate,
>>> +};
>>> +
>>> +int c3_isp_core_register(struct c3_isp_device *isp)
>>> +{
>>> +     struct c3_isp_core *core = &isp->core;
>>> +     struct v4l2_subdev *sd = &core->sd;
>>> +     int ret;
>>> +
>>> +     v4l2_subdev_init(sd, &c3_isp_core_subdev_ops);
>>> +     sd->owner = THIS_MODULE;
>>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>> +     sd->internal_ops = &c3_isp_core_internal_ops;
>>> +     snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME);
>>> +
>>> +     sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
>>> +     sd->entity.ops = &c3_isp_core_entity_ops;
>>> +
>>> +     core->isp = isp;
>>> +     sd->dev = isp->dev;
>>> +     v4l2_set_subdevdata(sd, core);
>>> +
>>> +     core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
>>> +     core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
>>> +     core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
>>> +     core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
>>> +     ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     ret = v4l2_subdev_init_finalize(sd);
>>> +     if (ret)
>>> +             goto err_entity_cleanup;
>>> +
>>> +     ret = v4l2_device_register_subdev(&isp->v4l2_dev, sd);
>>> +     if (ret)
>>> +             goto err_subdev_cleanup;
>>> +
>>> +     return 0;
>>> +
>>> +err_subdev_cleanup:
>>> +     v4l2_subdev_cleanup(sd);
>>> +err_entity_cleanup:
>>> +     media_entity_cleanup(&sd->entity);
>>> +     return ret;
>>> +}
>>> +
>>> +void c3_isp_core_unregister(struct c3_isp_device *isp)
>>> +{
>>> +     struct c3_isp_core *core = &isp->core;
>>> +     struct v4l2_subdev *sd = &core->sd;
>>> +
>>> +     v4l2_device_unregister_subdev(sd);
>>> +     v4l2_subdev_cleanup(sd);
>>> +     media_entity_cleanup(&sd->entity);
>>> +}
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c 
>>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
>>> new file mode 100644
>>> index 000000000000..a57b9f8dbc3c
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c
>>> @@ -0,0 +1,486 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/clk.h>
>>> +#include <linux/device.h>
>>> +#include <linux/module.h>
>>> +#include <linux/mutex.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pm_runtime.h>
>>> +
>>> +#include <media/v4l2-common.h>
>>> +#include <media/v4l2-device.h>
>>> +#include <media/v4l2-fwnode.h>
>>> +#include <media/v4l2-mc.h>
>>> +
>>> +#include "c3-isp-common.h"
>>> +#include "c3-isp-regs.h"
>>> +
>>> +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
>>> +{
>>> +     return readl(isp->base + reg);
>>> +}
>>> +
>>> +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
>>> +{
>>> +     writel(val, isp->base + reg);
>>> +}
>>> +
>>> +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
>>> +{
>>> +     u32 orig, tmp;
>>> +
>>> +     orig = c3_isp_read(isp, reg);
>>> +
>>> +     tmp = orig & ~mask;
>>> +     tmp |= val & mask;
>>> +
>>> +     if (tmp != orig)
>>> +             c3_isp_write(isp, reg, tmp);
>>> +}
>>> +
>>> +bool c3_isp_pipeline_ready(struct c3_isp_device *isp)
>>> +{
>>> +     struct media_pipeline_entity_iter iter;
>>> +     unsigned int n_video_devices = 0;
>>> +     struct media_entity *entity;
>>> +     int ret;
>>> +
>>> +     ret = media_pipeline_entity_iter_init(&isp->pipe, &iter);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     media_pipeline_for_each_entity(&isp->pipe, &iter, entity) {
>>> +             if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
>>> +                     n_video_devices++;
>>> +     }
>>> +
>>> +     media_pipeline_entity_iter_cleanup(&iter);
>>> +
>>> +     return n_video_devices == isp->pipe.start_count;
>>> +}
>>> +
>>> +/* PM runtime suspend */
>>> +static int __maybe_unused c3_isp_runtime_suspend(struct device *dev)
>>> +{
>>> +     struct c3_isp_device *isp = dev_get_drvdata(dev);
>>> +
>>> +     clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +/* PM runtime resume */
>>> +static int __maybe_unused c3_isp_runtime_resume(struct device *dev)
>>> +{
>>> +     struct c3_isp_device *isp = dev_get_drvdata(dev);
>>> +
>>> +     return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
>>> +}
>>> +
>>> +static const struct dev_pm_ops c3_isp_pm_ops = {
>>> +     SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>>> +                             pm_runtime_force_resume)
>>> +     SET_RUNTIME_PM_OPS(c3_isp_runtime_suspend,
>>> +                        c3_isp_runtime_resume, NULL)
>>> +};
>>> +
>>> +/* IRQ handling */
>>> +static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
>>> +{
>>> +     struct c3_isp_device *isp = dev;
>>> +     u32 status;
>>> +
>>> +     /* Get irq status and clear irq status */
>>> +     status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
>>> +     c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
>>> +
>>> +     if (status & TOP_IRQ_FRAME_DONE) {
>>> +             c3_isp_stats_done(isp);
>>> +             c3_isp_params_done(isp);
>>> +             c3_isp_captures_done(isp);
>>> +             isp->frm_sequence++;
>>> +     }
>>> +
>>> +     if (status & TOP_IRQ_STATS_ERR)
>>> +             dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n");
>>> +
>>> +     return IRQ_HANDLED;
>>> +}
>>> +
>>> +/* Subdev notifier register */
>>> +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
>>> +                            struct v4l2_subdev *sd,
>>> +                            struct v4l2_async_connection *asc)
>>> +{
>>> +     struct c3_isp_device *isp =
>>> +             container_of(notifier, struct c3_isp_device, notifier);
>>> +     struct c3_isp_core *core = &isp->core;
>>> +     struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
>>> +     int ret;
>>> +
>>> +     ret = media_entity_get_fwnode_pad(&sd->entity,
>>> +                                       sd->fwnode, MEDIA_PAD_FL_SOURCE);
>>> +     if (ret < 0) {
>>> +             dev_err(isp->dev, "Failed to find pad for %s\n", sd->name);
>>> +             return ret;
>>> +     }
>>> +
>>> +     core->src_sd = sd;
>>> +     core->src_sd_pad = ret;
>>> +
>>> +     return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
>>> + MEDIA_LNK_FL_IMMUTABLE);
>>> +}
>>> +
>>> +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
>>> +{
>>> +     struct c3_isp_device *isp =
>>> +             container_of(notifier, struct c3_isp_device, notifier);
>>> +     int ret;
>>> +
>>> +     ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
>>> +     if (ret < 0) {
>>> +             dev_err(isp->dev,
>>> +                     "Failed to register subdev nodes: %d\n", ret);
>>> +             return ret;
>>> +     }
>>> +
>>> +     dev_info(isp->dev, "notify complete\n");
>>> +
>>> +     return media_device_register(&isp->media_dev);
>>> +}
>>> +
>>> +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc)
>>> +{
>>> +     struct c3_isp_device *isp =
>>> +             container_of(asc->notifier, struct c3_isp_device, notifier);
>>> +
>>> +     media_device_unregister(&isp->media_dev);
>>> +}
>>> +
>>> +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
>>> +     .bound = c3_isp_notify_bound,
>>> +     .complete = c3_isp_notify_complete,
>>> +     .destroy = c3_isp_notify_destroy,
>>> +};
>>> +
>>> +static int c3_isp_async_nf_register(struct c3_isp_device *isp)
>>> +{
>>> +     struct v4l2_async_connection *asc;
>>> +     struct fwnode_handle *ep;
>>> +     int ret;
>>> +
>>> +     v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
>>> +
>>> +     ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
>>> + FWNODE_GRAPH_ENDPOINT_NEXT);
>>> +     if (!ep)
>>> +             return -ENOTCONN;
>>> +
>>> +     asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
>>> +                                           struct v4l2_async_connection);
>>> +     if (IS_ERR(asc)) {
>>> +             fwnode_handle_put(ep);
>>> +             return PTR_ERR(asc);
>>> +     }
>>> +
>>> +     fwnode_handle_put(ep);
>>
>>
>> Move this above if (IS_ERR(asc)) and you don't need to call it in two places
>>
>
> Will move this above "if (IS_ERR(asc))".
>
>>> +
>>> +     isp->notifier.ops = &c3_isp_notify_ops;
>>> +     ret = v4l2_async_nf_register(&isp->notifier);
>>> +     if (ret)
>>> +             v4l2_async_nf_cleanup(&isp->notifier);
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
>>> +{
>>> +     v4l2_async_nf_unregister(&isp->notifier);
>>> +     v4l2_async_nf_cleanup(&isp->notifier);
>>> +}
>>> +
>>> +static int c3_isp_v4l2_register(struct c3_isp_device *isp)
>>> +{
>>> +     struct media_device *media_dev = &isp->media_dev;
>>> +     struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
>>> +     int ret;
>>> +
>>> +     /* Initialize media device */
>>> +     strscpy(media_dev->model, C3_ISP_DRIVER_NAME,
>>> +             sizeof(media_dev->model));
>>> +     media_dev->dev = isp->dev;
>>> +
>>> +     media_device_init(media_dev);
>>> +
>>> +     /* Initialize v4l2 device */
>>> +     v4l2_dev->mdev = media_dev;
>>> +     strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME,
>>> +             sizeof(v4l2_dev->name));
>>> +
>>> +     ret = v4l2_device_register(isp->dev, v4l2_dev);
>>> +     if (ret) {
>>> +             media_device_cleanup(media_dev);
>>> +             dev_err(isp->dev,
>>> +                     "Failed to register V4L2 device: %d\n", ret);
>>> +     }
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp)
>>> +{
>>> +     v4l2_device_unregister(&isp->v4l2_dev);
>>> +     media_device_cleanup(&isp->media_dev);
>>> +}
>>> +
>>> +static void c3_isp_remove_links(struct c3_isp_device *isp)
>>> +{
>>> +     unsigned int i;
>>> +
>>> +     media_entity_remove_links(&isp->core.sd.entity);
>>> +
>>> +     for (i = 0; i < C3_ISP_NUM_RSZ; i++)
>>> + media_entity_remove_links(&isp->resizers[i].sd.entity);
>>> +
>>> +     for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
>>> + media_entity_remove_links(&isp->caps[i].vdev.entity);
>>> +}
>>> +
>>> +static int c3_isp_create_links(struct c3_isp_device *isp)
>>> +{
>>> +     unsigned int i;
>>> +     int ret;
>>> +
>>> +     for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
>>> +             ret = media_create_pad_link(&isp->resizers[i].sd.entity,
>>> + C3_ISP_RESIZER_PAD_SOURCE,
>>> + &isp->resizers[i].cap->vdev.entity,
>>> +                                         0, MEDIA_LNK_FL_ENABLED);
>>> +             if (ret) {
>>> +                     dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i);
>>> +                     goto err_remove_links;
>>> +             }
>>> +
>>> +             ret = media_create_pad_link(&isp->core.sd.entity,
>>> + C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>> + &isp->resizers[i].sd.entity,
>>> + C3_ISP_RESIZER_PAD_SINK,
>>> + MEDIA_LNK_FL_ENABLED);
>>> +             if (ret) {
>>> +                     dev_err(isp->dev, "Failed to link core and resizer %u\n", i);
>>> +                     goto err_remove_links;
>>> +             }
>>> +     }
>>> +
>>> +     ret = media_create_pad_link(&isp->core.sd.entity,
>>> + C3_ISP_CORE_PAD_SOURCE_STATS,
>>> + &isp->stats.vdev.entity,
>>> +                                 0, MEDIA_LNK_FL_ENABLED);
>>> +     if (ret) {
>>> +             dev_err(isp->dev, "Failed to link core and stats\n");
>>> +             goto err_remove_links;
>>> +     }
>>> +
>>> +     ret = media_create_pad_link(&isp->params.vdev.entity, 0,
>>> +                                 &isp->core.sd.entity,
>>> +                                 C3_ISP_CORE_PAD_SINK_PARAMS,
>>> +                                 MEDIA_LNK_FL_ENABLED);
>>> +     if (ret) {
>>> +             dev_err(isp->dev, "Failed to link params and core\n");
>>> +             goto err_remove_links;
>>> +     }
>>> +
>>> +     return 0;
>>> +
>>> +err_remove_links:
>>> +     c3_isp_remove_links(isp);
>>> +     return ret;
>>> +}
>>> +
>>> +static int c3_isp_videos_register(struct c3_isp_device *isp)
>>> +{
>>> +     int ret;
>>> +
>>> +     ret = c3_isp_captures_register(isp);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     ret = c3_isp_stats_register(isp);
>>> +     if (ret)
>>> +             goto err_captures_unregister;
>>> +
>>> +     ret = c3_isp_params_register(isp);
>>> +     if (ret)
>>> +             goto err_stats_unregister;
>>> +
>>> +     ret = c3_isp_create_links(isp);
>>> +     if (ret)
>>> +             goto err_params_unregister;
>>> +
>>> +     return 0;
>>> +
>>> +err_params_unregister:
>>> +     c3_isp_params_unregister(isp);
>>> +err_stats_unregister:
>>> +     c3_isp_stats_unregister(isp);
>>> +err_captures_unregister:
>>> +     c3_isp_captures_unregister(isp);
>>> +     return ret;
>>> +}
>>> +
>>> +static void c3_isp_videos_unregister(struct c3_isp_device *isp)
>>> +{
>>> +     c3_isp_remove_links(isp);
>>> +     c3_isp_params_unregister(isp);
>>> +     c3_isp_stats_unregister(isp);
>>> +     c3_isp_captures_unregister(isp);
>>> +}
>>> +
>>> +static int c3_isp_cfg_clocks(struct c3_isp_device *isp)
>>> +{
>>> +     const struct c3_isp_info *info = isp->info;
>>> +     int ret;
>>> +     u32 i;
>>> +
>>> +     for (i = 0; i < info->clock_num; i++)
>>> +             isp->clks[i].id = info->clocks[i];
>>> +
>>> +     ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     for (i = 0; i < info->clock_num; i++) {
>>> +             if (!info->clock_rates[i])
>>> +                     continue;
>>> +             ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]);
>>> +             if (ret) {
>>> +                     dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i],
>>> +                             info->clock_rates[i]);
>>> +                     return ret;
>>> +             }
>>> +     }
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_probe(struct platform_device *pdev)
>>> +{
>>> +     struct device *dev = &pdev->dev;
>>> +     struct c3_isp_device *isp;
>>> +     int irq;
>>> +     int ret;
>>> +
>>> +     isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
>>> +     if (!isp)
>>> +             return -ENOMEM;
>>> +
>>> +     isp->info = of_device_get_match_data(dev);
>>> +     isp->dev = dev;
>>> +
>>> +     isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
>>> +     if (IS_ERR(isp->base))
>>> +             return dev_err_probe(dev, PTR_ERR(isp->base),
>>> +                                  "Failed to ioremap resource\n");
>>> +
>>> +     irq = platform_get_irq(pdev, 0);
>>> +     if (irq < 0)
>>> +             return irq;
>>> +
>>> +     ret = c3_isp_cfg_clocks(isp);
>>> +     if (ret)
>>> +             return dev_err_probe(dev, ret, "Failed to configure clocks\n");
>>> +
>>> +     platform_set_drvdata(pdev, isp);
>>> +
>>> +     pm_runtime_enable(dev);
>>> +
>>> +     ret = c3_isp_v4l2_register(isp);
>>> +     if (ret)
>>> +             goto err_runtime_disable;
>>> +
>>> +     ret = c3_isp_core_register(isp);
>>> +     if (ret)
>>> +             goto err_v4l2_unregister;
>>> +
>>> +     ret = c3_isp_resizers_register(isp);
>>> +     if (ret)
>>> +             goto err_core_unregister;
>>> +
>>> +     ret = c3_isp_async_nf_register(isp);
>>> +     if (ret)
>>> +             goto err_resizers_unregister;
>>> +
>>> +     ret = c3_isp_videos_register(isp);
>>> +     if (ret)
>>> +             goto err_nf_unregister;
>>> +
>>> +     ret = devm_request_irq(dev, irq,
>>> +                            c3_isp_irq_handler, IRQF_SHARED,
>>> +                            dev_driver_string(dev), isp);
>>> +     if (ret)
>>> +             goto err_streams_unregister;
>>> +
>>> +     mutex_init(&isp->lock);
>>> +
>>> +     return 0;
>>> +
>>> +err_streams_unregister:
>>> +     c3_isp_videos_unregister(isp);
>>> +err_nf_unregister:
>>> +     c3_isp_async_nf_unregister(isp);
>>> +err_resizers_unregister:
>>> +     c3_isp_resizers_unregister(isp);
>>> +err_core_unregister:
>>> +     c3_isp_core_unregister(isp);
>>> +err_v4l2_unregister:
>>> +     c3_isp_v4l2_unregister(isp);
>>> +err_runtime_disable:
>>> +     pm_runtime_disable(dev);
>>> +     return ret;
>>> +};
>>> +
>>> +static void c3_isp_remove(struct platform_device *pdev)
>>> +{
>>> +     struct c3_isp_device *isp = platform_get_drvdata(pdev);
>>> +
>>> +     mutex_destroy(&isp->lock);
>>> +     c3_isp_videos_unregister(isp);
>>> +     c3_isp_async_nf_unregister(isp);
>>> +     c3_isp_core_unregister(isp);
>>> +     c3_isp_resizers_unregister(isp);
>>> +     c3_isp_v4l2_unregister(isp);
>>> +     pm_runtime_disable(isp->dev);
>>> +};
>>> +
>>> +static const struct c3_isp_info isp_info = {
>>> +     .clocks = {"vapb", "isp0"},
>>> +     .clock_rates = {0, 400000000},
>>> +     .clock_num = 2
>>> +};
>>> +
>>> +static const struct of_device_id c3_isp_of_match[] = {
>>> +     { .compatible = "amlogic,c3-isp",
>>> +       .data = &isp_info },
>>> +     { },
>>> +};
>>> +MODULE_DEVICE_TABLE(of, c3_isp_of_match);
>>> +
>>> +static struct platform_driver c3_isp_driver = {
>>> +     .probe = c3_isp_probe,
>>> +     .remove = c3_isp_remove,
>>> +     .driver = {
>>> +             .name = "c3-isp",
>>> +             .of_match_table = c3_isp_of_match,
>>> +             .pm = &c3_isp_pm_ops,
>>> +     },
>>> +};
>>> +
>>> +module_platform_driver(c3_isp_driver);
>>> +
>>> +MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
>>> +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c 
>>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
>>> new file mode 100644
>>> index 000000000000..8a6b7ce86eaf
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c
>>> @@ -0,0 +1,857 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/pm_runtime.h>
>>> +
>>> +#include <media/v4l2-ctrls.h>
>>> +#include <media/v4l2-event.h>
>>> +#include <media/v4l2-ioctl.h>
>>> +#include <media/v4l2-mc.h>
>>> +#include <media/videobuf2-dma-contig.h>
>>> +
>>> +#include "c3-isp-common.h"
>>> +#include "c3-isp-regs.h"
>>> +#include "include/uapi/c3-isp-config.h"
>>> +
>>> +typedef void (*block_handler)(struct c3_isp_device *isp,
>>> +                           struct c3_isp_param_block_header *block);
>>> +
>>> +struct c3_isp_block_handler {
>>> +     size_t size;
>>> +     block_handler handler;
>>> +};
>>> +
>>> +/* Hardware configuration */
>>> +
>>> +static void c3_isp_params_cfg_wb_change(struct c3_isp_device *isp,
>>> +                                     struct c3_isp_param_block_header *block)
>>> +{
>>> +     struct wb_change_cfg *wb = (struct wb_change_cfg *)block;
>>
>> Sakari had a cool suggestion that abstracted the parameter blocks better using a union without the
>> need to cast everywhere. See the use of union mali_c55_params_block in
>> https://lore.kernel.org/linux-media/20241106100534.768400-17-dan.scally@ideasonboard.com/T/#u
>>
>>
>> OK, will refer to the union mali_c55_params_block.
>>> +
>>> +     if (!block->enabled) {
>>> +             c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
>>> +                                TOP_BEO_CTRL_WB_EN, false);
>>> +             return;
>>> +     }
>>> +
>>> +     c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL,
>>> +                        TOP_BEO_CTRL_WB_EN, TOP_BEO_CTRL_WB_EN);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, LSWB_WB_GAIN0_MASK,
>>> +                        wb->wb_gain[0] << LSWB_WB_GAIN0_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0,
>>> +                        LSWB_WB_GAIN1_MASK, wb->wb_gain[1]);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, LSWB_WB_GAIN2_MASK,
>>> +                        wb->wb_gain[2] << LSWB_WB_GAIN2_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1,
>>> +                        LSWB_WB_GAIN3_MASK, wb->wb_gain[3]);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN2,
>>> +                        LSWB_WB_GAIN4_MASK, wb->wb_gain[4]);
>>> +
>>> +     c3_isp_write(isp, ISP_LSWB_WB_LIMIT0,
>>> +                  LSWB_WB_LIMIT0(wb->wb_limit[0]) | LSWB_WB_LIMIT1(wb->wb_limit[1]));
>>> +
>>> +     c3_isp_write(isp, ISP_LSWB_WB_LIMIT1,
>>> +                  LSWB_WB_LIMIT2(wb->wb_limit[2]) | LSWB_WB_LIMIT3(wb->wb_limit[3]));
>>> +
>>> +     c3_isp_update_bits(isp, ISP_LSWB_WB_LIMIT2, LSWB_WB_LIMIT4_MASK,
>>> +                        wb->wb_limit[4]);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_BL12_GRBGI0_MASK,
>>> +                        wb->ae_bl12_grbgi[0]);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_GAIN_GRBGI0_MASK,
>>> +                        wb->ae_gain_grbgi[0] << AE_CRTL2_GAIN_GRBGI0_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_BL12_GRBGI1_MASK,
>>> +                        wb->ae_bl12_grbgi[1]);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_GAIN_GRBGI1_MASK,
>>> +                        wb->ae_gain_grbgi[1] << AE_CRTL2_GAIN_GRBGI1_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_BL12_GRBGI2_MASK,
>>> +                        wb->ae_bl12_grbgi[2]);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_GAIN_GRBGI2_MASK,
>>> +                        wb->ae_gain_grbgi[2] << AE_CRTL2_GAIN_GRBGI2_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_BL12_GRBGI3_MASK,
>>> +                        wb->ae_bl12_grbgi[3]);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_GAIN_GRBGI3_MASK,
>>> +                        wb->ae_gain_grbgi[3] << AE_CRTL2_GAIN_GRBGI3_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_BL12_GRBGI4_MASK,
>>> +                        wb->ae_bl12_grbgi[4]);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_GAIN_GRBGI4_MASK,
>>> +                        wb->ae_gain_grbgi[4] << AE_CRTL2_GAIN_GRBGI4_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_wb_luma(struct c3_isp_device *isp,
>>> +                                   struct c3_isp_param_block_header *block)
>>> +{
>>> +     struct wb_luma_cfg *wb_luma = (struct wb_luma_cfg *)block;
>>> +
>>> +     if (!block->enabled)
>>> +             return;
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_0,
>>> +                        AWB_STAT_BLC20_GR_MASK, wb_luma->awb_stat_blc20[0]);
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_1,
>>> +                        AWB_STAT_BLC20_R_MASK, wb_luma->awb_stat_blc20[1]);
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_2,
>>> +                        AWB_STAT_BLC20_B_MASK, wb_luma->awb_stat_blc20[2]);
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_3,
>>> +                        AWB_STAT_BLC20_GB_MASK, wb_luma->awb_stat_blc20[3]);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_0,
>>> +                        AWB_STAT_GAIN10_GR_MASK, wb_luma->awb_stat_gain10[0]);
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_1,
>>> +                        AWB_STAT_GAIN10_R_MASK, wb_luma->awb_stat_gain10[1]);
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_2,
>>> +                        AWB_STAT_GAIN10_B_MASK, wb_luma->awb_stat_gain10[2]);
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_3,
>>> +                        AWB_STAT_GAIN10_GB_MASK, wb_luma->awb_stat_gain10[3]);
>>> +
>>> +     c3_isp_write(isp, ISP_AWB_STAT_SATUR_CTRL,
>>> + AWB_STAT_SATUR_LOW(wb_luma->awb_stat_satur_low) |
>>> + AWB_STAT_SATUR_HIGH(wb_luma->awb_stat_satur_high));
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_wb_triangle(struct c3_isp_device *isp,
>>> +                                       struct c3_isp_param_block_header *block)
>>> +{
>>> +     struct wb_triangle_cfg *wb = (struct wb_triangle_cfg *)block;
>>> +
>>> +     if (!block->enabled)
>>> +             return;
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_CTRL2, AWB_STAT_SATUR_VALID_MASK,
>>> +                        wb->awb_stat_satur_vald);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MIN_MASK,
>>> +                        wb->awb_stat_rg_min);
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MAX_MASK,
>>> +                        wb->awb_stat_rg_max << AWB_STAT_RG_MAX_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MIN_MASK,
>>> +                        wb->awb_stat_bg_min);
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MAX_MASK,
>>> +                        wb->awb_stat_bg_max << AWB_STAT_BG_MAX_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_LOW_MASK,
>>> +                        wb->awb_stat_rg_low);
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_HIGH_MASK,
>>> +                        wb->awb_stat_rg_high << AWB_STAT_RG_HIGH_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_LOW_MASK,
>>> +                        wb->awb_stat_bg_low);
>>> +     c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_HIGH_MASK,
>>> +                        wb->awb_stat_bg_high << AWB_STAT_BG_HIGH_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_awb_stats(struct c3_isp_device *isp,
>>> +                                     struct c3_isp_param_block_header *block)
>>> +{
>>> +     struct awb_stats_cfg *awb_stats = (struct awb_stats_cfg *)block;
>>> +     u32 *weight = awb_stats->awb_stat_blk_weight;
>>> +     int idx_base;
>>> +     int group;
>>> +     int i;
>>> +
>>> +     if (!block->enabled)
>>> +             return;
>>> +
>>> +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AWB_STAT_SWITCH_MASK,
>>> +                        awb_stats->awb_stat_switch << TOP_3A_AWB_STAT_SWITCH_SHIFT);
>>> +
>>> +     /* Calculate the group number */
>>> +     group = AWB_BLOCK_WT_NUM / AWB_BLK_WT_DATA_NUM_OF_GROUP;
>>> +
>>> +     /* Set the weight address to 0 position */
>>> +     c3_isp_write(isp, ISP_AWB_BLK_WT_ADDR, 0);
>>> +     for (i = 0; i < group; i++) {
>>> +             idx_base = i * AWB_BLK_WT_DATA_NUM_OF_GROUP;
>>> +             c3_isp_write(isp, ISP_AWB_BLK_WT_DATA,
>>> +                          AWB_BLK_WT_DATA0(weight[idx_base + 0]) |
>>> +                          AWB_BLK_WT_DATA1(weight[idx_base + 1]) |
>>> +                          AWB_BLK_WT_DATA2(weight[idx_base + 2]) |
>>> +                          AWB_BLK_WT_DATA3(weight[idx_base + 3]) |
>>> +                          AWB_BLK_WT_DATA4(weight[idx_base + 4]) |
>>> +                          AWB_BLK_WT_DATA5(weight[idx_base + 5]) |
>>> +                          AWB_BLK_WT_DATA6(weight[idx_base + 6]) |
>>> +                          AWB_BLK_WT_DATA7(weight[idx_base + 7]));
>>> +     }
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_ae_stats(struct c3_isp_device *isp,
>>> +                                    struct c3_isp_param_block_header *block)
>>> +{
>>> +     struct ae_stats_cfg *ae_stats = (struct ae_stats_cfg *)block;
>>> +     u32 *weight = ae_stats->ae_stat_blk_weight;
>>> +     int idx_base;
>>> +     int group;
>>> +     int i;
>>> +
>>> +     if (!block->enabled)
>>> +             return;
>>> +
>>> +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AE_STAT_SWITCH_MASK,
>>> +                        ae_stats->ae_stat_switch << TOP_3A_AE_STAT_SWITCH_SHIFT);
>>> +
>>> +     /* Calculate the group number */
>>> +     group = AE_BLOCK_WT_NUM / AE_BLK_WT_DATA_NUM_OF_GROUP;
>>> +
>>> +     /* Set the weight address to 0 position */
>>> +     c3_isp_write(isp, ISP_AE_BLK_WT_ADDR, 0);
>>> +     for (i = 0; i < group; i++) {
>>> +             idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
>>> +             c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
>>> +                          AE_BLK_WT_DATA0(weight[idx_base + 0]) |
>>> +                          AE_BLK_WT_DATA1(weight[idx_base + 1]) |
>>> +                          AE_BLK_WT_DATA2(weight[idx_base + 2]) |
>>> +                          AE_BLK_WT_DATA3(weight[idx_base + 3]) |
>>> +                          AE_BLK_WT_DATA4(weight[idx_base + 4]) |
>>> +                          AE_BLK_WT_DATA5(weight[idx_base + 5]) |
>>> +                          AE_BLK_WT_DATA6(weight[idx_base + 6]) |
>>> +                          AE_BLK_WT_DATA7(weight[idx_base + 7]));
>>> +     }
>>> +
>>> +     /* Write the last weight data */
>>> +     idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP;
>>> +     c3_isp_write(isp, ISP_AE_BLK_WT_DATA,
>>> +                  AE_BLK_WT_DATA0(weight[idx_base + 0]) |
>>> +                  AE_BLK_WT_DATA1(weight[idx_base + 1]) |
>>> +                  AE_BLK_WT_DATA2(weight[idx_base + 2]) |
>>> +                  AE_BLK_WT_DATA3(weight[idx_base + 3]) |
>>> +                  AE_BLK_WT_DATA4(weight[idx_base + 4]) |
>>> +                  AE_BLK_WT_DATA5(weight[idx_base + 5]) |
>>> +                  AE_BLK_WT_DATA6(weight[idx_base + 6]));
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_af_stats(struct c3_isp_device *isp,
>>> +                                    struct c3_isp_param_block_header *block)
>>> +{
>>> +     struct af_stats_cfg *af_stats = (struct af_stats_cfg *)block;
>>> +
>>> +     if (!block->enabled)
>>> +             return;
>>> +
>>> +     c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AF_STAT_SWITCH_MASK,
>>> +                        af_stats->af_stat_switch << TOP_3A_AF_STAT_SWITCH_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_pst_gamma(struct c3_isp_device *isp,
>>> +                                     struct c3_isp_param_block_header *block)
>>> +{
>>> +     struct pst_gamma_cfg *gamma = (struct pst_gamma_cfg *)block;
>>> +     int idx_base;
>>> +     int i, j;
>>> +
>>> +     if (!block->enabled) {
>>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, false);
>>> +             return;
>>> +     }
>>> +
>>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, TOP_BED_GAMMA_EN);
>>> +
>>> +     for (j = 0; j < GAMMA_LUT_GROUP_NUM; j++) {
>>> +             c3_isp_write(isp, ISP_PST_GAMMA_LUT_ADDR, PST_GAMMA_LUT_ADDR(j));
>>> +
>>> +             /* Calculate the block number */
>>> +             for (i = 0; i < GAMMA_LUT_POINT_NUM / GAMMA_LUT_DATA_NUM_OF_GROUP; i++) {
>>> +                     idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
>>> +                     c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>>> + PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]) |
>>> + PST_GAMMA_LUT_DATA1(gamma->pst_gamma_lut[j][idx_base + 1]));
>>> +             }
>>> +
>>> +             /* Write the last one lut data of group j */
>>> +             idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP;
>>> +             c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA,
>>> + PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]));
>>> +     }
>>> +}
>>> +
>>> +static void c3_isp_params_cfg_dmsc(struct c3_isp_device *isp,
>>> +                                struct c3_isp_param_block_header *block)
>>> +{
>>> +     if (!block->enabled) {
>>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, false);
>>> +             return;
>>> +     }
>>> +
>>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, TOP_BED_DMSC_EN);
>>> +}
>>> +
>>> +/* Configure 4 x 3 ccm matrix */
>>> +static void c3_isp_params_cfg_ccm(struct c3_isp_device *isp,
>>> +                               struct c3_isp_param_block_header *block)
>>> +{
>>> +     struct ccm_cfg *ccm = (struct ccm_cfg *)block;
>>> +
>>> +     if (!block->enabled) {
>>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, false);
>>> +             return;
>>> +     }
>>> +
>>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, TOP_BED_CCM_EN);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_00_MASK,
>>> +                        ccm->ccm_4x3matrix[0][0]);
>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_01_MASK,
>>> +                        ccm->ccm_4x3matrix[0][1] << CCM_MTX_01_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_02_MASK,
>>> +                        ccm->ccm_4x3matrix[0][2]);
>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_03_MASK,
>>> +                        ccm->ccm_4x3matrix[0][3] << CCM_MTX_03_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_10_MASK,
>>> +                        ccm->ccm_4x3matrix[1][0]);
>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_11_MASK,
>>> +                        ccm->ccm_4x3matrix[1][1] << CCM_MTX_11_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_12_MASK,
>>> +                        ccm->ccm_4x3matrix[1][2]);
>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_13_MASK,
>>> +                        ccm->ccm_4x3matrix[1][3] << CCM_MTX_13_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_20_MASK,
>>> +                        ccm->ccm_4x3matrix[2][0]);
>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_21_MASK,
>>> +                        ccm->ccm_4x3matrix[2][1] << CCM_MTX_21_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_22_MASK,
>>> +                        ccm->ccm_4x3matrix[2][2]);
>>> +     c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_23_MASK,
>>> +                        ccm->ccm_4x3matrix[2][3] << CCM_MTX_23_SHIFT);
>>> +}
>>> +
>>> +/* Configure color space conversion matrix parameters */
>>> +static void c3_isp_params_cfg_csc(struct c3_isp_device *isp,
>>> +                               struct c3_isp_param_block_header *block)
>>> +{
>>> +     struct csc_cfg *csc = (struct csc_cfg *)block;
>>> +
>>> +     if (!block->enabled) {
>>> +             c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, false);
>>> +             return;
>>> +     }
>>> +
>>> +     c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, TOP_BED_CM0_EN);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST0_MASK,
>>> +                        csc->cm0_offset_inp[0]);
>>> +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST1_MASK,
>>> +                        csc->cm0_offset_inp[1] << CM0_INP_OFST1_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CM0_INP_OFST2, CM0_INP_OFST2_MASK,
>>> +                        csc->cm0_offset_inp[2]);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_00_MASK,
>>> +                        csc->cm0_3x3matrix[0][0]);
>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_01_MASK,
>>> +                        csc->cm0_3x3matrix[0][1] << CM0_MTX_01_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_02_MASK,
>>> +                        csc->cm0_3x3matrix[0][2]);
>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_10_MASK,
>>> +                        csc->cm0_3x3matrix[1][0] << CM0_MTX_10_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_11_MASK,
>>> +                        csc->cm0_3x3matrix[1][1]);
>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_12_MASK,
>>> +                        csc->cm0_3x3matrix[1][2] << CM0_MTX_12_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_20_MASK,
>>> +                        csc->cm0_3x3matrix[2][0]);
>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_21_MASK,
>>> +                        csc->cm0_3x3matrix[2][1] << CM0_MTX_21_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_MTX_22_MASK,
>>> +                        csc->cm0_3x3matrix[2][2]);
>>> +     c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_OFST_OUP0_MASK,
>>> +                        csc->cm0_offset_oup[0] << CM0_OFST_OUP0_SHIFT);
>>> +
>>> +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP1_MASK,
>>> +                        csc->cm0_offset_oup[1]);
>>> +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP2_MASK,
>>> +                        csc->cm0_offset_oup[2] << CM0_OFST_OUP2_SHIFT);
>>> +     c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_MTX_RS_MASK,
>>> +                        csc->cm0_3x3mtrx_rs << CM0_MTX_RS_SHIFT);
>>> +}
>>> +
>>> +/* Set blc offset of each color channel */
>>> +static void c3_isp_params_cfg_blc(struct c3_isp_device *isp,
>>> +                               struct c3_isp_param_block_header *block)
>>> +{
>>> +     struct blc_cfg *blc = (struct blc_cfg *)block;
>>> +
>>> +     if (!block->enabled) {
>>> +             c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN, false);
>>> +             return;
>>> +     }
>>> +
>>> +     c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN,
>>> +                        TOP_BEO_CTRL_BLC_EN);
>>> +
>>> +     c3_isp_write(isp, ISP_FED_BL_OFST_GR, FED_BL_GR_OFST(blc->fe_bl_ofst[0]));
>>> +     c3_isp_write(isp, ISP_FED_BL_OFST_R, FED_BL_R_OFST(blc->fe_bl_ofst[1]));
>>> +     c3_isp_write(isp, ISP_FED_BL_OFST_B, FED_BL_B_OFST(blc->fe_bl_ofst[2]));
>>> +     c3_isp_write(isp, ISP_FED_BL_OFST_GB, FED_BL_GB_OFST(blc->fe_bl_ofst[3]));
>>> +     c3_isp_write(isp, ISP_FED_BL_OFST_IR, FED_BL_IR_OFST(blc->fe_bl_ofst[4]));
>>> +
>>> +     c3_isp_write(isp, ISP_LSWB_BLC_OFST0,
>>> +                  LSWB_BLC_OFST0(blc->blc_ofst[0]) | LSWB_BLC_OFST1(blc->blc_ofst[1]));
>>> +     c3_isp_write(isp, ISP_LSWB_BLC_OFST1,
>>> +                  LSWB_BLC_OFST2(blc->blc_ofst[2]) | LSWB_BLC_OFST3(blc->blc_ofst[3]));
>>> +     c3_isp_write(isp, ISP_LSWB_BLC_OFST2, LSWB_BLC_OFST4(blc->blc_ofst[4]));
>>> +}
>>> +
>>> +static const struct c3_isp_block_handler c3_isp_block_handlers[] = {
>>> +     [C3_ISP_PARAM_BLOCK_WB_CHANGE] = {
>>> +             .size = sizeof(struct wb_change_cfg),
>>> +             .handler = c3_isp_params_cfg_wb_change,
>>> +     },
>>> +     [C3_ISP_PARAM_BLOCK_WB_LUMA] = {
>>> +             .size = sizeof(struct wb_luma_cfg),
>>> +             .handler = c3_isp_params_cfg_wb_luma,
>>> +     },
>>> +     [C3_ISP_PARAM_BLOCK_WB_TRIANGLE] = {
>>> +             .size = sizeof(struct wb_triangle_cfg),
>>> +             .handler = c3_isp_params_cfg_wb_triangle,
>>> +     },
>>> +     [C3_ISP_PARAM_BLOCK_AWB_STATS] = {
>>> +             .size = sizeof(struct awb_stats_cfg),
>>> +             .handler = c3_isp_params_cfg_awb_stats,
>>> +     },
>>> +     [C3_ISP_PARAM_BLOCK_AE_STATS] = {
>>> +             .size = sizeof(struct ae_stats_cfg),
>>> +             .handler = c3_isp_params_cfg_ae_stats,
>>> +     },
>>> +     [C3_ISP_PARAM_BLOCK_AF_STATS] = {
>>> +             .size = sizeof(struct af_stats_cfg),
>>> +             .handler = c3_isp_params_cfg_af_stats,
>>> +     },
>>> +     [C3_ISP_PARAM_BLOCK_PST_GAMMA] = {
>>> +             .size = sizeof(struct pst_gamma_cfg),
>>> +             .handler = c3_isp_params_cfg_pst_gamma,
>>> +     },
>>> +     [C3_ISP_PARAM_BLOCK_DMSC] = {
>>> +             .size = sizeof(struct dmsc_cfg),
>>> +             .handler = c3_isp_params_cfg_dmsc,
>>> +     },
>>> +     [C3_ISP_PARAM_BLOCK_CCM] = {
>>> +             .size = sizeof(struct ccm_cfg),
>>> +             .handler = c3_isp_params_cfg_ccm,
>>> +     },
>>> +     [C3_ISP_PARAM_BLOCK_CSC] = {
>>> +             .size = sizeof(struct csc_cfg),
>>> +             .handler = c3_isp_params_cfg_csc,
>>> +     },
>>> +     [C3_ISP_PARAM_BLOCK_BLC] = {
>>> +             .size = sizeof(struct blc_cfg),
>>> +             .handler = c3_isp_params_cfg_blc,
>>> +     },
>>> +};
>>> +
>>> +static enum vb2_buffer_state
>>> +c3_isp_params_cfg_blocks(struct c3_isp_params *params)
>>> +{
>>> +     struct c3_isp_params_buffer *config = params->buff->vaddr;
>>> +     enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
>>> +     size_t block_offset = 0;
>>> +     size_t max_offset = 0;
>>> +
>>> +     if (config->total_size > C3_ISP_PARAMS_MAX_SIZE) {
>>> +             dev_dbg(params->isp->dev, "Invalid parameters buffer size %lu\n",
>>> +                     config->total_size);
>>> +             state = VB2_BUF_STATE_ERROR;
>>> +             goto err_return_state;
>>> +     }
>>> +
>>> +     /* Ensure config->data has a full struct c3_isp_param_block_header */
>>> +     max_offset = config->total_size - sizeof(struct c3_isp_param_block_header);
>>> +
>>> +     while (block_offset <= max_offset) {
>>> +             const struct c3_isp_block_handler *block_handler;
>>> +             struct c3_isp_param_block_header *block;
>>> +
>>> +             block = (struct c3_isp_param_block_header *)
>>> +                      &config->data[block_offset];
>>> +
>>> +             if (block->type >= C3_ISP_PARAM_BLOCK_SENTINEL) {
>>> +                     dev_dbg(params->isp->dev, "Invalid parameters block type\n");
>>> +                     state = VB2_BUF_STATE_ERROR;
>>> +                     goto err_return_state;
>>> +             }
>>> +
>>> +             block_handler = &c3_isp_block_handlers[block->type];
>>> +             if (block->size != block_handler->size) {
>>> +                     dev_dbg(params->isp->dev, "Invalid parameters block size\n");
>>> +                     state = VB2_BUF_STATE_ERROR;
>>> +                     goto err_return_state;
>>> +             }
>>> +
>>> +             block_handler->handler(params->isp, block);
>>> +
>>> +             block_offset += block->size;
>>> +     }
>>> +
>>> +err_return_state:
>>> +     return state;
>>> +}
>>
>>
>> The validation in this function can be moved to .buf_queue() so it's not done in interrupt context
>>
>
> OK, will move the validation to .buf_queue().
>
>>> +
>>> +/* Initialize ISP pipeline */
>>> +static int c3_isp_params_start(struct c3_isp_params *params)
>>> +{
>>> +     enum vb2_buffer_state state;
>>> +     unsigned long flags;
>>> +
>>> +     /* Reset these controllers */
>>> +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL0, TOP_FEO_CTRL0_ALL_DIS);
>>> +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_0, TOP_FEO_CTRL1_0_ALL_DIS);
>>> +     c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_1, TOP_FEO_CTRL1_1_ALL_DIS);
>>> +     c3_isp_write(params->isp, ISP_TOP_FED_CTRL, TOP_FED_CTRL_ALL_DIS);
>>> +     c3_isp_write(params->isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_ALL_DIS);
>>> +     c3_isp_write(params->isp, ISP_TOP_BED_CTRL, TOP_BED_CTRL_ALL_DIS);
>>> +
>>> +     spin_lock_irqsave(&params->buff_lock, flags);
>>> +
>>> +     /* Only use the first buffer to initialize ISP */
>>> +     params->buff = list_first_entry_or_null(&params->pending,
>>> +                                             struct c3_isp_vb2_buffer, list);
>>> +     if (!params->buff) {
>>> + spin_unlock_irqrestore(&params->buff_lock, flags);
>>> +             return -EINVAL;
>>> +     }
>>
>>
>> You have min_queued_buffers set to a non-zero figure, so this error is theoretically impossible - I
>> would keep the check but add a comment that it ought not to have happened.
>>
>
> Will reconsider the impact of this value.
>
>>> +
>>> +     state = c3_isp_params_cfg_blocks(params);
>>> +
>>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +/* V4L2 video operations */
>>> +
>>> +static void c3_isp_params_return_buffers(struct c3_isp_params *params,
>>> +                                      enum vb2_buffer_state state)
>>> +{
>>> +     unsigned long flags;
>>> +     struct c3_isp_vb2_buffer *buff;
>>> +
>>> +     spin_lock_irqsave(&params->buff_lock, flags);
>>> +
>>> +     while (!list_empty(&params->pending)) {
>>> +             buff = list_first_entry(&params->pending,
>>> +                                     struct c3_isp_vb2_buffer, list);
>>> +             list_del(&buff->list);
>>> +             vb2_buffer_done(&buff->vb.vb2_buf, state);
>>> +     }
>>> +
>>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>>> +}
>>> +
>>> +static int c3_isp_params_querycap(struct file *file, void *fh,
>>> +                               struct v4l2_capability *cap)
>>> +{
>>> +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>>> +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_params_enum_fmt(struct file *file, void *fh,
>>> +                               struct v4l2_fmtdesc *f)
>>> +{
>>> +     if (f->index)
>>> +             return -EINVAL;
>>> +
>>> +     f->pixelformat = V4L2_META_FMT_C3ISP_PARAMS;
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_params_g_fmt(struct file *file, void *fh,
>>> +                            struct v4l2_format *f)
>>> +{
>>> +     struct c3_isp_params *params = video_drvdata(file);
>>> +
>>> +     f->fmt.meta = params->vfmt.fmt.meta;
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static const struct v4l2_ioctl_ops isp_params_v4l2_ioctl_ops = {
>>> +     .vidioc_querycap                = c3_isp_params_querycap,
>>> +     .vidioc_enum_fmt_meta_out       = c3_isp_params_enum_fmt,
>>> +     .vidioc_g_fmt_meta_out          = c3_isp_params_g_fmt,
>>> +     .vidioc_s_fmt_meta_out          = c3_isp_params_g_fmt,
>>> +     .vidioc_try_fmt_meta_out        = c3_isp_params_g_fmt,
>>> +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>>> +     .vidioc_querybuf                = vb2_ioctl_querybuf,
>>> +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
>>> +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
>>> +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>>> +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>>> +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>>> +     .vidioc_streamon                = vb2_ioctl_streamon,
>>> +     .vidioc_streamoff               = vb2_ioctl_streamoff,
>>> +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>>> +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>>> +};
>>> +
>>> +static const struct v4l2_file_operations isp_params_v4l2_fops = {
>>> +     .open = v4l2_fh_open,
>>> +     .release = vb2_fop_release,
>>> +     .poll = vb2_fop_poll,
>>> +     .unlocked_ioctl = video_ioctl2,
>>> +     .mmap = vb2_fop_mmap,
>>> +};
>>> +
>>> +static int c3_isp_params_vb2_queue_setup(struct vb2_queue *q,
>>> +                                      unsigned int *num_buffers,
>>> +                                      unsigned int *num_planes,
>>> +                                      unsigned int sizes[],
>>> +                                      struct device *alloc_devs[])
>>> +{
>>> +     if (*num_planes) {
>>> +             if (*num_planes != 1)
>>> +                     return -EINVAL;
>>> +
>>> +             if (sizes[0] < sizeof(struct c3_isp_params_buffer))
>>> +                     return -EINVAL;
>>> +     } else {
>>> +             *num_planes = 1;
>>> +             sizes[0] = sizeof(struct c3_isp_params_buffer);
>>> +     }
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static void c3_isp_params_vb2_buf_queue(struct vb2_buffer *vb)
>>> +{
>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>> +     struct c3_isp_vb2_buffer *buf =
>>> +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>> +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>>> +     unsigned long flags;
>>> +
>>> +     spin_lock_irqsave(&params->buff_lock, flags);
>>> +
>>> +     list_add_tail(&buf->list, &params->pending);
>>> +
>>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>>> +}
>>
>>
>> So as mentioned above, you can validate the contents of the buffer in this function rather than
>> during the interrupt. We've also started using memcpy() here to store the buffer in a scratch buffer
>> internal to the driver; this is designed to protect against userspace making changes to the buffer
>> whilst it's supposed the be being processed by the driver. Check the equivalent function in the
>> mali-c55 series:
>>
>>
>> https://lore.kernel.org/linux-media/20241106100534.768400-17-dan.scally@ideasonboard.com/T/#u
>>
>
> OK, will refer to your patches.
>
>>> +
>>> +static int c3_isp_params_vb2_buf_prepare(struct vb2_buffer *vb)
>>> +{
>>> +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>>> +     unsigned int size = params->vfmt.fmt.meta.buffersize;
>>> +
>>> +     if (vb2_plane_size(vb, 0) < size) {
>>
>>
>> isn't vfmt.fmt.meta.buffersize filled by userspace? If so I think this would mean you'd have to pass
>> all of the blocks - we don't want that, the design of the extensible params is to allow only part of
>> the config to be passed if that's all that's needed.
>>
>
> Will check and modify this operation.
>
>>> + dev_err(params->isp->dev,
>>> +                     "User buffer too small (%ld < %u)\n",
>>> +                     vb2_plane_size(vb, 0), size);
>>> +             return -EINVAL;
>>> +     }
>>> +
>>> +     vb2_set_plane_payload(vb, 0, size);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_params_vb2_buf_init(struct vb2_buffer *vb)
>>> +{
>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>> +     struct c3_isp_vb2_buffer *buf =
>>> +             container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>> +     struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue);
>>> +
>>> +     buf->vaddr = vb2_plane_vaddr(vb, 0);
>>> +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>>> +
>>> +     memset(buf->vaddr, 0, params->vfmt.fmt.meta.buffersize);
>>> +
>> I'm not sure the memset is needed - although with the extensible format not all of the buffer need
>> be overwritten, everything up to buffersize would be.
>
>
> Will remove this memset().
>
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_params_vb2_start_streaming(struct vb2_queue *q,
>>> +                                          unsigned int count)
>>> +{
>>> +     struct c3_isp_params *params = vb2_get_drv_priv(q);
>>> +     int ret;
>>> +
>>> +     guard(mutex)(&params->isp->lock);
>>> +
>>> +     ret = pm_runtime_resume_and_get(params->isp->dev);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     ret = video_device_pipeline_start(&params->vdev, &params->isp->pipe);
>>> +     if (ret) {
>>> +             dev_err(params->isp->dev,
>>> +                     "Failed to start params pipeline: %d\n", ret);
>>> +             goto err_pm_put;
>>> +     }
>>> +
>>> +     if (c3_isp_pipeline_ready(params->isp)) {
>>> +             ret = v4l2_subdev_enable_streams(&params->isp->core.sd,
>>> + C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>> +                                              BIT(0));
>>> +             if (ret)
>>> +                     goto err_pipeline_stop;
>>> +     }
>>> +
>>> +     c3_isp_params_start(params);
>>> +
>>> +     return 0;
>>> +
>>> +err_pipeline_stop:
>>> +     video_device_pipeline_stop(&params->vdev);
>>> +err_pm_put:
>>> +     pm_runtime_put(params->isp->dev);
>>> +     c3_isp_params_return_buffers(params, VB2_BUF_STATE_QUEUED);
>>> +     return ret;
>>> +}
>>> +
>>> +static void c3_isp_params_vb2_stop_streaming(struct vb2_queue *q)
>>> +{
>>> +     struct c3_isp_params *params = vb2_get_drv_priv(q);
>>> +
>>> +     guard(mutex)(&params->isp->lock);
>>> +
>>> +     c3_isp_params_return_buffers(params, VB2_BUF_STATE_ERROR);
>>> +
>>> +     if (params->isp->pipe.start_count == 1)
>>> + v4l2_subdev_disable_streams(&params->isp->core.sd,
>>> + C3_ISP_CORE_PAD_SOURCE_VIDEO,
>>> +                                         BIT(0));
>>> +
>>> +     video_device_pipeline_stop(&params->vdev);
>>> +     pm_runtime_put(params->isp->dev);
>>> +}
>>> +
>>> +static const struct vb2_ops isp_params_vb2_ops = {
>>> +     .queue_setup = c3_isp_params_vb2_queue_setup,
>>> +     .buf_queue = c3_isp_params_vb2_buf_queue,
>>> +     .buf_prepare = c3_isp_params_vb2_buf_prepare,
>>> +     .buf_init = c3_isp_params_vb2_buf_init,
>>> +     .wait_prepare = vb2_ops_wait_prepare,
>>> +     .wait_finish = vb2_ops_wait_finish,
>>> +     .start_streaming = c3_isp_params_vb2_start_streaming,
>>> +     .stop_streaming = c3_isp_params_vb2_stop_streaming,
>>> +};
>>> +
>>> +int c3_isp_params_register(struct c3_isp_device *isp)
>>> +{
>>> +     struct c3_isp_params *params = &isp->params;
>>> +     struct video_device *vdev = &params->vdev;
>>> +     struct vb2_queue *vb2_q = &params->vb2_q;
>>> +     int ret;
>>> +
>>> +     memset(params, 0, sizeof(*params));
>>> +     params->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_PARAMS;
>>> +     params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_buffer);
>>> +     params->isp = isp;
>>> +     INIT_LIST_HEAD(&params->pending);
>>> +     spin_lock_init(&params->buff_lock);
>>> +     mutex_init(&params->lock);
>>> +
>>> +     snprintf(vdev->name, sizeof(vdev->name), "isp-params");
>>> +     vdev->fops = &isp_params_v4l2_fops;
>>> +     vdev->ioctl_ops = &isp_params_v4l2_ioctl_ops;
>>> +     vdev->v4l2_dev = &isp->v4l2_dev;
>>> +     vdev->lock = &params->lock;
>>> +     vdev->minor = -1;
>>> +     vdev->queue = vb2_q;
>>> +     vdev->release = video_device_release_empty;
>>> +     vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING;
>>> +     vdev->vfl_dir = VFL_DIR_TX;
>>> +     video_set_drvdata(vdev, params);
>>> +
>>> +     vb2_q->drv_priv = params;
>>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>>> +     vb2_q->ops = &isp_params_vb2_ops;
>>> +     vb2_q->type = V4L2_BUF_TYPE_META_OUTPUT;
>>> +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>>> +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>>> +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>>> +     vb2_q->dev = isp->dev;
>>> +     vb2_q->lock = &params->lock;
>>> +     vb2_q->min_queued_buffers = 1;
>>> +
>>> +     ret = vb2_queue_init(vb2_q);
>>> +     if (ret)
>>> +             goto err_detroy;
>>> +
>>> +     params->pad.flags = MEDIA_PAD_FL_SOURCE;
>>> +     ret = media_entity_pads_init(&vdev->entity, 1, &params->pad);
>>> +     if (ret)
>>> +             goto err_queue_release;
>>> +
>>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>>> +     if (ret < 0) {
>>> +             dev_err(isp->dev,
>>> +                     "Failed to register %s: %d\n", vdev->name, ret);
>>> +             goto err_entity_cleanup;
>>> +     }
>>> +
>>> +     return 0;
>>> +
>>> +err_entity_cleanup:
>>> +     media_entity_cleanup(&vdev->entity);
>>> +err_queue_release:
>>> +     vb2_queue_release(vb2_q);
>>> +err_detroy:
>>> +     mutex_destroy(&params->lock);
>>> +     return ret;
>>> +}
>>> +
>>> +void c3_isp_params_unregister(struct c3_isp_device *isp)
>>> +{
>>> +     struct c3_isp_params *params = &isp->params;
>>> +
>>> +     vb2_queue_release(&params->vb2_q);
>>> +     media_entity_cleanup(&params->vdev.entity);
>>> +     video_unregister_device(&params->vdev);
>>> +     mutex_destroy(&params->lock);
>>> +}
>>> +
>>> +int c3_isp_params_done(struct c3_isp_device *isp)
>>> +{
>>> +     struct c3_isp_params *params = &isp->params;
>>> +     enum vb2_buffer_state state;
>>> +     unsigned long flags;
>>> +
>>> +     spin_lock_irqsave(&params->buff_lock, flags);
>> Usual comment about guard()
>
>
> Will use guard().
>
>>> +
>>> +     params->buff = list_first_entry_or_null(&params->pending,
>>> +                                             struct c3_isp_vb2_buffer, list);
>>> +     if (!params->buff) {
>>> + spin_unlock_irqrestore(&params->buff_lock, flags);
>>> +             return -EINVAL;
>>> +     }
>>> +
>>> +     list_del(&params->buff->list);
>>> +
>>> +     state = c3_isp_params_cfg_blocks(params);
>>> +
>>> +     params->buff->vb.sequence = params->isp->frm_sequence;
>>> +     params->buff->vb.vb2_buf.timestamp = ktime_get();
>>> +     params->buff->vb.field = V4L2_FIELD_NONE;
>>> +     vb2_buffer_done(&params->buff->vb.vb2_buf, state);
>>> +
>>> +     spin_unlock_irqrestore(&params->buff_lock, flags);
>>> +
>>> +     return 0;
>>> +}
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h 
>>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
>>> new file mode 100644
>>> index 000000000000..de1938f7c354
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h
>>> @@ -0,0 +1,683 @@
>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#ifndef __C3_ISP_REGS_H__
>>> +#define __C3_ISP_REGS_H__
>>> +
>>> +#define ISP_TOP_INPUT_SIZE                       0x0000
>>> +#define TOP_INPUT_HSIZE(x)                       (((x) & 0xffff) << 16)
>>> +#define TOP_INPUT_VSIZE(x)                       ((x) & 0xffff)
>>> +
>>> +#define ISP_TOP_FRM_SIZE                         0x0004
>>> +#define TOP_FRM_CORE_IHSIZE(x)                   (((x) & 0xffff) << 16)
>>> +#define TOP_FRM_CORE_IVSIZE(x)                   ((x) & 0xffff)
>>> +
>>> +#define ISP_TOP_HOLD_SIZE                        0x0008
>>> +#define TOP_HOLD_HSIZE_MASK                      GENMASK(31, 16)
>>> +#define TOP_HOLD_HSIZE_SHIFT                     16
>>> +
>>> +#define ISP_TOP_PATH_EN                          0x0010
>>> +#define TOP_DISP_EN(x)                           BIT((x))
>>> +#define TOP_WRMIF_EN(x)                          BIT((x) + 8)
>>> +
>>> +#define ISP_TOP_PATH_SEL                         0x0014
>>> +#define TOP_DATA_PATH_MASK                       GENMASK(18, 16)
>>> +#define TOP_PATH_MIPI_TO_CORE                    BIT(16)
>>> +
>>> +#define ISP_TOP_IRQ_EN                           0x0080
>>> +#define TOP_IRQ_FRAME_DONE                       BIT(0)
>>> +#define TOP_IRQ_STATS_ERR                        BIT(5)
>>> +
>>> +#define ISP_TOP_IRQ_CLR                          0x0084
>>> +#define ISP_TOP_IRQ_LINE_THRD                    0x0088
>>> +#define TOP_IRQ_DIN_HSYNC                        BIT(16)
>>> +
>>> +#define ISP_TOP_RO_IRQ_STAT                      0x01c4
>>> +#define ISP_TOP_MODE_CTRL                        0x0400
>>> +#define ISP_TOP_FEO_CTRL0                        0x040c
>>> +#define TOP_FEO_CTRL0_ALL_DIS                    0
>>> +#define TOP_FEO_INPUT_FMT_EN                     BIT(8)
>>> +
>>> +#define ISP_TOP_FEO_CTRL1_0                      0x0410
>>> +#define TOP_FEO_CTRL1_0_ALL_DIS                  0
>>> +
>>> +#define ISP_TOP_FEO_CTRL1_1                      0x0414
>>> +#define TOP_FEO_CTRL1_1_ALL_DIS                  0
>>> +
>>> +#define ISP_TOP_FED_CTRL                         0x0418
>>> +#define TOP_FED_CTRL_ALL_DIS                     0
>>> +
>>> +#define ISP_TOP_BEO_CTRL                         0x041c
>>> +#define TOP_BEO_CTRL_ALL_DIS                     0
>>> +#define TOP_BEO_CTRL_GTM_EN                      BIT(2)
>>> +#define TOP_BEO_CTRL_WB_EN                       BIT(6)
>>> +#define TOP_BEO_CTRL_BLC_EN                      BIT(7)
>>> +#define TOP_BEO_CTRL_IDGAIN_EN                   BIT(8)
>>> +#define TOP_BEO_CTRL_EOTF_EN                     BIT(9)
>>> +
>>> +#define ISP_TOP_BED_CTRL                         0x0420
>>> +#define TOP_BED_CTRL_ALL_DIS                     0
>>> +#define TOP_BED_CM0_EN                           BIT(14)
>>> +#define TOP_BED_GAMMA_EN                         BIT(16)
>>> +#define TOP_BED_CCM_EN                           BIT(18)
>>> +#define TOP_BED_DMSC_EN                          BIT(19)
>>> +
>>> +#define ISP_TOP_3A_STAT_CRTL                     0x0424
>>> +#define TOP_3A_AE_STAT_EN                        BIT(0)
>>> +#define TOP_3A_AWB_STAT_EN                       BIT(1)
>>> +#define TOP_3A_AF_STAT_EN                        BIT(2)
>>> +#define TOP_3A_AWB_STAT_SWITCH_MASK              GENMASK(6, 4)
>>> +#define TOP_3A_AWB_STAT_SWITCH_SHIFT             4
>>> +#define TOP_3A_AWB_STAT_SWITCH_BEFORE_WB         2
>>> +
>>> +#define TOP_3A_AE_STAT_SWITCH_MASK               GENMASK(9, 8)
>>> +#define TOP_3A_AE_STAT_SWITCH_SHIFT              8
>>> +#define TOP_3A_AE_STAT_SWITCH_FROM_MLS           1
>>> +#define TOP_3A_AF_STAT_SWITCH_MASK               GENMASK(13, 12)
>>> +#define TOP_3A_AF_STAT_SWITCH_SHIFT              12
>>> +#define TOP_3A_AF_STAT_SWITCH_FROM_SNR            0
>>> +
>>> +#define ISP_FED_BL_OFST_GR                       0x2018
>>> +#define FED_BL_GR_OFST(x)                        ((x) & 0x1fffff)
>>> +
>>> +#define ISP_FED_BL_OFST_R                        0x201c
>>> +#define FED_BL_R_OFST(x)                         ((x) & 0x1fffff)
>>> +
>>> +#define ISP_FED_BL_OFST_B                        0x2020
>>> +#define FED_BL_B_OFST(x)                         ((x) & 0x1fffff)
>>> +
>>> +#define ISP_FED_BL_OFST_GB                       0x2024
>>> +#define FED_BL_GB_OFST(x)                        ((x) & 0x1fffff)
>>> +
>>> +#define ISP_FED_BL_OFST_IR                       0x2028
>>> +#define FED_BL_IR_OFST(x)                        ((x) & 0x1fffff)
>>> +
>>> +#define ISP_LSWB_BLC_OFST0                       0x4028
>>> +#define LSWB_BLC_OFST1_MASK                      GENMASK(15, 0)
>>> +#define LSWB_BLC_OFST1(x)                        ((x) & 0xffff)
>>> +#define LSWB_BLC_OFST1_EXT(x)                    ((x) & 0xffff)
>>> +#define LSWB_BLC_OFST0_MASK                      GENMASK(31, 16)
>>> +#define LSWB_BLC_OFST0(x)                        (((x) & 0xffff) << 16)
>>> +#define LSWB_BLC_OFST0_EXT(x)                    (((x) >> 16) & 0xffff)
>>> +
>>> +#define ISP_LSWB_BLC_OFST1                       0x402c
>>> +#define LSWB_BLC_OFST3_MASK                      GENMASK(15, 0)
>>> +#define LSWB_BLC_OFST3(x)                        ((x) & 0xffff)
>>> +#define LSWB_BLC_OFST3_EXT(x)                    ((x) & 0xffff)
>>> +#define LSWB_BLC_OFST2_MASK                      GENMASK(31, 16)
>>> +#define LSWB_BLC_OFST2(x)                        (((x) & 0xffff) << 16)
>>> +#define LSWB_BLC_OFST2_EXT(x)                    (((x) >> 16) & 0xffff)
>>> +
>>> +#define ISP_LSWB_BLC_OFST2                       0x4030
>>> +#define LSWB_BLC_OFST4_MASK                      GENMASK(15, 0)
>>> +#define LSWB_BLC_OFST4(x)                        ((x) & 0xffff)
>>> +#define LSWB_BLC_OFST4_EXT(x)                    ((x) & 0xffff)
>>> +
>>> +#define ISP_LSWB_BLC_PHSOFST                     0x4034
>>> +#define LSWB_BLC_YPHS_OFST_MASK                  GENMASK(1, 0)
>>> +#define LSWB_BLC_XPHS_OFST_MASK                  GENMASK(3, 2)
>>> +#define LSWB_BLC_XPHS_OFST_SHIFT                 2
>>> +
>>> +#define ISP_LSWB_WB_GAIN0                        0x4038
>>> +#define LSWB_WB_GAIN1_MASK                       GENMASK(11, 0)
>>> +#define LSWB_WB_GAIN0_MASK                       GENMASK(27, 16)
>>> +#define LSWB_WB_GAIN0_SHIFT                      16
>>> +
>>> +#define ISP_LSWB_WB_GAIN1                        0x403c
>>> +#define LSWB_WB_GAIN3_MASK                       GENMASK(11, 0)
>>> +#define LSWB_WB_GAIN2_MASK                       GENMASK(27, 16)
>>> +#define LSWB_WB_GAIN2_SHIFT                      16
>>> +
>>> +#define ISP_LSWB_WB_GAIN2                        0x4040
>>> +#define LSWB_WB_GAIN4_MASK                       GENMASK(11, 0)
>>> +
>>> +#define ISP_LSWB_WB_LIMIT0                       0x4044
>>> +#define LSWB_WB_LIMIT1(x)                        ((x) & 0xffff)
>>> +#define LSWB_WB_LIMIT0(x)                        (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_LSWB_WB_LIMIT1                       0x4048
>>> +#define LSWB_WB_LIMIT3(x)                        ((x) & 0xffff)
>>> +#define LSWB_WB_LIMIT2(x)                        (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_LSWB_WB_LIMIT2                       0x404c
>>> +#define LSWB_WB_LIMIT4_MASK                 ��    GENMASK(15, 0)
>>> +
>>> +#define ISP_LSWB_WB_PHSOFST                      0x4050
>>> +#define LSWB_WB_YPHS_OFST_MASK                   GENMASK(1, 0)
>>> +#define LSWB_WB_XPHS_OFST_MASK                   GENMASK(3, 2)
>>> +#define LSWB_WB_XPHS_OFST_SHIFT                  2
>>> +
>>> +#define ISP_LSWB_LNS_PHSOFST                     0x4054
>>> +#define LSWB_LNS_YPHS_OFST_MASK                  GENMASK(1, 0)
>>> +#define LSWB_LNS_XPHS_OFST_MASK                  GENMASK(3, 2)
>>> +#define LSWB_LNS_XPHS_OFST_SHIFT                 2
>>> +
>>> +#define ISP_DMS_COMMON_PARAM0                    0x5000
>>> +#define DMS_COMMON_YPHS_OFST_MASK                GENMASK(1, 0)
>>> +#define DMS_COMMON_XPHS_OFST_MASK                GENMASK(3, 2)
>>> +#define DMS_COMMON_XPHS_OFST_SHIFT               2
>>> +
>>> +#define ISP_CM0_INP_OFST01                       0x6040
>>> +#define CM0_INP_OFST0_MASK                       GENMASK(12, 0)
>>> +#define CM0_INP_OFST1_MASK                       GENMASK(28, 16)
>>> +#define CM0_INP_OFST1_SHIFT                      16
>>> +
>>> +#define ISP_CM0_INP_OFST2                        0x6044
>>> +#define CM0_INP_OFST2_MASK                       GENMASK(12, 0)
>>> +
>>> +#define ISP_CM0_COEF00_01                        0x6048
>>> +#define CM0_MTX_00_MASK                          GENMASK(12, 0)
>>> +#define CM0_MTX_01_MASK                          GENMASK(28, 16)
>>> +#define CM0_MTX_01_SHIFT                         16
>>> +
>>> +#define ISP_CM0_COEF02_10                        0x604c
>>> +#define CM0_MTX_02_MASK                          GENMASK(12, 0)
>>> +#define CM0_MTX_10_MASK                          GENMASK(28, 16)
>>> +#define CM0_MTX_10_SHIFT                         16
>>> +
>>> +#define ISP_CM0_COEF11_12                        0x6050
>>> +#define CM0_MTX_11_MASK                          GENMASK(12, 0)
>>> +#define CM0_MTX_12_MASK                          GENMASK(28, 16)
>>> +#define CM0_MTX_12_SHIFT                         16
>>> +
>>> +#define ISP_CM0_COEF20_21                        0x6054
>>> +#define CM0_MTX_20_MASK                          GENMASK(12, 0)
>>> +#define CM0_MTX_21_MASK                          GENMASK(28, 16)
>>> +#define CM0_MTX_21_SHIFT                         16
>>> +
>>> +#define ISP_CM0_COEF22_OUP_OFST0                 0x6058
>>> +#define CM0_MTX_22_MASK                          GENMASK(12, 0)
>>> +#define CM0_OFST_OUP0_MASK                       GENMASK(28, 16)
>>> +#define CM0_OFST_OUP0_SHIFT                      16
>>> +
>>> +#define ISP_CM0_OUP_OFST12_RS                    0x605c
>>> +#define CM0_OFST_OUP1_MASK                       GENMASK(12, 0)
>>> +#define CM0_OFST_OUP2_MASK                       GENMASK(28, 16)
>>> +#define CM0_OFST_OUP2_SHIFT                      16
>>> +#define CM0_MTX_RS_MASK                          GENMASK(31, 30)
>>> +#define CM0_MTX_RS_SHIFT                         30
>>> +
>>> +#define ISP_CCM_MTX_00_01                        0x6098
>>> +#define CCM_MTX_00_MASK                          GENMASK(12, 0)
>>> +#define CCM_MTX_01_MASK                          GENMASK(28, 16)
>>> +#define CCM_MTX_01_SHIFT                         16
>>> +
>>> +#define ISP_CCM_MTX_02_03                        0x609c
>>> +#define CCM_MTX_02_MASK                          GENMASK(12, 0)
>>> +#define CCM_MTX_03_MASK                          GENMASK(28, 16)
>>> +#define CCM_MTX_03_SHIFT                         16
>>> +
>>> +#define ISP_CCM_MTX_10_11                        0x60A0
>>> +#define CCM_MTX_10_MASK                          GENMASK(12, 0)
>>> +#define CCM_MTX_11_MASK                          GENMASK(28, 16)
>>> +#define CCM_MTX_11_SHIFT                         16
>>> +
>>> +#define ISP_CCM_MTX_12_13                        0x60A4
>>> +#define CCM_MTX_12_MASK                          GENMASK(12, 0)
>>> +#define CCM_MTX_13_MASK                          GENMASK(28, 16)
>>> +#define CCM_MTX_13_SHIFT                         16
>>> +
>>> +#define ISP_CCM_MTX_20_21                        0x60A8
>>> +#define CCM_MTX_20_MASK                          GENMASK(12, 0)
>>> +#define CCM_MTX_21_MASK                          GENMASK(28, 16)
>>> +#define CCM_MTX_21_SHIFT                         16
>>> +
>>> +#define ISP_CCM_MTX_22_23_RS                     0x60Ac
>>> +#define CCM_MTX_22_MASK                          GENMASK(12, 0)
>>> +#define CCM_MTX_23_MASK                          GENMASK(28, 16)
>>> +#define CCM_MTX_23_SHIFT                         16
>>> +
>>> +#define ISP_PST_GAMMA_MODE                       0x60C0
>>> +#define PST_GAMMA_MODE                           BIT(0)
>>> +#define PST_GAMMA_MODE_EXT(x)                    ((x) & 0x1)
>>> +
>>> +#define ISP_PST_GAMMA_LUT_ADDR                   0x60cc
>>> +#define PST_GAMMA_LUT_ADDR(x)                    ((x) << 7)
>>> +
>>> +#define ISP_PST_GAMMA_LUT_DATA                   0x60d0
>>> +#define PST_GAMMA_LUT_DATA0(x)                   ((x) & 0xffff)
>>> +#define PST_GAMMA_LUT_DATA1(x)                   (((x) & 0xffff) << 16)
>>> +
>>> +#define DISP0_TOP_TOP_CTRL                       0x8000
>>> +#define DISP_CRP2_EN                             BIT(5)
>>> +
>>> +#define DISP0_TOP_CRP2_START                     0x8004
>>> +#define DISP_CRP2_VSTART_MASK                    GENMASK(15, 0)
>>> +#define DISP_CRP2_VSTART(x)                      ((x) & 0xffff)
>>> +#define DISP_CRP2_HSTART_MASK                    GENMASK(31, 16)
>>> +#define DISP_CRP2_HSTART(x)                      (((x) & 0xffff) << 16)
>>> +
>>> +#define DISP0_TOP_CRP2_SIZE                      0x8008
>>> +#define DISP_CRP2_VSIZE_MASK                     GENMASK(15, 0)
>>> +#define DISP_CRP2_VSIZE(x)                       ((x) & 0xffff)
>>> +#define DISP_CRP2_HSIZE_MASK                     GENMASK(31, 16)
>>> +#define DISP_CRP2_HSIZE(x)                       (((x) & 0xffff) << 16)
>>> +
>>> +#define DISP0_TOP_OUT_SIZE                       0x800c
>>> +#define DISP_OUT_VSIZE_MASK                      GENMASK(12, 0)
>>> +#define DISP_OUT_HSIZE_MASK                      GENMASK(28, 16)
>>> +#define DISP_OUT_HSIZE_SHIFT                     16
>>> +
>>> +#define ISP_DISP0_TOP_IN_SIZE                    0x804c
>>> +#define DISP_TOP_IN_VSIZE(x)                     ((x) & 0x1fff)
>>> +#define DISP_TOP_IN_HSIZE(x)                     (((x) & 0x1fff) << 16)
>>> +
>>> +#define DISP0_PPS_SCALE_EN                       0x8200
>>> +#define PPS_VSC_TAP_NUM_MASK                     GENMASK(3, 0)
>>> +#define PPS_HSC_TAP_NUM_MASK                     GENMASK(7, 4)
>>> +#define PPS_HSC_TAP_NUM_SHIFT                    4
>>> +#define PPS_HSC_TAP_NUM_INIT                     4
>>> +#define PPS_PREVSC_FLT_NUM_MASK                  GENMASK(11, 8)
>>> +#define PPS_PREVSC_FLT_NUM_SHIFT                 8
>>> +#define PPS_PREHSC_FLT_NUM_MASK                  GENMASK(15, 12)
>>> +#define PPS_PREHSC_FLT_NUM_SHIFT                 12
>>> +#define PPS_PREHSC_FLT_NUM_INIT                  8
>>> +#define PPS_PREVSC_RATE_MASK                     GENMASK(17, 16)
>>> +#define PPS_PREVSC_RATE_SHIFT                    16
>>> +#define PPS_PREHSC_RATE_MASK                     GENMASK(19, 18)
>>> +#define PPS_PREHSC_RATE_SHIFT                    18
>>> +#define PPS_HSC_EN_MASK                          BIT(20)
>>> +#define PPS_HSC_EN_SHIFT                         20
>>> +#define PPS_VSC_EN_MASK                          BIT(21)
>>> +#define PPS_VSC_EN_SHIFT                         21
>>> +#define PPS_PREVSC_EN_MASK                       BIT(22)
>>> +#define PPS_PREVSC_EN_SHIFT                      22
>>> +#define PPS_PREHSC_EN_MASK                       BIT(23)
>>> +#define PPS_PREHSC_EN_SHIFT                      23
>>> +#define PPS_HSC_NOR_RS_BITS_MASK                 GENMASK(27, 24)
>>> +#define PPS_HSC_NOR_RS_BITS_SHIFT                24
>>> +#define PPS_HSC_NOR_RS_BITS_INIT                 9
>>> +#define PPS_VSC_NOR_RS_BITS_MASK                 GENMASK(31, 28)
>>> +#define PPS_VSC_NOR_RS_BITS_SHIFT                28
>>> +#define PPS_VSC_NOR_RS_BITS_INIT                 9
>>> +
>>> +#define DISP0_PPS_PRE_HSCALE_COEF_0              0x8204
>>> +#define PPS_PREHSC_LUMA_COEF0_MASK               GENMASK(9, 0)
>>> +#define PPS_PREHSC_LUMA_COEF0_INIT               128
>>> +#define PPS_PREHSC_LUMA_COEF1_MASK               GENMASK(25, 16)
>>> +#define PPS_PREHSC_LUMA_COEF1_SHIFT              16
>>> +#define PPS_PREHSC_LUMA_COEF1_INIT               128
>>> +
>>> +#define DISP0_PPS_PRE_HSCALE_COEF_1              0x8208
>>> +#define PPS_PREHSC_LUMA_COEF2_MASK               GENMASK(9, 0)
>>> +#define PPS_PREHSC_LUMA_COEF2_INIT               32
>>> +#define PPS_PREHSC_LUMA_COEF3_MASK               GENMASK(25, 16)
>>> +#define PPS_PREHSC_LUMA_COEF3_SHIFT              16
>>> +#define PPS_PREHSC_LUMA_COEF3_INIT               32
>>> +
>>> +#define DISP0_PPS_VSC_START_PHASE_STEP           0x8224
>>> +#define PPS_VSC_FRACTION_PART_MASK               GENMASK(23, 0)
>>> +#define PPS_VSC_INTEGER_PART_MASK                GENMASK(27, 24)
>>> +#define PPS_VSC_INTEGER_PART_SHIFT               24
>>> +
>>> +#define DISP0_PPS_HSC_START_PHASE_STEP           0x8230
>>> +#define PPS_HSC_FRACTION_PART_MASK               GENMASK(23, 0)
>>> +#define PPS_HSC_INTEGER_PART_MASK                GENMASK(27, 24)
>>> +#define PPS_HSC_INTEGER_PART_SHIFT               24
>>> +
>>> +#define DISP0_PPS_444TO422                       0x823c
>>> +#define PPS_444TO422_EN_MASK                     BIT(0)
>>> +
>>> +#define ISP_SCALE0_COEF_IDX_LUMA                 0x8240
>>> +#define SCALE_LUMA_COEF_S11_MODE                 BIT(9)
>>> +#define SCALE_LUMA_CTYPE(x)                      (((x) & 0x7) << 10)
>>> +
>>> +#define ISP_SCALE0_COEF_LUMA                     0x8244
>>> +#define SCALE_COEF_LUMA_DATA1(x)                 ((x) & 0x7ff)
>>> +#define SCALE_COEF_LUMA_DATA0(x)                 (((x) & 0x7ff) << 16)
>>> +
>>> +#define ISP_SCALE0_COEF_IDX_CHRO                 0x8248
>>> +#define SCALE_CHRO_COEF_S11_MODE                 BIT(9)
>>> +#define SCALE_CHRO_CTYPE(x)                      (((x) & 0x7) << 10)
>>> +
>>> +#define ISP_SCALE0_COEF_CHRO                     0x824c
>>> +#define SCALE_COEF_CHRO_DATA1(x)                 ((x) & 0x7ff)
>>> +#define SCALE_COEF_CHRO_DATA0(x)                 (((x) & 0x7ff) << 16)
>>> +
>>> +#define ISP_AF_ROI0_WIN01                        0xa00c
>>> +#define AF_ROI_XYWIN_00_INIT                     (50 & 0xffff)
>>> +#define AF_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
>>> +
>>> +#define ISP_AF_ROI1_WIN01                        0xa010
>>> +#define AF_ROI_XYWIN_10_INIT                     (100 & 0xffff)
>>> +#define AF_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
>>> +
>>> +#define ISP_AF_ROI0_WIN23                        0xa014
>>> +#define AF_ROI_XYWIN_02_INIT                     (80 & 0xffff)
>>> +#define AF_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
>>> +
>>> +#define ISP_AF_ROI1_WIN23                        0xa018
>>> +#define AF_ROI_XYWIN_12_INIT                     (80 & 0xffff)
>>> +#define AF_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
>>> +
>>> +#define ISP_AF_CTRL                              0xa044
>>> +#define AF_CTRL_YPHS_OFST_MASK                   GENMASK(15, 14)
>>> +#define AF_CTRL_YPHS_OFST_SHIFT                  14
>>> +#define AF_CTRL_XPHS_OFST_MASK                   GENMASK(17, 16)
>>> +#define AF_CTRL_XPHS_OFST_SHIFT                  16
>>> +
>>> +#define ISP_AF_HV_SIZE                           0xa04c
>>> +#define AF_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
>>> +#define AF_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AF_HV_BLKNUM                         0xa050
>>> +#define AF_HV_STAT_VBLK_NUM_MASK                 GENMASK(5, 0)
>>> +#define AF_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x3f)
>>> +#define AF_HV_STAT_HBLK_NUM_MASK                 GENMASK(21, 16)
>>> +#define AF_HV_STAT_HBLK_NUM_SHIFT                16
>>> +
>>> +#define ISP_AF_EN_CTRL                           0xa054
>>> +#define AF_STAT_SELECT                           BIT(21)
>>> +#define AF_STAT_SELECT_SHIFT                     21
>>> +
>>> +#define ISP_RO_AF_GLB_STAT_PCK0                  0xa0e0
>>> +#define ISP_RO_AF_GLB_STAT_PCK1                  0xa0e4
>>> +#define ISP_RO_AF_GLB_STAT_PCK2                  0xa0e8
>>> +#define ISP_RO_AF_GLB_STAT_PCK3                  0xa0eC
>>> +#define ISP_AF_IDX_ADDR                          0xa1c0
>>> +#define ISP_AF_IDX_DATA                          0xa1c4
>>> +#define AF_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
>>> +#define AF_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_ROI0_WIN01                        0xa40c
>>> +#define AE_ROI_XYWIN_00_INIT                     (50 & 0xffff)
>>> +#define AE_ROI_XYWIN_01_INIT                     ((50 & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_ROI1_WIN01                        0xa410
>>> +#define AE_ROI_XYWIN_10_INIT                     (100 & 0xffff)
>>> +#define AE_ROI_XYWIN_11_INIT                     ((50 & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_ROI0_WIN23                        0xa414
>>> +#define AE_ROI_XYWIN_02_INIT                     (80 & 0xffff)
>>> +#define AE_ROI_XYWIN_03_INIT                     ((40 & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_ROI1_WIN23                        0xa418
>>> +#define AE_ROI_XYWIN_12_INIT                     (80 & 0xffff)
>>> +#define AE_ROI_XYWIN_13_INIT                     ((40 & 0xffff) << 16)
>>> +
>>> +#define ISP_RO_AE_ROI_STAT_PCK0_0                0xa424
>>> +#define ISP_RO_AE_ROI_STAT_PCK1_0                0xa428
>>> +#define ISP_RO_AE_ROI_STAT_PCK0_1                0xa42c
>>> +#define ISP_RO_AE_ROI_STAT_PCK1_1                0xa430
>>> +#define ISP_AE_CTRL                              0xa448
>>> +#define AE_CTRL_AE_STAT_LOCAL_MODE               GENMASK(3, 2)
>>> +#define AE_CTRL_AE_STAT_LOCAL_MODE_EXT(x)        (((x) >> 2) & 0x3)
>>> +#define AE_CTRL_INPUT_2LINE_TOGETHER             BIT(7)
>>> +#define AE_CTRL_LUMA_MODE_MASK                   GENMASK(9, 8)
>>> +#define AE_CTRL_LUMA_MODE_SHIFT                  8
>>> +#define AE_CTRL_LUMA_MODE_FILTER                 2
>>> +#define AE_CTRL_YPHS_OFST_MASK                   GENMASK(25, 24)
>>> +#define AE_CTRL_YPHS_OFST_SHIFT                  24
>>> +#define AE_CTRL_XPHS_OFST_MASK                   GENMASK(27, 26)
>>> +#define AE_CTRL_XPHS_OFST_SHIFT                  26
>>> +
>>> +#define ISP_AE_CRTL2_0                           0xa44c
>>> +#define AE_CRTL2_BL12_GRBGI0_MASK                GENMASK(11, 0)
>>> +#define AE_CRTL2_GAIN_GRBGI0_MASK                GENMASK(23, 16)
>>> +#define AE_CRTL2_GAIN_GRBGI0_SHIFT               16
>>> +
>>> +#define ISP_AE_CRTL2_1                           0xa450
>>> +#define AE_CRTL2_BL12_GRBGI1_MASK                GENMASK(11, 0)
>>> +#define AE_CRTL2_GAIN_GRBGI1_MASK                GENMASK(23, 16)
>>> +#define AE_CRTL2_GAIN_GRBGI1_SHIFT               16
>>> +
>>> +#define ISP_AE_CRTL2_2                           0xa454
>>> +#define AE_CRTL2_BL12_GRBGI2_MASK                GENMASK(11, 0)
>>> +#define AE_CRTL2_GAIN_GRBGI2_MASK                GENMASK(23, 16)
>>> +#define AE_CRTL2_GAIN_GRBGI2_SHIFT               16
>>> +
>>> +#define ISP_AE_CRTL2_3                           0xa458
>>> +#define AE_CRTL2_BL12_GRBGI3_MASK                GENMASK(11, 0)
>>> +#define AE_CRTL2_GAIN_GRBGI3_MASK                GENMASK(23, 16)
>>> +#define AE_CRTL2_GAIN_GRBGI3_SHIFT               16
>>> +
>>> +#define ISP_AE_CRTL2_4                           0xa45C
>>> +#define AE_CRTL2_BL12_GRBGI4_MASK                GENMASK(11, 0)
>>> +#define AE_CRTL2_GAIN_GRBGI4_MASK                GENMASK(23, 16)
>>> +#define AE_CRTL2_GAIN_GRBGI4_SHIFT               16
>>> +
>>> +#define ISP_AE_HV_SIZE                           0xa464
>>> +#define AE_HV_STAT_VSIZE(x)                      ((x) & 0xffff)
>>> +#define AE_HV_STAT_HSIZE(x)                      (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_HV_BLKNUM                         0xa468
>>> +#define AE_HV_STAT_VBLK_NUM_MASK                 GENMASK(6, 0)
>>> +#define AE_HV_STAT_VBLK_NUM_EXT(x)               ((x) & 0x7f)
>>> +#define AE_HV_STAT_HBLK_NUM_MASK                 GENMASK(22, 16)
>>> +#define AE_HV_STAT_HBLK_NUM_SHIFT                16
>>> +
>>> +#define ISP_AE_STAT_THD01                        0xa46c
>>> +#define AE_STAT_THRD0_INIT                       (13107 & 0xffff)
>>> +#define AE_STAT_THRD1_INIT                       ((26214 & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_STAT_THD23                        0xa470
>>> +#define AE_STAT_THRD2_INIT                       (39321 & 0xffff)
>>> +#define AE_STAT_THRD3_INIT                       ((52428 & 0xffff) << 16)
>>> +
>>> +#define ISP_RO_AE_STAT_GPNUM                     0xa49c
>>> +#define ISP_AE_IDX_ADDR                          0xa600
>>> +#define ISP_AE_IDX_DATA                          0xa604
>>> +#define AE_IDX_VIDX_DATA(x)                      ((x) & 0xffff)
>>> +#define AE_IDX_HIDX_DATA(x)                      (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AE_BLK_WT_ADDR                       0xa608
>>> +#define ISP_AE_BLK_WT_DATA                       0xa60c
>>> +#define AE_BLK_WT_DATA0(x)                       ((x) & 0xf)
>>> +#define AE_BLK_WT_DATA1(x)                       (((x) & 0xf) << 4)
>>> +#define AE_BLK_WT_DATA2(x)                       (((x) & 0xf) << 8)
>>> +#define AE_BLK_WT_DATA3(x)                       (((x) & 0xf) << 12)
>>> +#define AE_BLK_WT_DATA4(x)                       (((x) & 0xf) << 16)
>>> +#define AE_BLK_WT_DATA5(x)                       (((x) & 0xf) << 20)
>>> +#define AE_BLK_WT_DATA6(x)                       (((x) & 0xf) << 24)
>>> +#define AE_BLK_WT_DATA7(x)                       (((x) & 0xf) << 28)
>>> +
>>> +#define ISP_AWB_CTRL                             0xa834
>>> +#define AWB_CTRL_YPHS_OFST_MASK                  GENMASK(1, 0)
>>> +#define AWB_CTRL_XPHS_OFST_MASK                  GENMASK(3, 2)
>>> +#define AWB_CTRL_XPHS_OFST_SHIFT                 2
>>> +#define AWB_CTRL_STAT_RATIO_MODE                 BIT(4)
>>> +
>>> +#define ISP_AWB_HV_SIZE                          0xa83c
>>> +#define AWB_HV_STAT_VSIZE(x)                     ((x) & 0xffff)
>>> +#define AWB_HV_STAT_HSIZE(x)                     (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_STAT_ROI0_WIN01                  0xa80c
>>> +#define AWB_ROI_XYWIN_00_INIT                    (32 & 0xffff)
>>> +#define AWB_ROI_XYWIN_01_INIT                    ((62 & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_STAT_ROI1_WIN01                  0xa810
>>> +#define AWB_ROI_XYWIN_10_INIT                    (32 & 0xffff)
>>> +#define AWB_ROI_XYWIN_11_INIT                    ((82 & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_STAT_ROI0_WIN23                  0xa814
>>> +#define AWB_ROI_XYWIN_02_INIT                    (32 & 0xffff)
>>> +#define AWB_ROI_XYWIN_03_INIT                    ((42 & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_STAT_ROI1_WIN23                  0xa818
>>> +#define AWB_ROI_XYWIN_12_INIT                    (64 & 0xffff)
>>> +#define AWB_ROI_XYWIN_13_INIT                    ((52 & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_HV_BLKNUM                        0xa840
>>> +#define AWB_HV_STAT_VBLK_NUM_MASK                GENMASK(5, 0)
>>> +#define AWB_HV_STAT_VBLK_NUM_EXT(x)              ((x) & 0x3f)
>>> +#define AWB_HV_STAT_HBLK_NUM_MASK                GENMASK(21, 16)
>>> +#define AWB_HV_STAT_HBLK_NUM_SHIFT               16
>>> +
>>> +#define ISP_AWB_STAT_RG                          0xa848
>>> +#define AWB_STAT_RG_MIN_MASK                     GENMASK(11, 0)
>>> +#define AWB_STAT_RG_MAX_MASK                     GENMASK(27, 16)
>>> +#define AWB_STAT_RG_MAX_SHIFT                    16
>>> +
>>> +#define ISP_AWB_STAT_BG                          0xa84c
>>> +#define AWB_STAT_BG_MIN_MASK                     GENMASK(11, 0)
>>> +#define AWB_STAT_BG_MAX_MASK                     GENMASK(27, 16)
>>> +#define AWB_STAT_BG_MAX_SHIFT                    16
>>> +
>>> +#define ISP_AWB_STAT_RG_HL                       0xa850
>>> +#define AWB_STAT_RG_LOW_MASK                     GENMASK(11, 0)
>>> +#define AWB_STAT_RG_HIGH_MASK                    GENMASK(27, 16)
>>> +#define AWB_STAT_RG_HIGH_SHIFT                   16
>>> +
>>> +#define ISP_AWB_STAT_BG_HL                       0xa854
>>> +#define AWB_STAT_BG_LOW_MASK                     GENMASK(11, 0)
>>> +#define AWB_STAT_BG_HIGH_MASK                    GENMASK(27, 16)
>>> +#define AWB_STAT_BG_HIGH_SHIFT                   16
>>> +
>>> +#define ISP_AWB_STAT_CTRL2                       0xa858
>>> +#define AWB_STAT_SATUR_VALID_MASK                BIT(0)
>>> +#define AWB_STAT_LOCAL_MODE                      BIT(2)
>>> +#define AWB_STAT_LOCAL_MODE_EXT(x)               (((x) >> 2) & 0x1)
>>> +#define AWB_STAT_LUMA_DIV_MODE                   GENMASK(4, 3)
>>> +#define AWB_STAT_LUMA_DIV_MODE_EXT(x)            (((x) >> 3) & 0x3)
>>> +
>>> +#define ISP_AWB_STAT_BLC20_0                     0xa85c
>>> +#define AWB_STAT_BLC20_GR_MASK                   GENMASK(19, 0)
>>> +
>>> +#define ISP_AWB_STAT_BLC20_1                     0xa860
>>> +#define AWB_STAT_BLC20_R_MASK                    GENMASK(19, 0)
>>> +
>>> +#define ISP_AWB_STAT_BLC20_2                     0xa864
>>> +#define AWB_STAT_BLC20_B_MASK                    GENMASK(19, 0)
>>> +
>>> +#define ISP_AWB_STAT_BLC20_3                     0xa868
>>> +#define AWB_STAT_BLC20_GB_MASK                   GENMASK(19, 0)
>>> +
>>> +#define ISP_AWB_STAT_GAIN10_0                    0xa86c
>>> +#define AWB_STAT_GAIN10_GR_MASK                  GENMASK(9, 0)
>>> +
>>> +#define ISP_AWB_STAT_GAIN10_1                    0xa870
>>> +#define AWB_STAT_GAIN10_R_MASK                   GENMASK(9, 0)
>>> +
>>> +#define ISP_AWB_STAT_GAIN10_2                    0xa874
>>> +#define AWB_STAT_GAIN10_B_MASK                   GENMASK(9, 0)
>>> +
>>> +#define ISP_AWB_STAT_GAIN10_3                    0xa878
>>> +#define AWB_STAT_GAIN10_GB_MASK                  GENMASK(9, 0)
>>> +
>>> +#define ISP_AWB_STAT_SATUR_CTRL                  0xa884
>>> +#define AWB_STAT_SATUR_LOW(x)                    ((x) & 0xffff)
>>> +#define AWB_STAT_SATUR_HIGH(x)                   (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_IDX_ADDR                         0xaa00
>>> +#define ISP_AWB_IDX_DATA                         0xaa04
>>> +#define AWB_IDX_VIDX_DATA(x)                     ((x) & 0xffff)
>>> +#define AWB_IDX_HIDX_DATA(x)                     (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_AWB_BLK_WT_ADDR                      0xaa08
>>> +#define ISP_AWB_BLK_WT_DATA                      0xaa0c
>>> +#define AWB_BLK_WT_DATA0(x)                      ((x) & 0xf)
>>> +#define AWB_BLK_WT_DATA1(x)                      (((x) & 0xf) << 4)
>>> +#define AWB_BLK_WT_DATA2(x)                      (((x) & 0xf) << 8)
>>> +#define AWB_BLK_WT_DATA3(x)                      (((x) & 0xf) << 12)
>>> +#define AWB_BLK_WT_DATA4(x)                      (((x) & 0xf) << 16)
>>> +#define AWB_BLK_WT_DATA5(x)                      (((x) & 0xf) << 20)
>>> +#define AWB_BLK_WT_DATA6(x)                      (((x) & 0xf) << 24)
>>> +#define AWB_BLK_WT_DATA7(x)                      (((x) & 0xf) << 28)
>>> +
>>> +#define ISP_WRMIFX3_0_CH0_CTRL0                  0xc400
>>> +#define WRMIFX3_CH0_STRIDE_MASK                  GENMASK(28, 16)
>>> +#define WRMIFX3_CH0_STRIDE_SHIFT                 16
>>> +
>>> +#define ISP_WRMIFX3_0_CH0_CTRL1                  0xc404
>>> +#define WRMIFX3_CH0_PIX_BITS_MODE_MASK           GENMASK(30, 27)
>>> +#define WRMIFX3_CH0_PIX_BITS_MODE_SHIFT          27
>>> +#define WRMIFX3_CH0_PIX_BITS_8BITS               1
>>> +#define WRMIFX3_CH0_PIX_BITS_16BITS              2
>>> +
>>> +#define ISP_WRMIFX3_0_CH1_CTRL0                  0xc408
>>> +#define WRMIFX3_CH1_STRIDE_MASK                  GENMASK(28, 16)
>>> +#define WRMIFX3_CH1_STRIDE_SHIFT                 16
>>> +
>>> +#define ISP_WRMIFX3_0_CH1_CTRL1                  0xc40c
>>> +#define WRMIFX3_CH1_PIX_BITS_MODE_MASK           GENMASK(30, 27)
>>> +#define WRMIFX3_CH1_PIX_BITS_MODE_SHIFT          27
>>> +#define WRMIFX3_CH1_PIX_BITS_16BITS              2
>>> +#define WRMIFX3_CH1_PIX_BITS_32BITS              3
>>> +
>>> +#define ISP_WRMIFX3_0_CH0_BADDR                  0xc440
>>> +/* WRMIF base address need 16 bits alignment */
>>> +#define WRMIFX3_CH0_BADDR(x)                     (((x) >> 4) & 0xffffffff)
>>> +
>>> +#define ISP_WRMIFX3_0_CH1_BADDR                  0xc444
>>> +#define WRMIFX3_CH1_BADDR(x)                     (((x) >> 4) & 0xffffffff)
>>> +
>>> +#define ISP_WRMIFX3_0_FMT_SIZE                   0xc464
>>> +#define WRMIFX3_FMT_HSIZE(x)                     ((x) & 0xffff)
>>> +#define WRMIFX3_FMT_VSIZE(x)                     (((x) & 0xffff) << 16)
>>> +
>>> +#define ISP_WRMIFX3_0_FMT_CTRL               ��   0xc468
>>> +#define WRMIFX3_FMT_MTX_IBITS_MASK               GENMASK(1, 0)
>>> +#define WRMIFX3_FMT_MTX_IBITS_8BIT               0
>>> +#define WRMIFX3_FMT_MTX_IBITS_10BIT              1
>>> +#define WRMIFX3_FMT_MTX_IBITS_12BIT              2
>>> +#define WRMIFX3_FMT_MTX_IBITS_16BIT              3
>>> +#define WRMIFX3_FMT_MTX_UV_SWAP_MASK             BIT(2)
>>> +#define WRMIFX3_FMT_MTX_UV_SWAP_SHIFT            2
>>> +#define WRMIFX3_FMT_MTX_UV_SWAP_VU               0
>>> +#define WRMIFX3_FMT_MTX_UV_SWAP_UV               1
>>> +#define WRMIFX3_FMT_MTX_PLANE_MASK               GENMASK(5, 4)
>>> +#define WRMIFX3_FMT_MTX_PLANE_SHIFT              4
>>> +#define WRMIFX3_FMT_MTX_PLANE_X1                 0
>>> +#define WRMIFX3_FMT_MTX_PLANE_X2                 1
>>> +#define WRMIFX3_FMT_MODE_OUT_MASK                GENMASK(18, 16)
>>> +#define WRMIFX3_FMT_MODE_OUT_SHIFT               16
>>> +#define WRMIFX3_FMT_MODE_OUT_YUV422              1
>>> +#define WRMIFX3_FMT_MODE_OUT_YUV420              2
>>> +#define WRMIFX3_FMT_MODE_OUT_Y_ONLY              3
>>> +
>>> +#define ISP_WRMIFX3_0_WIN_LUMA_H                 0xc420
>>> +#define WRMIFX3_WIN_LUMA_HEND_MASK               GENMASK(28, 16)
>>> +#define WRMIFX3_WIN_LUMA_HEND_SHIFT              16
>>> +#define WRMIFX3_WIN_LUMA_HEND(x)                 ((x) - 1)
>>> +
>>> +#define ISP_WRMIFX3_0_WIN_LUMA_V                 0xc424
>>> +#define WRMIFX3_WIN_LUMA_VEND_MASK               GENMASK(28, 16)
>>> +#define WRMIFX3_WIN_LUMA_VEND_SHIFT              16
>>> +#define WRMIFX3_WIN_LUMA_VEND(x)                 ((x) - 1)
>>> +
>>> +#define ISP_WRMIFX3_0_WIN_CHROM_H                0xc428
>>> +#define WRMIFX3_WIN_CHROM_HEND_MASK              GENMASK(28, 16)
>>> +#define WRMIFX3_WIN_CHROM_HEND_SHIFT             16
>>> +#define WRMIFX3_WIN_CHROM_HEND(x)                (((x) >> 1) - 1)
>>> +
>>> +#define ISP_WRMIFX3_0_WIN_CHROM_V                0xc42c
>>> +#define WRMIFX3_WIN_CHROM_VEND_MASK              GENMASK(28, 16)
>>> +#define WRMIFX3_WIN_CHROM_VEND_SHIFT             16
>>> +#define WRMIFX3_WIN_CHROM_VEND(x)                (((x) >> 1) - 1)
>>> +
>>> +#define ISP_WRMIFX3_0_CRP_HSIZE                  0xc48c
>>> +#define WRMIFX3_CROP_HEND_MASK                   GENMASK(31, 16)
>>> +#define WRMIFX3_CROP_HEND_SHIFT                  16
>>> +#define WRMIFX3_CROP_HEND(x)                     ((x) - 1)
>>> +
>>> +#define ISP_WRMIFX3_0_CRP_VSIZE                  0xc490
>>> +#define WRMIFX3_CROP_VEND_MASK                   GENMASK(31, 16)
>>> +#define WRMIFX3_CROP_VEND_SHIFT                  16
>>> +#define WRMIFX3_CROP_VEND(x)                     ((x) - 1)
>>> +
>>> +#define VIU_DMAWR_BADDR0                         0xc840
>>> +#define VIU_DMAWR_AF_BADDR_MASK                  GENMASK(27, 0)
>>> +/* AF base address need 16 bits alignment */
>>> +#define VIU_DMAWR_AF_BADDR(x)                    ((x) >> 4)
>>> +
>>> +#define VIU_DMAWR_BADDR1                         0xc844
>>> +#define VIU_DMAWR_AWB_BADDR_MASK                  GENMASK(27, 0)
>>> +/* AWB base address need 16 bits alignment */
>>> +#define VIU_DMAWR_AWB_BADDR(x)                    ((x) >> 4)
>>> +
>>> +#define VIU_DMAWR_BADDR2                         0xc848
>>> +#define VIU_DMAWR_AE_BADDR_MASK                  GENMASK(27, 0)
>>> +/* AE base address need 16 bits alignment */
>>> +#define VIU_DMAWR_AE_BADDR(x)                    ((x) >> 4)
>>> +
>>> +#define VIU_DMAWR_SIZE0                          0xc854
>>> +#define VIU_DMAWR_SIZE_AF_MASK                   GENMASK(15, 0)
>>> +#define VIU_DMAWR_SIZE_AWB_MASK                  GENMASK(31, 16)
>>> +#define VIU_DMAWR_SIZE_AWB_SHIFT                 16
>>> +
>>> +#define VIU_DMAWR_SIZE1                          0xc858
>>> +#define VIU_DMAWR_SIZE_AE_MASK                   GENMASK(15, 0)
>>> +
>>> +#endif
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c 
>>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
>>> new file mode 100644
>>> index 000000000000..01d99b66cb32
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c
>>> @@ -0,0 +1,768 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/pm_runtime.h>
>>> +
>>> +#include "c3-isp-common.h"
>>> +#include "c3-isp-regs.h"
>>> +
>>> +static const struct c3_isp_mbus_format_info c3_isp_rsz_mbus_formats[] = {
>>> +     /* YUV formats */
>>> +     {
>>> +             .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
>>> +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>>> +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +     }, {
>>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_1_5X8,
>>> +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>>> +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +     }, {
>>> +             .mbus_code      = MEDIA_BUS_FMT_YUYV8_2X8,
>>> +             .pads           = BIT(C3_ISP_RESIZER_PAD_SINK)
>>> +                             | BIT(C3_ISP_RESIZER_PAD_SOURCE),
>>> +             .xofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +             .yofst          = C3_ISP_PHASE_OFFSET_NONE,
>>> +     },
>>> +};
>>> +
>>> +/* The normal parameters of pps module */
>>> +static const int pps_lut_tap4_s11[C3_PPS_TAP4_S11_H_NUM][4] = {
>>> +     {  0, 511,   0,   0}, { -5, 511,   5,   0}, {-10, 511, 11,   0},
>>> +     {-14, 510,  17,  -1}, {-18, 508,  23,  -1}, {-22, 506, 29,  -1},
>>> +     {-25, 503,  36,  -2}, {-28, 500,  43,  -3}, {-32, 496, 51,  -3},
>>> +     {-34, 491,  59,  -4}, {-37, 487,  67,  -5}, {-39, 482, 75,  -6},
>>> +     {-41, 476,  84,  -7}, {-42, 470,  92,  -8}, {-44, 463, 102,  -9},
>>> +     {-45, 456, 111, -10}, {-45, 449, 120, -12}, {-47, 442, 130, -13},
>>> +     {-47, 434, 140, -15}, {-47, 425, 151, -17}, {-47, 416, 161, -18},
>>> +     {-47, 407, 172, -20}, {-47, 398, 182, -21}, {-47, 389, 193, -23},
>>> +     {-46, 379, 204, -25}, {-45, 369, 215, -27}, {-44, 358, 226, -28},
>>> +     {-43, 348, 237, -30}, {-43, 337, 249, -31}, {-41, 326, 260, -33},
>>> +     {-40, 316, 271, -35}, {-39, 305, 282, -36}, {-37, 293, 293, -37}
>>> +};
>>> +
>>> +static const struct c3_isp_mbus_format_info
>>> +*rsz_find_format_by_code(u32 code, u32 pad)
>>> +{
>>> +     int i;
>>> +
>>> +     for (i = 0; i < ARRAY_SIZE(c3_isp_rsz_mbus_formats); i++) {
>>> +             const struct c3_isp_mbus_format_info *info =
>>> +                     &c3_isp_rsz_mbus_formats[i];
>>> +
>>> +             if (info->mbus_code == code && info->pads & BIT(pad))
>>> +                     return info;
>>> +     }
>>> +
>>> +     return NULL;
>>> +}
>>> +
>>> +static void c3_isp_rsz_cfg_fmt(struct c3_isp_resizer *rsz,
>>> +                            struct v4l2_subdev_state *state)
>>> +{
>>> +     struct v4l2_mbus_framefmt *fmt;
>>> +
>>> +     fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>>> +
>>> +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_DISP0_TOP_IN_SIZE, rsz->id),
>>> +                  DISP_TOP_IN_HSIZE(fmt->width) | DISP_TOP_IN_VSIZE(fmt->height));
>>> +}
>>> +
>>> +static void c3_isp_rsz_crop_enable(struct c3_isp_resizer *rsz,
>>> +                                struct v4l2_subdev_state *state)
>>> +{
>>> +     struct v4l2_rect *crop;
>>> +
>>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>>> +
>>> +     c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_START, rsz->id),
>>> +                  DISP_CRP2_VSTART(crop->top) | DISP_CRP2_HSTART(crop->left));
>>> +     c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_SIZE, rsz->id),
>>> +                  DISP_CRP2_VSIZE(crop->height) | DISP_CRP2_HSIZE(crop->width));
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
>>> +                        DISP_CRP2_EN, DISP_CRP2_EN);
>>> +}
>>> +
>>> +static void c3_isp_rsz_pps_size(struct c3_isp_resizer *rsz,
>>> +                             struct c3_isp_pps_io_size *io_size)
>>> +{
>>> +     int thsize = io_size->thsize;
>>> +     int tvsize = io_size->tvsize;
>>> +     u32 ohsize = io_size->ohsize;
>>> +     u32 ovsize = io_size->ovsize;
>>> +     u32 ihsize = io_size->ihsize;
>>> +     u32 max_hsize = io_size->max_hsize;
>>> +     int step_h_integer, step_v_integer;
>>> +     int step_h_fraction, step_v_fraction;
>>> +     int yuv444to422_en;
>>> +
>>> +     /* Calculate the integer part of horizonal scaler step */
>>> +     step_h_integer = thsize / ohsize;
>>> +
>>> +     /* Calculate the vertical part of horizonal scaler step */
>>> +     step_v_integer = tvsize / ovsize;
>>> +
>>> +     /*
>>> +      * Calculate the fraction part of horizonal scaler step.
>>> +      * step_h_fraction = (source / dest) * 2^24,
>>> +      * so step_h_fraction = ((source << 12) / dest) << 12.
>>> +      */
>>> +     step_h_fraction = ((thsize << 12) / ohsize) << 12;
>>> +
>>> +     /*
>>> +      * Calculate the fraction part of vertical scaler step
>>> +      * step_v_fraction = (source / dest) * 2^24,
>>> +      * so step_v_fraction = ((source << 12) / dest) << 12.
>>> +      */
>>> +     step_v_fraction = ((tvsize << 12) / ovsize) << 12;
>>> +
>>> +     yuv444to422_en = ihsize > (max_hsize / 2) ? 1 : 0;
>>> +
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_444TO422, rsz->id),
>>> +                        PPS_444TO422_EN_MASK, yuv444to422_en);
>>> +
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>>> +                        PPS_VSC_FRACTION_PART_MASK, step_v_fraction);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id),
>>> +                        PPS_VSC_INTEGER_PART_MASK,
>>> +                        step_v_integer << PPS_VSC_INTEGER_PART_SHIFT);
>>> +
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>>> +                        PPS_HSC_FRACTION_PART_MASK, step_h_fraction);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id),
>>> +                        PPS_HSC_INTEGER_PART_MASK,
>>> +                        step_h_integer << PPS_HSC_INTEGER_PART_SHIFT);
>>> +
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
>>> +                        PPS_PREHSC_LUMA_COEF0_MASK, PPS_PREHSC_LUMA_COEF0_INIT);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id),
>>> +                        PPS_PREHSC_LUMA_COEF1_MASK,
>>> +                        PPS_PREHSC_LUMA_COEF1_INIT << PPS_PREHSC_LUMA_COEF1_SHIFT);
>>> +
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
>>> +                        PPS_PREHSC_LUMA_COEF2_MASK, PPS_PREHSC_LUMA_COEF2_INIT);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id),
>>> +                        PPS_PREHSC_LUMA_COEF3_MASK,
>>> +                        PPS_PREHSC_LUMA_COEF3_INIT << PPS_PREHSC_LUMA_COEF3_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_rsz_pps_lut(struct c3_isp_resizer *rsz, u32 ctype)
>>> +{
>>> +     int i;
>>> +
>>> +     /*
>>> +      * Default value of this register is 0,
>>> +      * so only need to set SCALE_LUMA_COEF_S11_MODE
>>> +      * and SCALE_LUMA_CTYPE.
>>> +      * This register needs to be written in one time.
>>> +      */
>>> +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_LUMA, rsz->id),
>>> +                  SCALE_LUMA_COEF_S11_MODE | SCALE_LUMA_CTYPE(ctype));
>>> +
>>> +     for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
>>> +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>>> + SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) |
>>> + SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1]));
>>> +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id),
>>> + SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) |
>>> + SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][3]));
>>> +     }
>>> +
>>> +     /*
>>> +      * Default value of this register is 0,
>>> +      * so only need to set SCALE_CHRO_COEF_S11_MODE
>>> +      * and SCALE_CHRO_CTYPE.
>>> +      * This register needs to be written in one time.
>>> +      */
>>> +     c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_IDX_CHRO, rsz->id),
>>> +                  SCALE_CHRO_COEF_S11_MODE | SCALE_CHRO_CTYPE(ctype));
>>> +
>>> +     for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) {
>>> +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>>> + SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) |
>>> + SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1]));
>>> +             c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id),
>>> + SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) |
>>> + SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][3]));
>>> +     }
>>> +}
>>> +
>>> +static void c3_isp_rsz_pps_disable(struct c3_isp_resizer *rsz)
>>> +{
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_HSC_EN_MASK, 0);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_VSC_EN_MASK, 0);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_PREVSC_EN_MASK, 0);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_PREHSC_EN_MASK, 0);
>>> +}
>>> +
>>> +static int c3_isp_rsz_pps_enable(struct c3_isp_resizer *rsz,
>>> +                              struct v4l2_subdev_state *state)
>>> +{
>>> +     struct v4l2_rect *crop;
>>> +     struct v4l2_rect *cmps;
>>> +     int max_hsize;
>>> +     int hsc_en, vsc_en;
>>> +     int preh_en, prev_en;
>>> +     u32 reg_prehsc_rate;
>>> +     u32 reg_prevsc_flt_num;
>>> +     int pre_vscale_max_hsize;
>>> +     u32 ihsize_after_pre_hsc;
>>> +     u32 ihsize_after_pre_hsc_alt;
>>> +     u32 reg_vsc_tap_num_alt;
>>> +     u32 ihsize;
>>> +     u32 ivsize;
>>> +     struct c3_isp_pps_io_size io_size;
>>> +
>>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>>> +     cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>>> +
>>> +     ihsize = crop->width;
>>> +     ivsize = crop->height;
>>> +
>>> +     hsc_en = (ihsize == cmps->width) ? C3_SCALE_DIS : C3_SCALE_EN;
>>> +     vsc_en = (ivsize == cmps->height) ? C3_SCALE_DIS : C3_SCALE_EN;
>>> +
>>> +     /* Disable pps when there no need to use pps */
>>> +     if (!hsc_en && !vsc_en) {
>>> +             c3_isp_rsz_pps_disable(rsz);
>>> +             return 0;
>>> +     }
>>> +
>>> +     /*
>>> +      * Pre-scale needs to be enable
>>> +      * if the down scaling factor exceeds 4.
>>> +      */
>>> +     preh_en = (ihsize > cmps->width * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
>>> +     prev_en = (ivsize > cmps->height * 4) ? C3_SCALE_EN : C3_SCALE_DIS;
>>> +
>>> +     if (rsz->id == C3_ISP_RSZ_2) {
>>> +             max_hsize = C3_ISP_MAX_WIDTH;
>>> +             /*
>>> +              * Set vertical tap number and
>>> +              * the max hsize of pre-vertical scale.
>>> +              */
>>> +             reg_prevsc_flt_num = 4;
>>> +             pre_vscale_max_hsize = max_hsize / 2;
>>> +     } else {
>>> +             max_hsize = C3_ISP_DEFAULT_WIDTH;
>>> +             preh_en  = (ihsize > max_hsize) ? C3_SCALE_EN : C3_SCALE_DIS;
>>> +             /*
>>> +              * Set vertical tap number and
>>> +              * the max hsize of pre-vertical scale.
>>> +              */
>>> +             if (ihsize > (max_hsize / 2) &&
>>> +                 ihsize <= max_hsize && prev_en) {
>>> +                     reg_prevsc_flt_num = 2;
>>> +                     pre_vscale_max_hsize = max_hsize;
>>> +             } else {
>>> +                     reg_prevsc_flt_num = 4;
>>> +                     pre_vscale_max_hsize = max_hsize / 2;
>>> +             }
>>> +     }
>>> +
>>> +     /*
>>> +      * Set pre-horizonal scale rate and
>>> +      * the hsize of after pre-horizonal scale.
>>> +      */
>>> +     if (preh_en) {
>>> +             reg_prehsc_rate = 1;
>>> +             ihsize_after_pre_hsc = DIV_ROUND_UP(ihsize, 2);
>>> +     } else {
>>> +             reg_prehsc_rate = 0;
>>> +             ihsize_after_pre_hsc = ihsize;
>>> +     }
>>> +
>>> +     /* Change pre-horizonal scale rate */
>>> +     if (prev_en && ihsize_after_pre_hsc >= pre_vscale_max_hsize)
>>> +             reg_prehsc_rate += 1;
>>> +
>>> +     /* Set the actual hsize of after pre-horizonal scale */
>>> +     if (preh_en)
>>> +             ihsize_after_pre_hsc_alt =
>>> +                     DIV_ROUND_UP(ihsize, 1 << reg_prehsc_rate);
>>> +     else
>>> +             ihsize_after_pre_hsc_alt = ihsize;
>>> +
>>> +     /* Set vertical scaler bank length */
>>> +     if (ihsize_after_pre_hsc_alt <= (max_hsize / 2))
>>> +             reg_vsc_tap_num_alt = 4;
>>> +     else if (ihsize_after_pre_hsc_alt <= max_hsize)
>>> +             reg_vsc_tap_num_alt = prev_en ? 2 : 4;
>>> +     else
>>> +             reg_vsc_tap_num_alt = prev_en ? 4 : 2;
>>> +
>>> +     io_size.thsize = ihsize_after_pre_hsc_alt;
>>> +     io_size.tvsize = prev_en ? DIV_ROUND_UP(ivsize, 2) : ivsize;
>>> +     io_size.ohsize = cmps->width;
>>> +     io_size.ovsize = cmps->height;
>>> +     io_size.ihsize = ihsize;
>>> +     io_size.max_hsize = max_hsize;
>>> +
>>> +     c3_isp_rsz_pps_size(rsz, &io_size);
>>> +     c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_0);
>>> +     c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_2);
>>> +
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_VSC_TAP_NUM_MASK, reg_vsc_tap_num_alt);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_HSC_TAP_NUM_MASK,
>>> +                        PPS_HSC_TAP_NUM_INIT << PPS_HSC_TAP_NUM_SHIFT);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_PREVSC_FLT_NUM_MASK,
>>> +                        reg_prevsc_flt_num << PPS_PREVSC_FLT_NUM_SHIFT);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_PREHSC_FLT_NUM_MASK,
>>> +                        PPS_PREHSC_FLT_NUM_INIT << PPS_PREHSC_FLT_NUM_SHIFT);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_PREVSC_RATE_MASK, prev_en << PPS_PREVSC_RATE_SHIFT);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_PREHSC_RATE_MASK, reg_prehsc_rate << PPS_PREHSC_RATE_SHIFT);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_HSC_EN_MASK, hsc_en << PPS_HSC_EN_SHIFT);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_VSC_EN_MASK, vsc_en << PPS_VSC_EN_SHIFT);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_PREVSC_EN_MASK, prev_en << PPS_PREVSC_EN_SHIFT);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_PREHSC_EN_MASK, preh_en << PPS_PREHSC_EN_SHIFT);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_HSC_NOR_RS_BITS_MASK,
>>> +                        PPS_HSC_NOR_RS_BITS_INIT << PPS_HSC_NOR_RS_BITS_SHIFT);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id),
>>> +                        PPS_VSC_NOR_RS_BITS_MASK,
>>> +                        PPS_VSC_NOR_RS_BITS_INIT << PPS_VSC_NOR_RS_BITS_SHIFT);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +void c3_isp_rsz_start(struct c3_isp_resizer *rsz)
>>> +{
>>> +     struct v4l2_subdev_state *state;
>>> +
>>> +     state = v4l2_subdev_lock_and_get_active_state(&rsz->sd);
>>> +
>>> +     c3_isp_rsz_cfg_fmt(rsz, state);
>>> +     c3_isp_rsz_crop_enable(rsz, state);
>>> +     c3_isp_rsz_pps_enable(rsz, state);
>>> +
>>> +     c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN,
>>> +                        TOP_DISP_EN(rsz->id), TOP_DISP_EN(rsz->id));
>>> +
>>> +     v4l2_subdev_unlock_state(state);
>>> +}
>>> +
>>> +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz)
>>> +{
>>> +     c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, TOP_DISP_EN(rsz->id), 0);
>>> +     c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id),
>>> +                        DISP_CRP2_EN, 0x0);
>>> +
>>> +     c3_isp_rsz_pps_disable(rsz);
>>
>>
>> I would suggest enable_streams() / disable_streams() for these subdevices too, which gives you the
>> state already
>>
>
> OK, will add enable_streams() and disable_streams() interfaces.
>
>>> +}
>>> +
>>> +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd,
>>> +                               struct v4l2_subdev_state *state,
>>> +                               struct v4l2_subdev_krouting *routing)
>>> +{
>>> +     static const struct v4l2_mbus_framefmt format = {
>>> +             .width = C3_ISP_DEFAULT_WIDTH,
>>> +             .height = C3_ISP_DEFAULT_HEIGHT,
>>> +             .code = C3_ISP_RSZ_DEF_PAD_FMT,
>>> +             .field = V4L2_FIELD_NONE,
>>> +             .colorspace = V4L2_COLORSPACE_SRGB,
>>> +             .ycbcr_enc = V4L2_YCBCR_ENC_601,
>>> +             .quantization = V4L2_QUANTIZATION_LIM_RANGE,
>>> +             .xfer_func = V4L2_XFER_FUNC_SRGB,
>>> +     };
>>> +     int ret;
>>> +
>>> +     ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd,
>>> +                                struct v4l2_subdev_state *state)
>>> +{
>>> +     struct v4l2_subdev_route routes;
>>> +     struct v4l2_subdev_krouting routing;
>>> +
>>> +     routes.sink_pad = C3_ISP_RESIZER_PAD_SINK;
>>> +     routes.sink_stream = 0;
>>> +     routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE;
>>> +     routes.source_stream = 0;
>>> +     routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>>> +
>>> +     routing.num_routes = 1;
>>> +     routing.routes = &routes;
>>> +
>>> +     return c3_isp_rsz_cfg_routing(sd, state, &routing);
>>> +}
>>> +
>>> +static int c3_isp_rsz_set_routing(struct v4l2_subdev *sd,
>>> +                               struct v4l2_subdev_state *state,
>>> +                               enum v4l2_subdev_format_whence which,
>>> +                               struct v4l2_subdev_krouting *routing)
>>> +{
>>> +     bool is_streaming = v4l2_subdev_is_streaming(sd);
>>> +
>>> +     if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming)
>>> +             return -EBUSY;
>>> +
>>> +     return c3_isp_rsz_cfg_routing(sd, state, routing);
>>> +}
>>> +
>>> +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd,
>>> +                                  struct v4l2_subdev_state *state,
>>> +                                  struct v4l2_subdev_mbus_code_enum *code)
>>> +{
>>> +     if (code->index >= ARRAY_SIZE(c3_isp_rsz_mbus_formats))
>>> +             return -EINVAL;
>>> +
>>> +     code->code = c3_isp_rsz_mbus_formats[code->index].mbus_code;
>>> +
>>> +     return 0;
>>> +}
>>> +
>> If there's only a single sink and source pad I don't think the routing functions are needed
>
>
> Will remove this function.
>
>>> +static void c3_isp_rsz_set_sink_fmt(struct v4l2_subdev_state *state,
>>> +                                 struct v4l2_subdev_format *format)
>>> +{
>>> +     struct v4l2_mbus_framefmt *sink_fmt;
>>> +     const struct c3_isp_mbus_format_info *isp_fmt;
>>> +     struct v4l2_rect *sink_crop;
>>> +     struct v4l2_rect *sink_cmps;
>>> +
>>> +     sink_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>> +     sink_crop = v4l2_subdev_state_get_crop(state, format->pad);
>>> +     sink_cmps = v4l2_subdev_state_get_compose(state, format->pad);
>>> +
>>> +     isp_fmt = rsz_find_format_by_code(format->format.code, format->pad);
>>> +     if (!isp_fmt)
>>> +             sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>>> +     else
>>> +             sink_fmt->code = format->format.code;
>>> +
>>> +     sink_fmt->width = clamp_t(u32, format->format.width,
>>> +                               C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH);
>>> +     sink_fmt->height = clamp_t(u32, format->format.height,
>>> +                                C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT);
>>> +
>>> +     sink_crop->width = sink_fmt->width;
>>> +     sink_crop->height = sink_fmt->height;
>>> +     sink_crop->left = 0;
>>> +     sink_crop->top = 0;
>>> +
>>> +     sink_cmps->width = sink_crop->width;
>>> +     sink_cmps->height = sink_crop->height;
>>> +     sink_cmps->left = 0;
>>> +     sink_cmps->top = 0;
>>> +
>>> +     format->format = *sink_fmt;
>>> +}
>> You should propagate to the source pad here too
>
>
> Will set the source pad here.
>
>>> +
>>> +static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state,
>>> +                                   struct v4l2_subdev_format *format)
>>> +{
>>> +     const struct c3_isp_mbus_format_info *rsz_fmt;
>>> +     struct v4l2_mbus_framefmt *src_fmt;
>>> +     struct v4l2_rect *sink_crop;
>>> +     struct v4l2_rect *sink_cmps;
>>> +
>>> +     src_fmt = v4l2_subdev_state_get_format(state, format->pad);
>>> +     sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>>> +     sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>>> +
>>> +     rsz_fmt = rsz_find_format_by_code(format->format.code, format->pad);
>>> +     if (!rsz_fmt)
>>> +             src_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>>> +     else
>>> +             src_fmt->code = format->format.code;
>> Can the ISP convert between the YUV formats? If not I'd expect it the source pad to take the same
>> mbus code as the sink pad format
>
>
> ISP can't convert between the YUV formats.
>
> Will set the same mbus code as the sink pad format.
>
>>> +
>>> +     src_fmt->width = clamp_t(u32, format->format.width,
>>> +                              C3_ISP_MIN_WIDTH, sink_crop->width);
>>> +     src_fmt->height = clamp_t(u32, format->format.height,
>>> +                               C3_ISP_MIN_HEIGHT, sink_crop->height);
>>> +
>>> +     /* The sink compose size must be same with the source size. */
>>> +     sink_cmps->width = src_fmt->width;
>>> +     sink_cmps->height = src_fmt->height;
>> It should work the other way round - the .set_fmt() for the sink pad should update the source pad
>> format instead
>
>
> Will solve this issue.
>
>>> +
>>> +     format->format = *src_fmt;
>>> +}
>>> +
>>> +static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd,
>>> +                           struct v4l2_subdev_state *state,
>>> +                           struct v4l2_subdev_format *format)
>>> +{
>>> +     if (format->pad == C3_ISP_RESIZER_PAD_SINK) {
>>> +             c3_isp_rsz_set_sink_fmt(state, format);
>>> +     } else if (format->pad == C3_ISP_RESIZER_PAD_SOURCE) {
>>> +             c3_isp_rsz_set_source_fmt(state, format);
>>> +     } else {
>>> +             dev_err(sd->dev, "Invalid pad: %u\n", format->pad);
>>> +             return -ENOTTY;
>>> +     }
>> v4l2-core should already have checked that I believe
>
>
> Will remove the "else {}" branch.
>
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd,
>>> +                                 struct v4l2_subdev_state *state,
>>> +                                 struct v4l2_subdev_selection *sel)
>>> +{
>>> +     struct v4l2_mbus_framefmt *fmt;
>>> +     struct v4l2_rect *crop;
>>> +     struct v4l2_rect *cmps;
>>> +
>>> +     if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
>>> +             return -EINVAL;
>>> +
>>> +     switch (sel->target) {
>>> +     case V4L2_SEL_TGT_CROP_BOUNDS:
>>> +             fmt = v4l2_subdev_state_get_format(state, sel->pad);
>>> +             sel->r.width = fmt->width;
>>> +             sel->r.height = fmt->height;
>>> +             sel->r.left = 0;
>>> +             sel->r.top = 0;
>>> +             break;
>>> +     case V4L2_SEL_TGT_COMPOSE_BOUNDS:
>>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>>> +             sel->r.width = crop->width;
>>> +             sel->r.height = crop->height;
>>> +             sel->r.left = 0;
>>> +             sel->r.top = 0;
>>> +             break;
>>> +     case V4L2_SEL_TGT_CROP:
>>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>>> +             sel->r = *crop;
>>> +             break;
>>> +     case V4L2_SEL_TGT_COMPOSE:
>>> +             cmps = v4l2_subdev_state_get_compose(state, sel->pad);
>>> +             sel->r = *cmps;
>>> +             break;
>>> +     default:
>>> +             return -EINVAL;
>>> +     }
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd,
>>> +                                 struct v4l2_subdev_state *state,
>>> +                                 struct v4l2_subdev_selection *sel)
>>> +{
>>> +     struct v4l2_mbus_framefmt *fmt;
>>> +     struct v4l2_rect *crop;
>>> +     struct v4l2_rect *cmps;
>>> +
>>> +     if (sel->pad == C3_ISP_RESIZER_PAD_SOURCE)
>>> +             return -EINVAL;
>>> +
>>> +     switch (sel->target) {
>>> +     case V4L2_SEL_TGT_CROP:
>>> +             fmt = v4l2_subdev_state_get_format(state, sel->pad);
>>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>>> +
>>> +             sel->r.left = clamp_t(s32, sel->r.left, 0, fmt->width - 1);
>>> +             sel->r.top = clamp_t(s32, sel->r.top, 0, fmt->height - 1);
>>> +             sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH,
>>> +                                  fmt->width - sel->r.left);
>>> +             sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT,
>>> +                                   fmt->height - sel->r.top);
>>> +
>>> +             crop->width = ALIGN(sel->r.width, 2);
>>> +             crop->height = ALIGN(sel->r.height, 2);
>>> +             crop->left = sel->r.left;
>>> +             crop->top = sel->r.top;
>>> +
>>> +             sel->r = *crop;
>> The size should be propagated to the compose rectangle and source format too.
>
>
> Will  set the compose rectangle and source format with this size.
>
>>> +             break;
>>> +     case V4L2_SEL_TGT_COMPOSE:
>>> +             crop = v4l2_subdev_state_get_crop(state, sel->pad);
>>> +             cmps = v4l2_subdev_state_get_compose(state, sel->pad);
>>> +
>>> +             sel->r.left = 0;
>>> +             sel->r.top = 0;
>>> +             sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH, crop->width);
>>> +             sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT, crop->height);
>>> +
>>> +             cmps->width = ALIGN(sel->r.width, 2);
>>> +             cmps->height = ALIGN(sel->r.height, 2);
>>> +             cmps->left = sel->r.left;
>>> +             cmps->top = sel->r.top;
>>> +
>>> +             sel->r = *cmps;
>>> +
>>> +             fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
>>> +             fmt->width = cmps->width;
>>> +             fmt->height = cmps->height;
>>> +             break;
>> This looks ok
>
>
> Thanks.
>
>>> +     default:
>>> +             return -EINVAL;
>>> +     }
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_rsz_init_state(struct v4l2_subdev *sd,
>>> +                              struct v4l2_subdev_state *state)
>>> +{
>>> +     struct v4l2_mbus_framefmt *sink_fmt;
>>> +     struct v4l2_mbus_framefmt *src_fmt;
>>> +     struct v4l2_rect *crop;
>>> +     struct v4l2_rect *cmps;
>>> +
>>> +     sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK);
>>> +     sink_fmt->width = C3_ISP_DEFAULT_WIDTH;
>>> +     sink_fmt->height = C3_ISP_DEFAULT_HEIGHT;
>>> +     sink_fmt->field = V4L2_FIELD_NONE;
>>> +     sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT;
>>> +     sink_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>> +     sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
>>> +     sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
>>> +     sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
>>> +
>>> +     crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK);
>>> +     crop->width = C3_ISP_DEFAULT_WIDTH;
>>> +     crop->height = C3_ISP_DEFAULT_HEIGHT;
>>> +     crop->left = 0;
>>> +     crop->top = 0;
>>> +
>>> +     cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK);
>>> +     cmps->width = C3_ISP_DEFAULT_WIDTH;
>>> +     cmps->height = C3_ISP_DEFAULT_HEIGHT;
>>> +     cmps->left = 0;
>>> +     cmps->top = 0;
>>> +
>>> +     src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE);
>>> +     *src_fmt = *sink_fmt;
>>> +
>>> +     return c3_isp_rsz_init_routing(sd, state);
>>> +}
>>> +
>>> +static const struct v4l2_subdev_pad_ops c3_isp_rsz_pad_ops = {
>>> +     .enum_mbus_code = c3_isp_rsz_enum_mbus_code,
>>> +     .get_fmt = v4l2_subdev_get_fmt,
>>> +     .set_fmt = c3_isp_rsz_set_fmt,
>>> +     .get_selection = c3_isp_rsz_get_selection,
>>> +     .set_selection = c3_isp_rsz_set_selection,
>>> +     .set_routing = c3_isp_rsz_set_routing,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_ops c3_isp_rsz_subdev_ops = {
>>> +     .pad = &c3_isp_rsz_pad_ops,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_internal_ops c3_isp_rsz_internal_ops = {
>>> +     .init_state = c3_isp_rsz_init_state,
>>> +};
>>> +
>>> +/* Media entity operations */
>>> +static const struct media_entity_operations c3_isp_rsz_entity_ops = {
>>> +     .link_validate = v4l2_subdev_link_validate,
>>> +};
>>> +
>>> +static int c3_isp_rsz_register(struct c3_isp_resizer *rsz)
>>> +{
>>> +     struct v4l2_subdev *sd = &rsz->sd;
>>> +     int ret;
>>> +
>>> +     v4l2_subdev_init(sd, &c3_isp_rsz_subdev_ops);
>>> +     sd->owner = THIS_MODULE;
>>> +     sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>> +     sd->internal_ops = &c3_isp_rsz_internal_ops;
>>> +     snprintf(sd->name, sizeof(sd->name), "isp-resizer%u", rsz->id);
>>> +
>>> +     sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
>>> +     sd->entity.ops = &c3_isp_rsz_entity_ops;
>>> +
>>> +     sd->dev = rsz->isp->dev;
>>> +     v4l2_set_subdevdata(sd, rsz);
>>> +
>>> +     rsz->pads[C3_ISP_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>>> +     rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>>> +     ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_PAD_MAX, rsz->pads);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     ret = v4l2_subdev_init_finalize(sd);
>>> +     if (ret)
>>> +             goto err_entity_cleanup;
>>> +
>>> +     ret = v4l2_device_register_subdev(&rsz->isp->v4l2_dev, sd);
>>> +     if (ret)
>>> +             goto err_subdev_cleanup;
>>> +
>>> +     return 0;
>>> +
>>> +err_subdev_cleanup:
>>> +     v4l2_subdev_cleanup(sd);
>>> +err_entity_cleanup:
>>> +     media_entity_cleanup(&sd->entity);
>>> +     return ret;
>>> +}
>>> +
>>> +static void c3_isp_rsz_unregister(struct c3_isp_resizer *rsz)
>>> +{
>>> +     struct v4l2_subdev *sd = &rsz->sd;
>>> +
>>> +     v4l2_device_unregister_subdev(sd);
>>> +     v4l2_subdev_cleanup(sd);
>>> +     media_entity_cleanup(&sd->entity);
>>> +}
>>> +
>>> +int c3_isp_resizers_register(struct c3_isp_device *isp)
>>> +{
>>> +     u32 i;
>>> +     int ret;
>>> +
>>> +     for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
>>> +             struct c3_isp_resizer *rsz = &isp->resizers[i];
>>> +
>>> +             rsz->id = i;
>>> +             rsz->isp = isp;
>>> +
>>> +             if (rsz->id == C3_ISP_RSZ_0)
>>> +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_0];
>>> +             else if (rsz->id == C3_ISP_RSZ_1)
>>> +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_1];
>>> +             else
>>> +                     rsz->cap = &isp->caps[C3_ISP_CAP_DEV_2];
>>> +
>>> +             ret = c3_isp_rsz_register(rsz);
>>> +             if (ret) {
>>> +                     rsz->isp = NULL;
>>> +                     c3_isp_resizers_unregister(isp);
>>> +                     return ret;
>>> +             }
>>> +     }
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +void c3_isp_resizers_unregister(struct c3_isp_device *isp)
>>> +{
>>> +     u32 i;
>>> +
>>> +     for (i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) {
>>> +             struct c3_isp_resizer *rsz = &isp->resizers[i];
>>> +
>>> +             if (rsz->isp)
>>> +                     c3_isp_rsz_unregister(rsz);
>>> +     };
>>> +}
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c 
>>> b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
>>> new file mode 100644
>>> index 000000000000..72024442d48f
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c
>>> @@ -0,0 +1,488 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/cleanup.h>
>>> +#include <linux/pm_runtime.h>
>>> +
>>> +#include <media/v4l2-ctrls.h>
>>> +#include <media/v4l2-event.h>
>>> +#include <media/v4l2-ioctl.h>
>>> +#include <media/v4l2-mc.h>
>>> +#include <media/videobuf2-dma-contig.h>
>>> +
>>> +#include "c3-isp-common.h"
>>> +#include "c3-isp-regs.h"
>>> +#include "include/uapi/c3-isp-config.h"
>>> +
>>> +/* Hardware configuration */
>>> +
>>> +static void c3_isp_stats_af_init(struct c3_isp_stats *stats)
>>> +{
>>> +     c3_isp_write(stats->isp, ISP_AF_ROI0_WIN01,
>>> +                  AF_ROI_XYWIN_00_INIT | AF_ROI_XYWIN_01_INIT);
>>> +
>>> +     c3_isp_write(stats->isp, ISP_AF_ROI0_WIN23,
>>> +                  AF_ROI_XYWIN_02_INIT | AF_ROI_XYWIN_03_INIT);
>>> +
>>> +     c3_isp_write(stats->isp, ISP_AF_ROI1_WIN01,
>>> +                  AF_ROI_XYWIN_10_INIT | AF_ROI_XYWIN_11_INIT);
>>> +
>>> +     c3_isp_write(stats->isp, ISP_AF_ROI1_WIN23,
>>> +                  AF_ROI_XYWIN_12_INIT | AF_ROI_XYWIN_13_INIT);
>>> +
>>> +     /* 0: old statistics output, 1: new statistics output. */
>>> +     c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL,
>>> +                        AF_STAT_SELECT, 0x1 << AF_STAT_SELECT_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_stats_ae_init(struct c3_isp_stats *stats)
>>> +{
>>> +     c3_isp_write(stats->isp, ISP_AE_ROI0_WIN01,
>>> +                  AE_ROI_XYWIN_00_INIT | AE_ROI_XYWIN_01_INIT);
>>> +
>>> +     c3_isp_write(stats->isp, ISP_AE_ROI0_WIN23,
>>> +                  AE_ROI_XYWIN_02_INIT | AE_ROI_XYWIN_03_INIT);
>>> +
>>> +     c3_isp_write(stats->isp, ISP_AE_ROI1_WIN01,
>>> +                  AE_ROI_XYWIN_10_INIT | AE_ROI_XYWIN_11_INIT);
>>> +
>>> +     c3_isp_write(stats->isp, ISP_AE_ROI1_WIN23,
>>> +                  AE_ROI_XYWIN_12_INIT | AE_ROI_XYWIN_13_INIT);
>>> +
>>> +     c3_isp_write(stats->isp, ISP_AE_STAT_THD01,
>>> +                  AE_STAT_THRD0_INIT | AE_STAT_THRD1_INIT);
>>> +
>>> +     c3_isp_write(stats->isp, ISP_AE_STAT_THD23,
>>> +                  AE_STAT_THRD2_INIT | AE_STAT_THRD3_INIT);
>>> +
>>> +     /* Set 0 when ae_stat_switch is not 0 */
>>> +     c3_isp_update_bits(stats->isp, ISP_AE_CTRL,
>>> +                        AE_CTRL_INPUT_2LINE_TOGETHER, 0);
>>> +
>>> +     /* Configure ae luma mode */
>>> +     c3_isp_update_bits(stats->isp, ISP_AE_CTRL, AE_CTRL_LUMA_MODE_MASK,
>>> +                        AE_CTRL_LUMA_MODE_FILTER << AE_CTRL_LUMA_MODE_SHIFT);
>>> +}
>>> +
>>> +static void c3_isp_stats_awb_init(struct c3_isp_stats *stats)
>>> +{
>>> +     /* Initialize the awb statistics rectangle of image */
>>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN01,
>>> +                  AWB_ROI_XYWIN_00_INIT | AWB_ROI_XYWIN_01_INIT);
>>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN23,
>>> +                  AWB_ROI_XYWIN_02_INIT | AWB_ROI_XYWIN_03_INIT);
>>> +
>>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN01,
>>> +                  AWB_ROI_XYWIN_10_INIT | AWB_ROI_XYWIN_11_INIT);
>>> +     c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN23,
>>> +                  AWB_ROI_XYWIN_12_INIT | AWB_ROI_XYWIN_13_INIT);
>>> +}
>>> +
>>> +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats)
>>> +{
>>> +     struct c3_isp_device *isp = stats->isp;
>>> +     struct c3_isp_stats_info *stats_info = stats->buff->vaddr;
>>> +     u32 awb_dma_size = sizeof(stats_info->awb_stats);
>>> +     u32 ae_dma_size = sizeof(stats_info->ae_stats);
>> You could just use sizeof(struct awb_stats_info) and not bother with the vaddr at all...but then
>> again perhaps doing it this way is less prone to future mistakes from expansion of struct
>> c3_isp_stats_info. I vacillate a little, so I leave it up to you.
>
>
> OK, Will check this.
>
>>> +     u32 awb_dma_addr = stats->buff->paddr;
>>> +     u32 af_dma_addr;
>>> +     u32 ae_dma_addr;
>>> +
>>> +     ae_dma_addr = awb_dma_addr + awb_dma_size;
>>> +     af_dma_addr = ae_dma_addr + ae_dma_size;
>>> +
>>> +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR0, VIU_DMAWR_AF_BADDR_MASK,
>>> +                        VIU_DMAWR_AF_BADDR(af_dma_addr));
>>> +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR1, VIU_DMAWR_AWB_BADDR_MASK,
>>> +                        VIU_DMAWR_AWB_BADDR(awb_dma_addr));
>>> +     c3_isp_update_bits(isp, VIU_DMAWR_BADDR2, VIU_DMAWR_AE_BADDR_MASK,
>>> +                        VIU_DMAWR_AE_BADDR(ae_dma_addr));
>>> +}
>>> +
>>> +static void c3_isp_stats_enable(struct c3_isp_stats *stats)
>>> +{
>>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>> +                        TOP_3A_AE_STAT_EN, TOP_3A_AE_STAT_EN);
>>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>> +                        TOP_3A_AWB_STAT_EN, TOP_3A_AWB_STAT_EN);
>>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>> +                        TOP_3A_AF_STAT_EN, TOP_3A_AF_STAT_EN);
>>> +}
>>> +
>>> +static void c3_isp_stats_disable(struct c3_isp_stats *stats)
>>> +{
>>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>> +                        TOP_3A_AE_STAT_EN, 0);
>>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>> +                        TOP_3A_AWB_STAT_EN, 0);
>>> +     c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL,
>>> +                        TOP_3A_AF_STAT_EN, 0);
>>> +}
>>> +
>>> +/* The unit of dma_size is 16 bytes */
>>> +static void c3_isp_stats_cfg_dmawr_size(struct c3_isp_stats *stats)
>>> +{
>>> +     u32 dma_size;
>>> +
>>> +     dma_size = sizeof(struct af_stats_info) / 16;
>>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0,
>>> +                        VIU_DMAWR_SIZE_AF_MASK, dma_size);
>>> +
>>> +     dma_size = sizeof(struct awb_stats_info) / 16;
>>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE_AWB_MASK,
>>> +                        dma_size << VIU_DMAWR_SIZE_AWB_SHIFT);
>>> +
>>> +     dma_size = sizeof(struct ae_stats_info) / 16;
>>> +     c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1,
>>> +                        VIU_DMAWR_SIZE_AE_MASK, dma_size);
>>> +}
>>> +
>>> +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats)
>>> +{
>>> +     stats->buff = list_first_entry_or_null(&stats->pending,
>>> +                                            struct c3_isp_vb2_buffer, list);
>>> +     if (stats->buff) {
>>> +             c3_isp_stats_cfg_dmawr_addr(stats);
>>> +             list_del(&stats->buff->list);
>>> +     }
>>> +}
>>
>> Same locking comment as with the capture code.
>
>
> Will add lock.
>
>>> +
>>> +static void c3_isp_stats_start(struct c3_isp_stats *stats)
>>> +{
>>> +     c3_isp_stats_af_init(stats);
>>> +     c3_isp_stats_ae_init(stats);
>>> +     c3_isp_stats_awb_init(stats);
>>> +
>>> +     c3_isp_stats_cfg_dmawr_size(stats);
>>> +     c3_isp_stats_cfg_buff(stats);
>>> +     c3_isp_stats_enable(stats);
>>> +
>>> +     stats->is_streaming = true;
>> And similarly I'd drop this variable and rely on the state of the list and stats->buff
>
>
> Will try to use c3_isp_stats_return_buffers().
>
>>> +}
>>> +
>>> +static void c3_isp_stats_stop(struct c3_isp_stats *stats)
>>> +{
>>> +     stats->is_streaming = false;
>>> +
>>> +     c3_isp_stats_disable(stats);
>>> +}
>>> +
>>> +static void c3_isp_stats_return_buffers(struct c3_isp_stats *stats,
>>> +                                     enum vb2_buffer_state state)
>>> +{
>>> +     unsigned long flags;
>>> +     struct c3_isp_vb2_buffer *buff;
>>> +
>>> +     spin_lock_irqsave(&stats->buff_lock, flags);
>> Usual comment about the use of guard()
>
>
> Will use guard().
>
>>> +
>>> +     if (stats->buff) {
>>> + vb2_buffer_done(&stats->buff->vb.vb2_buf, state);
>>> +             stats->buff = NULL;
>>> +     }
>>> +
>>> +     while (!list_empty(&stats->pending)) {
>>> +             buff = list_first_entry(&stats->pending,
>>> +                                     struct c3_isp_vb2_buffer, list);
>>> +             list_del(&buff->list);
>>> +             vb2_buffer_done(&buff->vb.vb2_buf, state);
>>> +     }
>>> +
>>> +     spin_unlock_irqrestore(&stats->buff_lock, flags);
>>> +}
>>> +
>>> +static int c3_isp_stats_querycap(struct file *file, void *fh,
>>> +                              struct v4l2_capability *cap)
>>> +{
>>> +     strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver));
>>> +     strscpy(cap->card, "AML C3 ISP", sizeof(cap->card));
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_stats_enum_fmt(struct file *file, void *fh,
>>> +                              struct v4l2_fmtdesc *f)
>>> +{
>>> +     struct c3_isp_stats *stats = video_drvdata(file);
>>> +
>>> +     if (f->index > 0 || f->type != stats->vb2_q.type)
>>> +             return -EINVAL;
>>> +
>>> +     f->pixelformat = V4L2_META_FMT_C3ISP_STATS;
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_stats_g_fmt(struct file *file, void *fh,
>>> +                           struct v4l2_format *f)
>>> +{
>>> +     struct c3_isp_stats *stats = video_drvdata(file);
>>> +
>>> +     f->fmt.meta = stats->vfmt.fmt.meta;
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = {
>>> +     .vidioc_querycap                = c3_isp_stats_querycap,
>>> +     .vidioc_enum_fmt_meta_cap       = c3_isp_stats_enum_fmt,
>>> +     .vidioc_g_fmt_meta_cap          = c3_isp_stats_g_fmt,
>>> +     .vidioc_s_fmt_meta_cap          = c3_isp_stats_g_fmt,
>>> +     .vidioc_try_fmt_meta_cap        = c3_isp_stats_g_fmt,
>>> +     .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
>>> +     .vidioc_querybuf                = vb2_ioctl_querybuf,
>>> +     .vidioc_qbuf                    = vb2_ioctl_qbuf,
>>> +     .vidioc_expbuf                  = vb2_ioctl_expbuf,
>>> +     .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
>>> +     .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
>>> +     .vidioc_create_bufs             = vb2_ioctl_create_bufs,
>>> +     .vidioc_streamon                = vb2_ioctl_streamon,
>>> +     .vidioc_streamoff               = vb2_ioctl_streamoff,
>>> +     .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
>>> +     .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
>>> +};
>>> +
>>> +static const struct v4l2_file_operations isp_stats_v4l2_fops = {
>>> +     .open = v4l2_fh_open,
>>> +     .release = vb2_fop_release,
>>> +     .poll = vb2_fop_poll,
>>> +     .unlocked_ioctl = video_ioctl2,
>>> +     .mmap = vb2_fop_mmap,
>>> +};
>>> +
>>> +static int c3_isp_stats_vb2_queue_setup(struct vb2_queue *q,
>>> +                                     unsigned int *num_buffers,
>>> +                                     unsigned int *num_planes,
>>> +                                     unsigned int sizes[],
>>> +                                     struct device *alloc_devs[])
>>> +{
>>> +     if (*num_planes) {
>>> +             if (*num_planes != 1)
>>> +                     return -EINVAL;
>>> +
>>> +             if (sizes[0] < sizeof(struct c3_isp_stats_info))
>>> +                     return -EINVAL;
>>> +     } else {
>>> +             *num_planes = 1;
>>> +             sizes[0] = sizeof(struct c3_isp_stats_info);
>>> +     }
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb)
>>> +{
>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>> +     struct c3_isp_vb2_buffer *buf =
>>> +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>>> +     unsigned long flags;
>>> +
>>> +     spin_lock_irqsave(&stats->buff_lock, flags);
>>> +
>>> +     list_add_tail(&buf->list, &stats->pending);
>>> +
>>> +     spin_unlock_irqrestore(&stats->buff_lock, flags);
>>> +}
>>> +
>>> +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb)
>>> +{
>>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>>> +     unsigned int size = stats->vfmt.fmt.meta.buffersize;
>>> +
>>> +     if (vb2_plane_size(vb, 0) < size) {
>>> +             dev_err(stats->isp->dev,
>>> +                     "User buffer too small (%ld < %u)\n",
>>> +                     vb2_plane_size(vb, 0), size);
>>> +             return -EINVAL;
>>> +     }
>>> +
>>> +     vb2_set_plane_payload(vb, 0, size);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb)
>>> +{
>>> +     struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
>>> +     struct c3_isp_vb2_buffer *buf =
>>> +                     container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb);
>>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue);
>>> +
>>> +     buf->vaddr = vb2_plane_vaddr(vb, 0);
>>> +     buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
>>> +
>>> +     memset(buf->vaddr, 0, stats->vfmt.fmt.meta.buffersize);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int c3_isp_stats_vb2_start_streaming(struct vb2_queue *q,
>>> +                                         unsigned int count)
>>> +{
>>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(q);
>>> +     int ret;
>>> +
>>> +     guard(mutex)(&stats->isp->lock);
>>> +
>>> +     ret = pm_runtime_resume_and_get(stats->isp->dev);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     ret = video_device_pipeline_start(&stats->vdev, &stats->isp->pipe);
>>> +     if (ret) {
>>> +             dev_err(stats->isp->dev,
>>> +                     "Failed to start stats pipeline: %d\n", ret);
>>> +             goto err_pm_put;
>>> +     }
>>> +
>>> +     if (c3_isp_pipeline_ready(stats->isp)) {
>>> +             ret = v4l2_subdev_enable_streams(&stats->isp->core.sd,
>>> + C3_ISP_CORE_PAD_SOURCE_STATS,
>>> +                                              BIT(0));
>>> +             if (ret)
>>> +                     goto err_pipeline_stop;
>>> +     }
>>> +
>>> +     c3_isp_stats_start(stats);
>>> +
>>> +     return 0;
>>> +
>>> +err_pipeline_stop:
>>> +     video_device_pipeline_stop(&stats->vdev);
>>> +err_pm_put:
>>> +     pm_runtime_put(stats->isp->dev);
>>> +     c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_QUEUED);
>>> +     return ret;
>>> +}
>>> +
>>> +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q)
>>> +{
>>> +     struct c3_isp_stats *stats = vb2_get_drv_priv(q);
>>> +
>>> +     guard(mutex)(&stats->isp->lock);
>>> +
>>> +     c3_isp_stats_stop(stats);
>>> +     c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_ERROR);
>>> +
>>> +     if (stats->isp->pipe.start_count == 1)
>>> + v4l2_subdev_disable_streams(&stats->isp->core.sd,
>>> + C3_ISP_CORE_PAD_SOURCE_STATS,
>>> +                                         BIT(0));
>>> +
>>> +     video_device_pipeline_stop(&stats->vdev);
>>> +     pm_runtime_put(stats->isp->dev);
>>> +}
>>> +
>>> +static const struct vb2_ops isp_stats_vb2_ops = {
>>> +     .queue_setup = c3_isp_stats_vb2_queue_setup,
>>> +     .buf_queue = c3_isp_stats_vb2_buf_queue,
>>> +     .buf_prepare = c3_isp_stats_vb2_buf_prepare,
>>> +     .buf_init = c3_isp_stats_vb2_buf_init,
>>> +     .wait_prepare = vb2_ops_wait_prepare,
>>> +     .wait_finish = vb2_ops_wait_finish,
>>> +     .start_streaming = c3_isp_stats_vb2_start_streaming,
>>> +     .stop_streaming = c3_isp_stats_vb2_stop_streaming,
>>> +};
>>> +
>>> +int c3_isp_stats_register(struct c3_isp_device *isp)
>>> +{
>>> +     struct c3_isp_stats *stats = &isp->stats;
>>> +     struct video_device *vdev = &stats->vdev;
>>> +     struct vb2_queue *vb2_q = &stats->vb2_q;
>>> +     int ret;
>>> +
>>> +     memset(stats, 0, sizeof(*stats));
>>> +     stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS;
>>> +     stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_info);
>>> +     stats->isp = isp;
>>> +     INIT_LIST_HEAD(&stats->pending);
>>> +     spin_lock_init(&stats->buff_lock);
>>> +
>>> +     mutex_init(&stats->lock);
>>> +
>>> +     snprintf(vdev->name, sizeof(vdev->name), "isp-stats");
>>> +     vdev->fops = &isp_stats_v4l2_fops;
>>> +     vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops;
>>> +     vdev->v4l2_dev = &isp->v4l2_dev;
>>> +     vdev->lock = &stats->lock;
>>> +     vdev->minor = -1;
>>> +     vdev->queue = vb2_q;
>>> +     vdev->release = video_device_release_empty;
>>> +     vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
>>> +     vdev->vfl_dir = VFL_DIR_RX;
>>> +     video_set_drvdata(vdev, stats);
>>> +
>>> +     vb2_q->drv_priv = stats;
>>> +     vb2_q->mem_ops = &vb2_dma_contig_memops;
>>> +     vb2_q->ops = &isp_stats_vb2_ops;
>>> +     vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE;
>>> +     vb2_q->io_modes = VB2_DMABUF | VB2_MMAP;
>>> +     vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>>> +     vb2_q->buf_struct_size = sizeof(struct c3_isp_vb2_buffer);
>>> +     vb2_q->dev = isp->dev;
>>> +     vb2_q->lock = &stats->lock;
>>> +     vb2_q->min_queued_buffers = 2;
>>> +
>>> +     ret = vb2_queue_init(vb2_q);
>>> +     if (ret)
>>> +             goto err_destroy;
>>> +
>>> +     stats->pad.flags = MEDIA_PAD_FL_SINK;
>>> +     ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad);
>>> +     if (ret)
>>> +             goto err_queue_release;
>>> +
>>> +     ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>>> +     if (ret) {
>>> +             dev_err(isp->dev,
>>> +                     "Failed to register %s: %d\n", vdev->name, ret);
>>> +             goto err_entity_cleanup;
>>> +     }
>>> +
>>> +     return 0;
>>> +
>>> +err_entity_cleanup:
>>> +     media_entity_cleanup(&vdev->entity);
>>> +err_queue_release:
>>> +     vb2_queue_release(vb2_q);
>>> +err_destroy:
>>> +     mutex_destroy(&stats->lock);
>>> +     return ret;
>>> +}
>>> +
>>> +void c3_isp_stats_unregister(struct c3_isp_device *isp)
>>> +{
>>> +     struct c3_isp_stats *stats = &isp->stats;
>>> +
>>> +     vb2_queue_release(&stats->vb2_q);
>>> +     media_entity_cleanup(&stats->vdev.entity);
>>> +     video_unregister_device(&stats->vdev);
>>> +     mutex_destroy(&stats->lock);
>>> +}
>>> +
>>> +int c3_isp_stats_done(struct c3_isp_device *isp)
>>> +{
>>> +     struct c3_isp_stats *stats = &isp->stats;
>>> +     struct c3_isp_vb2_buffer *buff = stats->buff;
>>> +     unsigned long flags;
>>> +
>>> +     if (!stats->is_streaming)
>>> +             return -EINVAL;
>>> +
>>> +     spin_lock_irqsave(&stats->buff_lock, flags);
>>> +
>>> +     if (buff) {
>>> +             buff->vb.sequence = stats->isp->frm_sequence;
>>> +             buff->vb.vb2_buf.timestamp = ktime_get();
>>> +             buff->vb.field = V4L2_FIELD_NONE;
>>> +             vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE);
>>> +     }
>>> +
>>> +     c3_isp_stats_cfg_buff(stats);
>>> +
>>> +     spin_unlock_irqrestore(&stats->buff_lock, flags);
>>> +
>>> +     return 0;
>>> +}
>>> diff --git a/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h 
>>> b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
>>> new file mode 100644
>>> index 000000000000..84ff5741357a
>>> --- /dev/null
>>> +++ b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h
>>> @@ -0,0 +1,537 @@
>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#ifndef __C3_ISP_CONFIG_H__
>>> +#define __C3_ISP_CONFIG_H__
>>> +
>>> +#define AF_STAT_BLKH_NUM             17
>>> +#define AF_STAT_BLKV_NUM             15
>>> +#define AF_STAT_BLK_NUM              (AF_STAT_BLKH_NUM * AF_STAT_BLKV_NUM)
>>> +/* AF stats block size need to be aligned with 2 */
>>> +#define AF_STAT_BLK_SIZE             ALIGN(AF_STAT_BLK_NUM, 2)
>>> +#define AE_HISTOGRAM_SIZE         1024
>>> +#define AE_STAT_BLKH_NUM             17
>>> +#define AE_STAT_BLKV_NUM             15
>>> +#define AE_STAT_BLK_NUM              (AE_STAT_BLKH_NUM * AE_STAT_BLKV_NUM)
>>> +/* AE stats block size need to be aligned with 2 */
>>> +#define AE_STAT_BLK_SIZE             ALIGN(AE_STAT_BLK_NUM, 2)
>>> +#define AE_BLOCK_WT_NUM              255
>>> +#define AE_BLK_WT_DATA_NUM_OF_GROUP  8
>>> +#define AWB_STAT_BLKH_NUM            32
>>> +#define AWB_STAT_BLKV_NUM            24
>>> +#define AWB_STAT_BLK_NUM             (AWB_STAT_BLKH_NUM * AWB_STAT_BLKV_NUM)
>>> +/* AWB stats block size need to be aligned with 2 */
>>> +#define AWB_STAT_BLK_SIZE            ALIGN(AWB_STAT_BLK_NUM, 2)
>>> +#define AWB_BLOCK_WT_NUM             768
>>> +#define AWB_BLK_WT_DATA_NUM_OF_GROUP 8
>>> +#define AWB_STAT_BLC20_NUM           4
>>> +#define AWB_STAT_GAIN10_NUM          4
>>> +#define BLC_OFFSET_NUM               5
>>> +#define GAMMA_LUT_GROUP_NUM          4
>>> +#define GAMMA_LUT_POINT_NUM          129
>>> +#define GAMMA_LUT_DATA_NUM_OF_GROUP  2
>>> +
>>> +/**
>>> + * struct awb_zone_stats - AWB statistics of a block
>>> + *
>>> + * AWB zone stats is aligned with 8 bytes
>>> + *
>>> + * @rg: the ratio of R / G in a zone
>>> + * @bg: the ratio of B / G in a zone
>>> + * @pixel_sum: the total number of pixels used in a zone
>>> + */
>> The documentation throughout this file is much improved
>
>
> Thanks.
>
>>> +struct awb_zone_stats {
>>> +     u16 rg;
>>> +     u16 bg;
>>> +     u32 pixel_sum;
>>> +};
>>
>>
>> __u8/__u16/__u32 for userspace API headers
>>
>
> Will add "include <linux/types.h>" and use __8/__u16/__u32.
>
>>> +
>>> +/**
>>> + * struct awb_stats_info - Auto white balance statistics information.
>>> + *
>>> + * AWB statistical information of all blocks.
>>> + *
>>> + * @awb_stats: array of auto white balance statistics
>>> + */
>>> +struct awb_stats_info {
>>> +     struct awb_zone_stats awb_stats[AWB_STAT_BLK_SIZE];
>>> +};
>>> +
>>> +/**
>>> + * struct ae_zone_stats - AE statistics of a block
>>> + *
>>> + * AE zone stats is aligned with 8 bytes.
>>> + * This is a 5-bin histogram and the total sum is
>>> + * normalized to 0xffff.
>>> + * So hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4)
>>> + *
>>> + * @hist0: the global normalized pixel count for bin 0
>>> + * @hist1: the global normalized pixel count for bin 1
>>> + * @hist3: the global normalized pixel count for bin 3
>>> + * @hist4: the global normalized pixel count for bin 4
>>> + */
>>> +struct ae_zone_stats {
>>> +     u16 hist0;
>>> +     u16 hist1;
>>> +     u16 hist3;
>>> +     u16 hist4;
>>> +};
>>> +
>>> +/**
>>> + * struct ae_stats_info - Exposure statistics information
>>> + *
>>> + * AE statistical information consists of
>>> + * all blocks information and a 1024-bin histogram.
>>> + *
>>> + * @ae_stats: array of auto exposure block statistics
>>> + * @hist: a 1024-bin histogram for the entire image
>>> + */
>>> +struct ae_stats_info {
>>> +     struct ae_zone_stats ae_stats[AE_STAT_BLK_SIZE];
>>> +     u32 hist[AE_HISTOGRAM_SIZE];
>>> +};
>>> +
>>> +/**
>>> + * struct af_zone_stats - AF statistics of a block
>>> + *
>>> + * AF block stats is aligned with 8 bytes.
>>> + * The zonal accumulated contrast metrics are stored
>>> + * in floating point format with 16 bits mantissa and
>>> + * 5 or 6 bits exponent.
>>> + * Apart from contrast metrics we accumulate squared image and
>>> + * quartic image data over the zone.
>>> + *
>>> + * @i2_mat: the mantissa of zonal squared image pixel sum
>>> + * @i4_mat: the mantissa of zonal quartic image pixel sum
>>> + * @e4_mat: the mantissa of zonal multi-directional quartic edge sum
>>> + * @i2_exp: the exponent of zonal squared image pixel sum
>>> + * @i4_exp: the exponent of zonal quartic image pixel sum
>>> + * @e4_exp: the exponent of zonal multi-directional quartic edge sum
>>> + */
>>> +struct af_zone_stats {
>>> +     u16 i2_mat;
>>> +     u16 i4_mat;
>>> +     u16 e4_mat;
>>> +     u16 i2_exp: 5;
>>> +     u16 i4_exp: 6;
>>> +     u16 e4_exp: 5;
>>> +};
>>> +
>>> +/**
>>> + * struct af_stats_info - Auto Focus statistics information
>>> + *
>>> + * AF statistical information of each block
>>> + *
>>> + * @af_stats: array of auto focus block statistics
>>> + */
>>> +struct af_stats_info {
>>> +     struct af_zone_stats af_stats[AF_STAT_BLK_SIZE];
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_stats_info - V4L2_META_FMT_C3ISP_STATS
>>> + *
>>> + * Contains ISP statistics
>>> + *
>>> + * @awb_stats: auto white balance stats
>>> + * @ae_stats: auto exposure stats
>>> + * @af_stats: auto focus stats
>>> + */
>>> +struct c3_isp_stats_info {
>>> +     struct awb_stats_info awb_stats;
>>> +     struct ae_stats_info ae_stats;
>>> +     struct af_stats_info af_stats;
>>> +};
>>> +
>>> +/**
>>> + * enum c3_isp_param_buffer_version -  C3 ISP parameters block versioning
>>> + *
>>> + * @C3_ISP_PARAM_BUFFER_V0: First version of C3 ISP parameters block
>>> + */
>>> +enum c3_isp_param_buffer_version {
>>> +     C3_ISP_PARAM_BUFFER_V0,
>>> +};
>>> +
>>> +/**
>>> + * enum c3_isp_param_block_type - Enumeration of C3 ISP parameter blocks
>>> + *
>>> + * Each block configures a specific processing block of the C3 ISP.
>>> + * The block type allows the driver to correctly interpret
>>> + * the parameters block data.
>>> + *
>>> + * @C3_ISP_PARAM_BLOCK_WB_CHANGE: White balance change parameters
>>> + * @C3_ISP_PARAM_BLOCK_WB_LUMA: White balance Luma-based parameters
>>> + * @C3_ISP_PARAM_BLOCK_WB_TRIANGLE: White balance triangle parameters
>>> + * @C3_ISP_PARAM_BLOCK_AWB_STATS: AWB statistics parameters
>>> + * @C3_ISP_PARAM_BLOCK_AE_STATS: AE statistics parameters
>>> + * @C3_ISP_PARAM_BLOCK_AF_STATS: AF statistics parameters
>>> + * @C3_ISP_PARAM_BLOCK_PST_GAMMA: post gamma parameters
>>> + * @C3_ISP_PARAM_BLOCK_DMSC: demosaic parameters
>>> + * @C3_ISP_PARAM_BLOCK_CCM: Color correction matrix parameters
>>> + * @C3_ISP_PARAM_BLOCK_CSC: Color space conversion parameters
>>> + * @C3_ISP_PARAM_BLOCK_BLC: Black level correction parameters
>>> + * @C3_ISP_PARAM_BLOCK_SENTINEL: First non-valid block index
>>> + */
>>> +enum c3_isp_param_block_type {
>>> +     C3_ISP_PARAM_BLOCK_WB_CHANGE,
>>> +     C3_ISP_PARAM_BLOCK_WB_LUMA,
>>> +     C3_ISP_PARAM_BLOCK_WB_TRIANGLE,
>>> +     C3_ISP_PARAM_BLOCK_AWB_STATS,
>>> +     C3_ISP_PARAM_BLOCK_AE_STATS,
>>> +     C3_ISP_PARAM_BLOCK_AF_STATS,
>>> +     C3_ISP_PARAM_BLOCK_PST_GAMMA,
>>> +     C3_ISP_PARAM_BLOCK_DMSC,
>>> +     C3_ISP_PARAM_BLOCK_CCM,
>>> +     C3_ISP_PARAM_BLOCK_CSC,
>>> +     C3_ISP_PARAM_BLOCK_BLC,
>>> +     C3_ISP_PARAM_BLOCK_SENTINEL
>>> +};
>>> +
>>> +/**
>>> + * struct c3_isp_param_block_header - C3 ISP parameter block header
>>> + *
>>> + * This structure represents the common part of all the ISP configuration
>>> + * blocks. Each parameters block shall embed an instance of this structure type
>>> + * as its first member, followed by the block-specific configuration data. The
>>> + * driver inspects this common header to discern the block type and its size and
>>> + * properly handle the block content by casting it to the correct block-specific
>>> + * type.
>>> + *
>>> + * @type: The parameters block type (enum c3_isp_param_block_type)
>>> + * @enabled: Block enabled/disabled flag
>>> + * @size: Size (in bytes) of parameters block
>>> + */
>>> +
>>> +struct c3_isp_param_block_header {
>>> +     enum c3_isp_param_block_type type;
>>> +     bool enabled;
>>> +     size_t size;
>>> +};
>> Stick to types with strict size definitions; __u16 type, __u32 size and so on. Some feedback from
>> the mali-c55 series was to switch to a "flags" field rather than an enabled bool and use BIT(0) as
>> the enable flag; more flexibility for future expansion.
>
>
> Will refer to the mali-c55 patches.
>
>>> +
>>> +/**
>>> + * struct wb_change_cfg - White Balance configuration
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @wb_gain: white balance gain of each color
>>> + *   wb_gain[0]: Gr gain, range 0~0xfff
>>> + *   wb_gain[1]: R gain, range 0~0xfff
>>> + *   wb_gain[2]: B gain, range 0~0xfff
>>> + *   wb_gain[3]: Gb gain, range 0~0xfff
>>> + *   wb_gain[4]: Ir gain, range 0~0xfff
>>> + * @wb_limit: white balance limit of each color
>>> + *   wb_limit[0]: Gr limit, 16 bits float
>>> + *   wb_limit[1]: R limit, 16 bits float
>>> + *   wb_limit[2]: B limit, 16 bits float
>>> + *   wb_limit[3]: Gb limit, 16 bits float
>>> + *   wb_limit[4]: Ir limit, 16 bits float
>>> + * @ae_gain_grbgi: Gain of each color before blending to luma
>>> + *   ae_gain_grbgi[0]: Gr gain, range 0~255
>>> + *   ae_gain_grbgi[1]: R gain, range 0~255
>>> + *   ae_gain_grbgi[2]: B gain, range 0~255
>>> + *   ae_gain_grbgi[3]: Gb gain, range 0~255
>>> + *   ae_gain_grbgi[4]: Ir gain, range 0~255
>>> + * @ae_bl12_grbgi: Offset of each color before ae_gain_grbgi
>>> + *   ae_bl12_grbgi[0]: Gr offset, range 0~4095
>>> + *   ae_bl12_grbgi[1]: R offset, range 0~4095
>>> + *   ae_bl12_grbgi[2]: B offset, range 0~4095
>>> + *   ae_bl12_grbgi[3]: Gb offset, range 0~4095
>>> + *   ae_bl12_grbgi[4]: Ir offset, range 0~4095
>>> + */
>> Much nicer. Could you mention which value from enum c3_isp_param_block_type this struct pertains to
>> though please?
>
>
> OK, will add the block type of this struct.
>
>>> +struct wb_change_cfg {
>> c3_isp_ prefix please
>
>
> Will add "c3_isp_" prefix
>
>>> +     struct c3_isp_param_block_header header;
>>> +     u32 wb_gain[5];
>>> +     u32 wb_limit[5];
>>> +     u32 ae_gain_grbgi[5];
>>> +     u32 ae_bl12_grbgi[5];
>>> +};
>>> +
>>> +/**
>>> + * struct wb_luma_cfg - White Balance Luma-based configuration
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @awb_stat_blc20: BLC in AWB statistic
>>> + *   awb_stat_blc20[0]: Gr blc, range 0~0xfffff
>>> + *   awb_stat_blc20[1]: R blc, range 0~0xfffff
>>> + *   awb_stat_blc20[2]: B blc, range 0~0xfffff
>>> + *   awb_stat_blc20[3]: Gb blc, range 0~0xfffff
>>> + * @awb_stat_gain10: Gain in AWB statistic
>>> + *   awb_stat_gain10[0]: Gr gain, range 0~1023
>>> + *   awb_stat_gain10[1]: R gain, range 0~1023
>>> + *   awb_stat_gain10[2]: B gain, range 0~1023
>>> + *   awb_stat_gain10[3]: Gb gain, range 0~1023
>>> + * @awb_stat_satur_low: AWB statistic under-saturation threshold
>>> + *   value: range 0~65535
>>> + * @awb_stat_satur_high: AWB statistic over-saturation threshold
>>> + *   value: range 0~65535
>>> + */
>>> +struct wb_luma_cfg {
>>> +     struct c3_isp_param_block_header header;
>>> +     u32 awb_stat_blc20[AWB_STAT_BLC20_NUM];
>>> +     u32 awb_stat_gain10[AWB_STAT_GAIN10_NUM];
>>> +     u32 awb_stat_satur_low;
>>> +     u32 awb_stat_satur_high;
>>> +};
>>> +
>>> +/**
>>> + * struct wb_triangle_cfg - White Balance Triangle
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @awb_stat_satur_vald: AWB statistic over saturation control
>>> + *   value: 0: disable, 1: enable
>>> + * @awb_stat_rg_min: min value of r/g
>>> + *   value: 0~4095
>>> + * @awb_stat_rg_max: max value of r/g
>>> + *   value: 0~4095
>>> + * @awb_stat_bg_min: min value of b/g
>>> + *   value: 0~4095
>>> + * @awb_stat_bg_max: max value of b/g
>>> + *   value: 0~4095
>>> + * @awb_stat_rg_low: low value of r/g
>>> + *   value: 0~4095
>>> + * @awb_stat_rg_high: high value of r/g
>>> + *   value: 0~4095
>>> + * @awb_stat_bg_low: low value of b/g
>>> + *   value: 0~4095
>>> + * @awb_stat_bg_high: high value of b/g
>>> + *   value: 0~4095
>>> + */
>>> +struct wb_triangle_cfg {
>>> +     struct c3_isp_param_block_header header;
>>> +     u32 awb_stat_satur_vald;
>>> +     u32 awb_stat_rg_min;
>>> +     u32 awb_stat_rg_max;
>>> +     u32 awb_stat_bg_min;
>>> +     u32 awb_stat_bg_max;
>>> +     u32 awb_stat_rg_low;
>>> +     u32 awb_stat_rg_high;
>>> +     u32 awb_stat_bg_low;
>>> +     u32 awb_stat_bg_high;
>>> +};
>>> +
>>> +/**
>>> + * struct awb_stats_cfg - AWB statistics configuration
>>> + *
>>> + * This structure contains AWB statistics control information.
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @awb_stat_switch: the switch of AWB statistics
>>> + *   value: 0~7
>> I assume this switch controls where in the ISP pipeline the values are drawn from? Could there be an
>> enumeration that documents those 8 values? See for example "enum mali_c55_aexp_hist_tap_points".
>> Same comments for the other switches below
>
>
>
> OK, will add an enumeration and refer to "enum mali_c55_aexp_hist_tap_points".
>
>>> + * @awb_stat_blk_weight: Array of weights for AWB statistics blocks
>>> + *   value: 0~15
>>> + */
>>> +struct awb_stats_cfg {
>>> +     struct c3_isp_param_block_header header;
>>> +     u8 awb_stat_switch;
>>> +     u32 awb_stat_blk_weight[AWB_BLOCK_WT_NUM];
>>> +};
>>> +
>>> +/**
>>> + * struct ae_stats_cfg - AE statistics configuration
>>> + *
>>> + * This structure contains AE statistics control information.
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @ae_stat_switch: the switch of AE statistics
>>> + *   value: 0~3
>>> + * @ae_stat_blk_weight: Array of weights for AE statistics blocks
>>> + *   value: 0~15
>>> + */
>>> +struct ae_stats_cfg {
>>> +     struct c3_isp_param_block_header header;
>>> +     u8 ae_stat_switch;
>>> +     u32 ae_stat_blk_weight[AE_BLOCK_WT_NUM];
>>> +};
>>> +
>>> +/**
>>> + * struct af_stats_cfg - AF statistics configuration
>>> + *
>>> + * This structure contains AF statistics control information.
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @af_stat_switch: the switch of AF statistics
>>> + *   value: 0~3
>>> + */
>>> +struct af_stats_cfg {
>>> +     struct c3_isp_param_block_header header;
>>> +     u8 af_stat_switch;
>>> +};
>>> +
>>> +/**
>>> + * struct pst_gamma_cfg - Post gamma configuration
>>> + *
>>> + * This structure contains post gamma parameters
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @pst_gamma_lut: LUT for P-Stitch gamma
>>> + *   value: 0~65535
>>> + */
>>> +struct pst_gamma_cfg {
>>> +     struct c3_isp_param_block_header header;
>>> +     u32 pst_gamma_lut[GAMMA_LUT_GROUP_NUM][GAMMA_LUT_POINT_NUM];
>>> +};
>>> +
>>> +/**
>>> + * struct dmsc_cfg - Demosaic configuration
>>> + *
>>> + * This structure contains demosaic parameters
>> You don't need the empty struct if all you're really after is the enable / disable field (or ideally
>> flag) - just pass in a header block and ensure that the driver handles that correctly
>
>
> OK, will remove this empty struct and only use a header block.
>
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + */
>>> +struct dmsc_cfg {
>>> +     struct c3_isp_param_block_header header;
>>> +};
>>> +
>>> +/**
>>> + * struct ccm_cfg - ISP CCM configuration
>>> + *
>>> + * This structure holds the parameters for configuring the CCM,
>>> + * which is used for color correction.
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @ccm_4x3matrix: A 3x4 matrix used for color correction
>>> + *   value: 0~8191
>>> + */
>>> +struct ccm_cfg {
>>> +     struct c3_isp_param_block_header header;
>>> +     u32 ccm_4x3matrix[3][4];
>>> +};
>>> +
>>> +/**
>>> + * struct csc_cfg - ISP Color Space Conversion configuration
>>> + *
>>> + * This structure contains settings for color space conversion.
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @cm0_offset_inp: Input offset values for the 0-order color matrix
>>> + *   value: 0~8191
>>> + * @cm0_offset_oup: Output offset values for the 0-order color matrix
>>> + *   value: 0~8191
>>> + * @cm0_3x3mtrx_rs: matrix right shift for cm0
>>> + *   value: 0~3
>>> + * @cm0_3x3matrix: A 3x3 matrix used for the color matrix
>>> + *   value: 0~8191
>>> + */
>>> +struct csc_cfg {
>>> +     struct c3_isp_param_block_header header;
>>> +     u32 cm0_offset_inp[3];
>>> +     u32 cm0_offset_oup[3];
>>> +     u32 cm0_3x3mtrx_rs;
>>> +     u32 cm0_3x3matrix[3][3];
>>> +};
>>> +
>>> +/**
>>> + * struct blc_cfg - ISP Black Level Correction (BLC) configuration
>>> + *
>>> + * This structure holds the parameters for BLC in image processing.
>>> + *
>>> + * @header: The C3 ISP parameters block header
>>> + * @fe_bl_ofst: Array of front-end BLC offsets for each color each channels
>>> + *   fe_bl_ofst[0]: Gr blc offset, range 0~0x1fffff
>>> + *   fe_bl_ofst[1]: R blc offset, range 0~0x1fffff
>>> + *   fe_bl_ofst[2]: B blc offset, range 0~0x1fffff
>>> + *   fe_bl_ofst[3]: Gb blc offset, range 0~0x1fffff
>>> + *   fe_bl_ofst[4]: Ir blc offset, range 0~0x1fffff
>>> + * @blc_ofst: Array of LSWB BLC offsets
>> LSWB?
>
>
> LSWB: Lens shading and white balance
>
> This blc offset takes effect before lens shading.
>
> Will add new explanation.
>
>>> + *   blc_ofst[0]: Gr blc offset, 16 bits float
>>> + *   blc_ofst[1]: R blc offset, 16 bits float
>>> + *   blc_ofst[2]: B blc offset, 16 bits float
>>> + *   blc_ofst[3]: Gb blc offset, 16 bits float
>>> + *   blc_ofst[4]: Ir blc offset, 16 bits float
>>> + */
>> If it's a 16 bit field, how come the member is a u32 array?
>
>
> Will use a 16 bit field.
>
>>> +struct blc_cfg {
>>> +     struct c3_isp_param_block_header header;
>>> +     u32 fe_bl_ofst[BLC_OFFSET_NUM];
>>> +     u32 blc_ofst[BLC_OFFSET_NUM];
>>> +};
>>> +
>>> +/**
>>> + * define C3_ISP_PARAMS_MAX_SIZE - Maximum size of all C3 ISP Parameters
>>> + *
>>> + * Though the parameters for the C3 ISP are passed as optional blocks, the
>>> + * driver still needs to know the absolute maximum size so that it can allocate
>>> + * a buffer sized appropriately to accommodate userspace attempting to set all
>>> + * possible parameters in a single frame.
>>> + */
>>> +#define C3_ISP_PARAMS_MAX_SIZE                 \
>>> +     (sizeof(struct wb_change_cfg) +        \
>>> +     sizeof(struct wb_luma_cfg)   +         \
>>> +     sizeof(struct wb_triangle_cfg) +       \
>>> +     sizeof(struct awb_stats_cfg) +          \
>>> +     sizeof(struct ae_stats_cfg) +           \
>>> +     sizeof(struct af_stats_cfg) +           \
>>> +     sizeof(struct pst_gamma_cfg) +         \
>>> +     sizeof(struct dmsc_cfg) +              \
>>> +     sizeof(struct ccm_cfg) +               \
>>> +     sizeof(struct csc_cfg) +               \
>>> +     sizeof(struct blc_cfg))
>>> +
>>> +/**
>>> + * struct c3_isp_params_buffer - C3 ISP configuration parameters
>>> + *
>>> + * This struct contains the configuration parameters of the C3 ISP
>>> + * algorithms, serialized by userspace into an opaque data buffer. Each
>>> + * configuration parameter block is represented by a block-specific structure
>>> + * which contains a :c:type:`c3_isp_param_block_header` entry as first
>>> + * member. Userspace populates the @data buffer with configuration parameters
>>> + * for the blocks that it intends to configure. As a consequence, the data
>>> + * buffer effective size changes according to the number of ISP blocks that
>>> + * userspace intends to configure.
>>> + *
>>> + * The parameters buffer is versioned by the @version field to allow modifying
>>> + * and extending its definition. Userspace should populate the @version field to
>>> + * inform the driver about the version it intends to use. The driver will parse
>>> + * and handle the @data buffer according to the data layout specific to the
>>> + * indicated revision and return an error if the desired revision is not
>>> + * supported.
>>> + *
>>> + * For each ISP block that userspace wants to configure, a block-specific
>>> + * structure is appended to the @data buffer, one after the other without gaps
>>> + * in between nor overlaps. Userspace shall populate the @total_size field with
>>> + * the effective size, in bytes, of the @data buffer.
>>> + *
>>> + * The expected memory layout of the parameters buffer is::
>>> + *
>>> + *   +-------------------- struct c3_isp_params_buffer ------------------+
>>> + *   | version = C3_ISP_PARAM_BUFFER_V0;                                   |
>>> + *   | total_size = sizeof(sizeof(struct wb_change_cfg))                   |
>>> + *   |              sizeof(sizeof(struct wb_luma_cfg));                    |
>>> + *   | +------------------------- data ---------------------------------+ |
>>> + *   | | +------------------ struct wb_change_cfg) --------------------+ | |
>>> + *   | | | +---------  struct c3_isp_param_block_header header -----+ | | |
>>> + *   | | | | type = C3_ISP_PARAM_BLOCK_WB_CHANGE;                    | | | |
>>> + *   | | | | enabled = true;                                            | | | |
>>> + *   | | | | size =                                                  | | | |
>>> + *   | | | |    sizeof(struct c3_isp_param_block_header header);     | | | |
>>> + *   | | | +---------------------------------------------------------+ | | |
>>> + *   | | | wb_gain[5] = ...;                                           | | |
>>> + *   | | | wb_limit[5] = ...;                                          | | |
>>> + *   | | | ae_gain_grbgi[5] = ...;                                     | | |
>>> + *   | | | ae_bl12_grbgi[5] = ...;                                     | | |
>>> + *   | | +------------------ struct wb_luma_cfg -----------------------+ | |
>>> + *   | | | +---------- struct c3_isp_param_block_header header ------+ | | |
>>> + *   | | | | type = C3_ISP_PARAM_BLOCK_WB_LUMA;                      | | | |
>>> + *   | | | | enabled = true;                                            | | | |
>>> + *   | | | | size = sizeof(struct wb_luma_cfg);                      | | | |
>>> + *   | | | +---------------------------------------------------------+ | | |
>>> + *   | | | awb_stat_blc20[4] = ...;                                    | | |
>>> + *   | | | awb_stat_gain10[4] = ...;                                   | | |
>>> + *   | | | awb_stat_satur_low = ...;                                   | | |
>>> + *   | | | awb_stat_satur_high = ...;                                  | | |
>>> + *   | | +-------------------------------------------------------------+ | |
>>> + *   | +-----------------------------------------------------------------+ |
>>> + * +---------------------------------------------------------------------+
>>> + *
>>> + * @version: The C3 ISP parameters buffer version
>>> + * @total_size: The C3 ISP configuration data effective size,
>>> + *           excluding this header
>>> + * @data: The C3 ISP configuration blocks data
>>> + */
>>> +struct c3_isp_params_buffer {
>>> +     enum c3_isp_param_buffer_version version;
>>> +     size_t total_size;
>>> +     u8 data[C3_ISP_PARAMS_MAX_SIZE];
>>> +};
>>> +
>>> +#endif
>>>

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

end of thread, other threads:[~2024-11-12 11:41 UTC | newest]

Thread overview: 37+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-09-18  6:07 [PATCH v3 0/9] Amlogic C3 ISP support Keke Li via B4 Relay
2024-09-18  6:07 ` [PATCH v3 1/9] dt-bindings: media: Add amlogic,c3-mipi-csi2.yaml Keke Li via B4 Relay
2024-09-18  6:07 ` [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver Keke Li via B4 Relay
2024-11-04 17:50   ` Jacopo Mondi
2024-11-05 10:06     ` Jacopo Mondi
2024-11-05 11:40       ` Keke Li
2024-11-05 11:21     ` Keke Li
2024-11-05 11:39       ` Jacopo Mondi
2024-11-05 11:58         ` Keke Li
2024-11-05 12:08         ` Tomi Valkeinen
2024-11-06  2:10           ` Keke Li
2024-11-05  8:21   ` Jacopo Mondi
2024-11-05 11:36     ` Keke Li
2024-09-18  6:07 ` [PATCH v3 3/9] dt-bindings: media: Add amlogic,c3-mipi-adapter.yaml Keke Li via B4 Relay
2024-09-18  6:07 ` [PATCH v3 4/9] media: platform: Add c3 mipi adapter driver Keke Li via B4 Relay
2024-11-05 11:24   ` Jacopo Mondi
2024-11-06  5:32     ` Keke Li
2024-11-07  8:48       ` Jacopo Mondi
2024-11-08  7:48         ` Keke Li
2024-09-18  6:07 ` [PATCH v3 5/9] dt-bindings: media: Add amlogic,c3-isp.yaml Keke Li via B4 Relay
2024-09-21  0:32   ` Rob Herring (Arm)
2024-09-18  6:07 ` [PATCH v3 6/9] media: Add C3ISP_PARAMS and C3ISP_STATS meta formats Keke Li via B4 Relay
2024-09-18  6:07 ` [PATCH v3 7/9] media: platform: Add c3 ISP driver Keke Li via B4 Relay
2024-11-07 16:03   ` Jacopo Mondi
2024-11-07 16:15     ` Jacopo Mondi
2024-11-08 12:38       ` Keke Li
2024-11-08 12:34     ` Keke Li
2024-11-08 14:04       ` Jacopo Mondi
2024-11-11  3:00         ` Keke Li
2024-11-11  7:50           ` Jacopo Mondi
2024-11-08 10:47   ` Dan Scally
2024-11-11  9:05     ` Keke Li
2024-11-12 11:41       ` Dan Scally
2024-09-18  6:07 ` [PATCH v3 8/9] Documentation: media: add documentation file metafmt-c3-isp.rst Keke Li via B4 Relay
2024-11-07 10:38   ` Jacopo Mondi
2024-09-18  6:07 ` [PATCH v3 9/9] Documentation: media: add documentation file c3-isp.rst Keke Li via B4 Relay
2024-11-07  9:07   ` Jacopo Mondi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).