Devicetree
 help / color / mirror / Atom feed
* [PATCH v2 0/3] media: Add bindings and driver for Sony IMX708
@ 2026-08-07 12:55 Jai Luthra
  2026-08-07 12:55 ` [PATCH v2 1/3] dt-bindings: media: i2c: Add imx708 image sensor Jai Luthra
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Jai Luthra @ 2026-08-07 12:55 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Sakari Ailus
  Cc: Dave Stevenson, Naushir Patuck, David Plowman, Kieran Bingham,
	Laurent Pinchart, linux-media, devicetree,
	Raspberry Pi Kernel Maintenance, Jai Luthra, Krzysztof Kozlowski,
	Nick Hollinghurst

Hi,

This series adds dt-bindings and a basic V4L2 driver for Sony IMX708
camera sensor, used in Raspberry Pi Camera Module 3 [1].

This basic driver is an adaptation of the one present in the downstream
rpi-6.18.y tree (written by Nick Hollinghurst). Many features from the
downstream driver (like HDR, binned and 720p crop modes, etc.) are not
present here, and will be added on top using common raw sensor model in
a later series.

This has been tested on a Raspberry Pi 5 with libcamera.

[1]: https://www.raspberrypi.com/products/camera-module-3/

Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
---
Changes in v2:
PATCH 1/3:
    - Drop the |- before the description
    - Add Dave as M:, and move kernel-list@raspberrypi.com as R:
    - Add Krzysztof's R-by tag
PATCH 2/3:
    - New in v2, to support reuse of CCS PLL helpers with IMX708.
PATCH 3/3:
    - Use the helpers from ccs-pll.h to calculate the VT and OP clock tree,
      setting up the limits following the datasheet instead of the CCS limit
      registers
    - Keep the same VT Pixel Rate and OP Link Rate as before. The VT tree
      runs much faster than the 2-Lane CSI link, and we don't have enough
      details about the FIFO capacity to support freely-configurable rates
      just yet.
    - Drop macros of CCS registers, and include ccs-regs.h to use the
      existing names
    - Update Copyright and MODULE_AUTHOR entries

- Link to v1: https://lore.kernel.org/r/20260715-imx708-v1-0-1f36f54e14d4@ideasonboard.com

---
Jai Luthra (3):
      dt-bindings: media: i2c: Add imx708 image sensor
      media: ccs-pll: Support optional input for VT pixel rate
      media: i2c: Add a driver for Sony IMX708

 .../devicetree/bindings/media/i2c/sony,imx708.yaml |  120 ++
 MAINTAINERS                                        |    9 +
 drivers/media/i2c/Kconfig                          |   12 +
 drivers/media/i2c/Makefile                         |    1 +
 drivers/media/i2c/ccs-pll.c                        |   13 +-
 drivers/media/i2c/ccs-pll.h                        |    4 +-
 drivers/media/i2c/imx708.c                         | 1312 ++++++++++++++++++++
 7 files changed, 1468 insertions(+), 3 deletions(-)
---
base-commit: 8a6e017c06fbc1b96af618e82a4d35889d442964
change-id: 20260715-imx708-967d68ca6ca8

Best regards,
-- 
Jai Luthra <jai.luthra@ideasonboard.com>


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

* [PATCH v2 1/3] dt-bindings: media: i2c: Add imx708 image sensor
  2026-08-07 12:55 [PATCH v2 0/3] media: Add bindings and driver for Sony IMX708 Jai Luthra
@ 2026-08-07 12:55 ` Jai Luthra
  2026-08-07 13:04   ` sashiko-bot
  2026-08-07 12:55 ` [PATCH v2 2/3] media: ccs-pll: Support optional input for VT pixel rate Jai Luthra
  2026-08-07 12:55 ` [PATCH v2 3/3] media: i2c: Add a driver for Sony IMX708 Jai Luthra
  2 siblings, 1 reply; 7+ messages in thread
From: Jai Luthra @ 2026-08-07 12:55 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Sakari Ailus
  Cc: Dave Stevenson, Naushir Patuck, David Plowman, Kieran Bingham,
	Laurent Pinchart, linux-media, devicetree,
	Raspberry Pi Kernel Maintenance, Jai Luthra, Krzysztof Kozlowski

Sony IMX708 is an 11.9 Megapixel CMOS sensor, that can output pixels
over MIPI CSI-2 bus. Add bindings for it.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
---
Changes in v2:
- Drop the |- before the description
- Add Dave as M:, and move kernel-list@raspberrypi.com as R:
- Add Krzysztof's R-by tag
---
 .../devicetree/bindings/media/i2c/sony,imx708.yaml | 120 +++++++++++++++++++++
 MAINTAINERS                                        |   8 ++
 2 files changed, 128 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml
new file mode 100644
index 000000000000..e41b3ca53706
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml
@@ -0,0 +1,120 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/sony,imx708.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sony IMX708 Sensor
+
+maintainers:
+  - Jai Luthra <jai.luthra@ideasonboard.com>
+  - Dave Stevenson <dave.stevenson@raspberrypi.com>
+
+description:
+  The Sony IMX708 is a 1/2.3-inch CMOS active pixel digital image sensor
+  with an active array size of 4608H x 2592V. It is programmable through
+  I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet.
+  Image data is sent through MIPI CSI-2, which is configured as either 2 or
+  4 data lanes.
+
+properties:
+  compatible:
+    const: sony,imx708
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  vdig-supply:
+    description:
+      Digital I/O voltage supply, 1.1 volts
+
+  vana1-supply:
+    description:
+      Analog1 voltage supply, 2.8 volts
+
+  vana2-supply:
+    description:
+      Analog2 voltage supply, 1.8 volts
+
+  vddl-supply:
+    description:
+      Digital core voltage supply, 1.8 volts
+
+  reset-gpios:
+    description: Sensor RESETN (XCLR) GPIO
+    maxItems: 1
+
+  port:
+    $ref: /schemas/graph.yaml#/$defs/port-base
+    additionalProperties: false
+
+    properties:
+      endpoint:
+        $ref: /schemas/media/video-interfaces.yaml#
+        unevaluatedProperties: false
+
+        properties:
+          data-lanes:
+            oneOf:
+              - items:
+                  - const: 1
+                  - const: 2
+              - items:
+                  - const: 1
+                  - const: 2
+                  - const: 3
+                  - const: 4
+
+        required:
+          - data-lanes
+          - link-frequencies
+
+    required:
+      - endpoint
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - vdig-supply
+  - vana1-supply
+  - vana2-supply
+  - vddl-supply
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        camera@1a {
+            compatible = "sony,imx708";
+            reg = <0x1a>;
+
+            clocks = <&clk 90>;
+
+            vdig-supply = <&camera_vdig>;
+            vana1-supply = <&camera_vana1>;
+            vana2-supply = <&camera_vana2>;
+            vddl-supply = <&camera_vddl>;
+
+            reset-gpios = <&gpio 35 GPIO_ACTIVE_LOW>;
+
+            port {
+                imx708_ep: endpoint {
+                    data-lanes = <1 2>;
+                    link-frequencies = /bits/ 64 <450000000>;
+                    remote-endpoint = <&csi_ep>;
+                };
+            };
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 3785b8c1de0a..9928f68175ed 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25281,6 +25281,14 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/media/i2c/sony,imx678.yaml
 F:	drivers/media/i2c/imx678.c
 
+SONY IMX708 SENSOR DRIVER
+M:	Dave Stevenson <dave.stevenson@raspberrypi.com>
+M:	Jai Luthra <jai.luthra@ideasonboard.com>
+R:	Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml
+
 SONY MEMORYSTICK SUBSYSTEM
 M:	Maxim Levitsky <maximlevitsky@gmail.com>
 M:	Alex Dubov <oakad@yahoo.com>

-- 
2.54.0


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

* [PATCH v2 2/3] media: ccs-pll: Support optional input for VT pixel rate
  2026-08-07 12:55 [PATCH v2 0/3] media: Add bindings and driver for Sony IMX708 Jai Luthra
  2026-08-07 12:55 ` [PATCH v2 1/3] dt-bindings: media: i2c: Add imx708 image sensor Jai Luthra
@ 2026-08-07 12:55 ` Jai Luthra
  2026-08-07 13:15   ` sashiko-bot
  2026-08-07 12:55 ` [PATCH v2 3/3] media: i2c: Add a driver for Sony IMX708 Jai Luthra
  2 siblings, 1 reply; 7+ messages in thread
From: Jai Luthra @ 2026-08-07 12:55 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Sakari Ailus
  Cc: Dave Stevenson, Naushir Patuck, David Plowman, Kieran Bingham,
	Laurent Pinchart, linux-media, devicetree,
	Raspberry Pi Kernel Maintenance, Jai Luthra

When the PLL is configured in DUAL_MODE (separate VT and OP trees) and
the sensor's FIFOs allow derating and/or overrating, the VT pixel rate
might be higher or lower than OP pixel rate.

Support such usecases by allowing an optional input for the target VT
pixel rate.

Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
---
 drivers/media/i2c/ccs-pll.c | 13 +++++++++++--
 drivers/media/i2c/ccs-pll.h |  4 +++-
 2 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/drivers/media/i2c/ccs-pll.c b/drivers/media/i2c/ccs-pll.c
index 1605cfa5db19..9aa164994cc7 100644
--- a/drivers/media/i2c/ccs-pll.c
+++ b/drivers/media/i2c/ccs-pll.c
@@ -388,6 +388,14 @@ __ccs_pll_calculate_vt_tree(struct device *dev,
 	return 0;
 }
 
+static inline u32 ccs_pll_vt_pixel_rate(const struct ccs_pll *pll)
+{
+	if (pll->pixel_rate_pixel_array)
+		return pll->pixel_rate_pixel_array;
+
+	return pll->pixel_rate_csi;
+}
+
 static int ccs_pll_calculate_vt_tree(struct device *dev,
 				     const struct ccs_pll_limits *lim,
 				     struct ccs_pll *pll)
@@ -396,11 +404,12 @@ static int ccs_pll_calculate_vt_tree(struct device *dev,
 	struct ccs_pll_branch_fr *pll_fr = &pll->vt_fr;
 	u16 min_pre_pll_clk_div = lim_fr->min_pre_pll_clk_div;
 	u16 max_pre_pll_clk_div = lim_fr->max_pre_pll_clk_div;
+	u32 vt_pixel_rate = ccs_pll_vt_pixel_rate(pll);
 	u32 pre_mul, pre_div;
 
-	pre_div = gcd(pll->pixel_rate_csi,
+	pre_div = gcd(vt_pixel_rate,
 		      pll->ext_clk_freq_hz * pll->vt_lanes);
-	pre_mul = pll->pixel_rate_csi / pre_div;
+	pre_mul = vt_pixel_rate / pre_div;
 	pre_div = pll->ext_clk_freq_hz * pll->vt_lanes / pre_div;
 
 	/* Make sure PLL input frequency is within limits */
diff --git a/drivers/media/i2c/ccs-pll.h b/drivers/media/i2c/ccs-pll.h
index e22903931e72..5c00a72c41df 100644
--- a/drivers/media/i2c/ccs-pll.h
+++ b/drivers/media/i2c/ccs-pll.h
@@ -114,7 +114,9 @@ struct ccs_pll_branch_bk {
  * @op_bk: Operational timing back-end configuration (output)
  * @pixel_rate_csi: Pixel rate on the output data bus (output)
  * @pixel_rate_pixel_array: Nominal pixel rate in the sensor's pixel array
- *			    (output)
+ *			    (input/output). It is a valid input only when
+ *			    FLAG_DUAL_PLL is set, serving as a target rate
+ *			    for the VT tree.
  */
 struct ccs_pll {
 	/* input values */

-- 
2.54.0


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

* [PATCH v2 3/3] media: i2c: Add a driver for Sony IMX708
  2026-08-07 12:55 [PATCH v2 0/3] media: Add bindings and driver for Sony IMX708 Jai Luthra
  2026-08-07 12:55 ` [PATCH v2 1/3] dt-bindings: media: i2c: Add imx708 image sensor Jai Luthra
  2026-08-07 12:55 ` [PATCH v2 2/3] media: ccs-pll: Support optional input for VT pixel rate Jai Luthra
@ 2026-08-07 12:55 ` Jai Luthra
  2026-08-07 13:12   ` sashiko-bot
  2 siblings, 1 reply; 7+ messages in thread
From: Jai Luthra @ 2026-08-07 12:55 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Sakari Ailus
  Cc: Dave Stevenson, Naushir Patuck, David Plowman, Kieran Bingham,
	Laurent Pinchart, linux-media, devicetree,
	Raspberry Pi Kernel Maintenance, Jai Luthra, Nick Hollinghurst

Sony IMX708 is an 11.9 Megapixel CMOS sensor with a 16:9 aspect ratio.
It is a quad-bayer sensor with support for remosaicing the output to
a normal 2x2 bayer pattern.

This basic driver adds supports for capturing the full active-area of
4608x2592, up to 14.35fps. The following features are supported by the
driver:
- MIPI RAW10 output
- Fixed quad-bayer correction
- Multiple link frequencies (different IOP_PLL_MPY)
- VBLANK control for flexible frame-rate
- Analogue gain and exposure control
- Extremely long exposure time with LONG_EXP_SHIFT
- Test pattern support

The following features are currently not supported and may be added
later. Some of them depend on the new common raw sensor model:
- Embedded data stream
- Freely configurable crop (analog and digital)
- 2x2 binning
- Quad-bayer multi-exposure HDR support
- HBLANK control
- 4-lane support
- Configurable clock-tree
- Flexible quad-bayer correction

Co-developed-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
---
Changes in v2:
- Use the helpers from ccs-pll.h to calculate the VT and OP clock tree,
  setting up the limits following the datasheet instead of the CCS limit
  registers
- Keep the same VT Pixel Rate and OP Link Rate as before. The VT tree
  runs much faster than the 2-Lane CSI link, and we don't have enough
  details about the FIFO capacity to support freely-configurable rates
  just yet.
- Drop macros of CCS registers, and include ccs-regs.h to use the
  existing names
- Update Copyright and MODULE_AUTHOR entries
---
 MAINTAINERS                |    1 +
 drivers/media/i2c/Kconfig  |   12 +
 drivers/media/i2c/Makefile |    1 +
 drivers/media/i2c/imx708.c | 1312 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1326 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 9928f68175ed..7317bd852264 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25288,6 +25288,7 @@ R:	Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
 L:	linux-media@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml
+F:	drivers/media/i2c/imx708.c
 
 SONY MEMORYSTICK SUBSYSTEM
 M:	Maxim Levitsky <maximlevitsky@gmail.com>
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 5c52007f9cbe..38ec23eb6e38 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -310,6 +310,18 @@ config VIDEO_IMX678
 	  To compile this driver as a module, choose M here: the
 	  module will be called imx678.
 
+config VIDEO_IMX708
+	tristate "Sony IMX708 sensor support"
+	depends on GPIOLIB
+	select V4L2_CCI_I2C
+	select VIDEO_CCS_PLL
+	help
+	  This is a Video4Linux2 sensor driver for the Sony
+	  IMX708 camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called imx708.
+
 config VIDEO_MAX9271_LIB
 	tristate
 
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index d04bd5724552..15dc9755e321 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -62,6 +62,7 @@ obj-$(CONFIG_VIDEO_IMX355) += imx355.o
 obj-$(CONFIG_VIDEO_IMX412) += imx412.o
 obj-$(CONFIG_VIDEO_IMX415) += imx415.o
 obj-$(CONFIG_VIDEO_IMX678) += imx678.o
+obj-$(CONFIG_VIDEO_IMX708) += imx708.o
 obj-$(CONFIG_VIDEO_IMX471) += imx471.o
 obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o
 obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o
diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c
new file mode 100644
index 000000000000..7d5b8bdc2b43
--- /dev/null
+++ b/drivers/media/i2c/imx708.c
@@ -0,0 +1,1312 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A V4L2 driver for Sony IMX708
+ *
+ * Copyright (C) 2022-2026 Raspberry Pi Ltd
+ * Copyright (C) 2026 Ideas on Board Oy
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <media/media-entity.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-cci.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mediabus.h>
+#include <media/v4l2-subdev.h>
+
+#include "ccs/ccs-regs.h"
+#include "ccs-pll.h"
+
+#define IMX708_CHIP_ID			0x0708
+
+#define IMX708_INCLK_FREQ		24000000
+
+/* Pixel rate  */
+#define IMX708_PIXEL_RATE		595200000
+
+/* PLL */
+#define IMX708_REG_IVT_PXCK_DIV		CCI_REG8(0x0301)
+#define IMX708_REG_IVT_SYCK_DIV		CCI_REG8(0x0303)
+#define IMX708_REG_IVT_PREPLLCK_DIV	CCI_REG8(0x0305)
+#define IMX708_REG_IOP_PXCK_DIV		CCI_REG8(0x0309)
+#define IMX708_REG_IOP_SYCK_DIV		CCI_REG8(0x030b)
+#define IMX708_REG_IOP_PREPLLCK_DIV	CCI_REG8(0x030d)
+
+/* V_TIMING internal */
+#define IMX708_FRAME_LENGTH_MAX		0xffff
+#define IMX708_LINE_LENGTH		15648
+#define IMX708_VBLANK_MIN		58
+
+/* Imaging area */
+#define IMX708_REG_ACROPLP_EN		CCI_REG8(0x32df)
+#define IMX708_REG_BINNING_PRIORITY_H	CCI_REG8(0x3200)
+#define IMX708_REG_BINNING_PRIORITY_V	CCI_REG8(0x3201)
+
+/* Long exposure multiplier */
+#define IMX708_LONG_EXP_SHIFT_MAX	7
+#define IMX708_LONG_EXP_SHIFT_REG	CCI_REG8(0x3100)
+
+/* Exposure control */
+#define IMX708_EXPOSURE_OFFSET		48
+#define IMX708_EXPOSURE_DEFAULT		0x640
+#define IMX708_EXPOSURE_STEP		1
+#define IMX708_EXPOSURE_MIN		8
+
+/* Analog gain control */
+#define IMX708_ANA_GAIN_MIN		0
+#define IMX708_ANA_GAIN_MAX		960
+#define IMX708_ANA_GAIN_STEP		1
+#define IMX708_ANA_GAIN_DEFAULT	   IMX708_ANA_GAIN_MIN
+
+/* Digital gain control */
+#define IMX708_DGTL_GAIN_MIN		0x0100
+#define IMX708_DGTL_GAIN_MAX		0xffff
+#define IMX708_DGTL_GAIN_DEFAULT	0x0100
+#define IMX708_DGTL_GAIN_STEP		1
+
+/* Colour balance controls */
+#define IMX708_COLOUR_BALANCE_MIN	0x01
+#define IMX708_COLOUR_BALANCE_MAX	0xffff
+#define IMX708_COLOUR_BALANCE_STEP	0x01
+#define IMX708_COLOUR_BALANCE_DEFAULT	0x100
+
+/* Test pattern colour components */
+#define IMX708_TEST_PATTERN_COLOUR_MIN	0
+#define IMX708_TEST_PATTERN_COLOUR_MAX	0x0fff
+#define IMX708_TEST_PATTERN_COLOUR_STEP	1
+
+#define IMX708_REG_BASE_SPC_GAINS_L	CCI_REG8(0x7b10)
+#define IMX708_REG_BASE_SPC_GAINS_R	CCI_REG8(0x7c00)
+
+/* Middle exposure */
+#define IMX708_REG_MID_EXPOSURE		CCI_REG16(0x3116)
+#define IMX708_REG_MID_ANALOG_GAIN	CCI_REG16(0x3118)
+#define IMX708_REG_MID_DIGITAL_GAIN	CCI_REG16(0x311a)
+
+#define IMX708_REG_CLKLANE_BLANK	CCI_REG8(0x3220)
+#define IMX708_CLKLANE_BLANK_NONCONT	BIT(0)
+
+/* QBC Re-mosaic broken line correction registers */
+#define IMX708_REG_QBC_RMSC_EN		CCI_REG8(0x32d5)
+#define IMX708_REG_LPF_INTENSITY_EN	CCI_REG8(0xc428)
+#define IMX708_LPF_INTENSITY_ENABLED	0x00
+#define IMX708_LPF_INTENSITY_DISABLED	0x01
+#define IMX708_REG_LPF_INTENSITY	CCI_REG8(0xc429)
+#define IMX708_LPF_INTENSITY_DEFAULT	2
+
+/* AE HIST */
+#define IMX708_REG_AEHIST_AUTO_THRESH	CCI_REG16(0x3360)
+#define IMX708_REG_AEHIST1_AREA_WIDTH	CCI_REG16(0x3366)
+#define IMX708_REG_AEHIST1_AREA_HEIGHT	CCI_REG16(0x3368)
+
+enum pad_types {
+	IMX708_SOURCE_PAD,
+	IMX708_NUM_PADS
+};
+
+/* IMX708 native and active pixel array size. */
+static const struct v4l2_rect imx708_native_area = {
+	.top = 0,
+	.left = 0,
+	.width = 4640,
+	.height = 2658,
+};
+
+static const struct v4l2_rect imx708_active_area = {
+	.top = 24,
+	.left = 16,
+	.width = 4608,
+	.height = 2592,
+};
+
+/* Default PDAF pixel correction gains */
+static const u8 pdaf_gains[2][9] = {
+	{ 0x4c, 0x4c, 0x4c, 0x46, 0x3e, 0x39, 0x36, 0x36, 0x36 },
+	{ 0x36, 0x36, 0x36, 0x39, 0x3e, 0x46, 0x4c, 0x4c, 0x4c }
+};
+
+/* Link frequency setup */
+enum {
+	IMX708_LINK_FREQ_450MHZ,
+	IMX708_LINK_FREQ_447MHZ,
+	IMX708_LINK_FREQ_453MHZ,
+};
+
+static const s64 link_freqs[] = {
+	[IMX708_LINK_FREQ_450MHZ] = 450000000,
+	[IMX708_LINK_FREQ_447MHZ] = 447000000,
+	[IMX708_LINK_FREQ_453MHZ] = 453000000,
+};
+
+static const struct cci_reg_sequence imx708_common_regs[] = {
+	/* Common */
+	{ CCS_R_MODE_SELECT, 0x00 },
+	{ CCS_R_EXTCLK_FREQUENCY_MHZ, 0x1800},
+	{ CCI_REG8(0x33f0), 0x02 },
+	{ CCI_REG8(0x33f1), 0x05 },
+	{ CCI_REG8(0x3062), 0x00 },
+	{ CCI_REG8(0x3063), 0x12 },
+	{ CCI_REG8(0x3068), 0x00 },
+	{ CCI_REG8(0x3069), 0x12 },
+	{ CCI_REG8(0x306a), 0x00 },
+	{ CCI_REG8(0x306b), 0x30 },
+	{ CCI_REG8(0x3076), 0x00 },
+	{ CCI_REG8(0x3077), 0x30 },
+	{ CCI_REG8(0x3078), 0x00 },
+	{ CCI_REG8(0x3079), 0x30 },
+	{ CCI_REG8(0x5e54), 0x0c },
+	{ CCI_REG8(0x6e44), 0x00 },
+	{ CCI_REG8(0xb0b6), 0x01 },
+	{ CCI_REG8(0xe829), 0x00 },
+	{ CCI_REG8(0xf001), 0x08 },
+	{ CCI_REG8(0xf003), 0x08 },
+	{ CCI_REG8(0xf00d), 0x10 },
+	{ CCI_REG8(0xf00f), 0x10 },
+	{ CCI_REG8(0xf031), 0x08 },
+	{ CCI_REG8(0xf033), 0x08 },
+	{ CCI_REG8(0xf03d), 0x10 },
+	{ CCI_REG8(0xf03f), 0x10 },
+	{ CCS_R_CSI_DATA_FORMAT, 0x0a0a},
+	{ CCS_R_CSI_LANE_MODE, 0x01 },
+	{ CCS_R_ABSOLUTE_GAIN_GREENR, 0x0100},
+	{ CCS_R_ABSOLUTE_GAIN_GREENB, 0x0100},
+	{ CCI_REG8(0x3400), 0x01 },
+	{ CCI_REG8(0x3478), 0x01 },
+	{ CCI_REG8(0x3479), 0x1c },
+	{ CCI_REG8(0x3091), 0x01 },
+	{ CCI_REG8(0x3092), 0x00 },
+	{ CCI_REG8(0x3419), 0x00 },
+	{ CCI_REG8(0xbcf1), 0x02 },
+	{ CCI_REG8(0x3094), 0x01 },
+	{ CCI_REG8(0x3095), 0x01 },
+	{ CCI_REG8(0x3362), 0x00 },
+	{ CCI_REG8(0x3363), 0x00 },
+	{ CCI_REG8(0x3364), 0x00 },
+	{ CCI_REG8(0x3365), 0x00 },
+	{ CCS_R_TEMP_SENSOR_CTRL, 0x01 },
+	/* non-HDR defaults */
+	{ CCS_R_HDR_MODE, 0x62 },
+	{ CCS_R_EXPOSURE_RATIO, 0x01 },
+	{ CCI_REG8(0x350c), 0x00 },
+	{ CCI_REG8(0x350d), 0x00 },
+	{ CCS_R_DIRECT_SHORT_INTEGRATION_TIME, 0x01f4 },
+	{ IMX708_REG_MID_EXPOSURE, 0x01f4 },
+	{ CCS_R_SHORT_ANALOG_GAIN_GLOBAL, 0x0000 },
+	{ CCS_R_SHORT_DIGITAL_GAIN_GLOBAL, 0x0100 },
+	{ IMX708_REG_MID_ANALOG_GAIN, 0x0000 },
+	{ IMX708_REG_MID_DIGITAL_GAIN, 0x0100 },
+	{ IMX708_REG_AEHIST1_AREA_WIDTH, 0x0000 },
+	{ IMX708_REG_AEHIST1_AREA_HEIGHT, 0x0000 },
+	/* Quad-Bayer Compensation */
+	{ IMX708_REG_QBC_RMSC_EN, 0x01 },
+	{ IMX708_REG_LPF_INTENSITY, IMX708_LPF_INTENSITY_DEFAULT },
+	{ IMX708_REG_LPF_INTENSITY_EN, IMX708_LPF_INTENSITY_ENABLED },
+	{ CCI_REG8(0x32d6), 0x00 },
+	{ CCI_REG8(0x32db), 0x01 },
+	/* Analogue crop disabled */
+	{ IMX708_REG_ACROPLP_EN, 0x00 },
+	/* Unknown registers */
+	{ CCI_REG8(0x3ca0), 0x00 },
+	{ CCI_REG8(0x3ca1), 0x64 },
+	{ CCI_REG8(0x3ca4), 0x00 },
+	{ CCI_REG8(0x3ca5), 0x00 },
+	{ CCI_REG8(0x3ca6), 0x00 },
+	{ CCI_REG8(0x3ca7), 0x00 },
+	{ CCI_REG8(0x3caa), 0x00 },
+	{ CCI_REG8(0x3cab), 0x00 },
+	{ CCI_REG8(0x3cb8), 0x00 },
+	{ CCI_REG8(0x3cb9), 0x08 },
+	{ CCI_REG8(0x3cba), 0x00 },
+	{ CCI_REG8(0x3cbb), 0x00 },
+	{ CCI_REG8(0x3cbc), 0x00 },
+	{ CCI_REG8(0x3cbd), 0x3c },
+	{ CCI_REG8(0x3cbe), 0x00 },
+	{ CCI_REG8(0x3cbf), 0x00 },
+	{ CCI_REG8(0x341a), 0x00 },
+	{ CCI_REG8(0x341b), 0x00 },
+	{ CCI_REG8(0x341c), 0x00 },
+	{ CCI_REG8(0x341d), 0x00 },
+	{ CCI_REG8(0x341e), 0x01 },
+	{ CCI_REG8(0x341f), 0x20 },
+	{ CCI_REG8(0x3420), 0x00 },
+	{ CCI_REG8(0x3421), 0xd8 },
+};
+
+/*
+ * The supported formats.
+ * This table MUST contain 4 entries per format, to cover the various flip
+ * combinations in the order
+ * - no flip
+ * - h flip
+ * - v flip
+ * - h&v flips
+ */
+static const u32 codes[] = {
+	/* 10-bit modes. */
+	MEDIA_BUS_FMT_SRGGB10_1X10,
+	MEDIA_BUS_FMT_SGRBG10_1X10,
+	MEDIA_BUS_FMT_SGBRG10_1X10,
+	MEDIA_BUS_FMT_SBGGR10_1X10,
+};
+
+static const char * const imx708_test_pattern_menu[] = {
+	"Disabled",
+	"Color Bars",
+	"Solid Color",
+	"Grey Color Bars",
+	"PN9"
+};
+
+static const int imx708_test_pattern_val[] = {
+	CCS_TEST_PATTERN_MODE_NONE,
+	CCS_TEST_PATTERN_MODE_COLOR_BARS,
+	CCS_TEST_PATTERN_MODE_SOLID_COLOR,
+	CCS_TEST_PATTERN_MODE_FADE_TO_GREY,
+	CCS_TEST_PATTERN_MODE_PN9,
+};
+
+/* regulator supplies */
+static const char * const imx708_supply_name[] = {
+	/* Supplies can be enabled in any order */
+	"vana1",  /* Analog1 (2.8V) supply */
+	"vana2",  /* Analog2 (1.8V) supply */
+	"vdig",  /* Digital Core (1.1V) supply */
+	"vddl",  /* IF (1.8V) supply */
+};
+
+/*
+ * Initialisation delay between XCLR low->high and the moment when the sensor
+ * can start capture (i.e. can leave software standby), given by T7 in the
+ * datasheet is 8ms.  This does include I2C setup time as well.
+ *
+ * Note, that delay between XCLR low->high and reading the CCI ID register (T6
+ * in the datasheet) is much smaller - 600us.
+ */
+#define IMX708_XCLR_MIN_DELAY_US	8000
+#define IMX708_XCLR_DELAY_RANGE_US	1000
+
+struct imx708 {
+	struct v4l2_subdev sd;
+	struct media_pad pad[IMX708_NUM_PADS];
+	struct regmap *cci;
+	struct ccs_pll pll;
+
+	struct clk *inclk;
+	u32 inclk_freq;
+
+	struct gpio_desc *reset_gpio;
+	struct regulator_bulk_data supplies[ARRAY_SIZE(imx708_supply_name)];
+
+	struct v4l2_ctrl_handler ctrl_handler;
+	/* V4L2 Controls */
+	struct v4l2_ctrl *exposure;
+	struct v4l2_ctrl *vblank;
+	struct v4l2_ctrl *hblank;
+	struct {
+		struct v4l2_ctrl *hflip;
+		struct v4l2_ctrl *vflip;
+	};
+
+	/* Current long exposure factor in use. Set through V4L2_CID_VBLANK */
+	unsigned int long_exp_shift;
+
+	unsigned long link_freq_bitmap;
+
+	unsigned int csi_flags;
+
+	u8 lanes;
+};
+
+static inline struct imx708 *to_imx708(struct v4l2_subdev *_sd)
+{
+	return container_of_const(_sd, struct imx708, sd);
+}
+
+/* Get bayer order based on flip setting. */
+static u32 imx708_get_format_code(struct imx708 *imx708)
+{
+	unsigned int i;
+
+	i = (imx708->vflip->val ? 2 : 0) |
+	    (imx708->hflip->val ? 1 : 0);
+
+	return codes[i];
+}
+
+static void imx708_adjust_exposure_range(struct imx708 *imx708)
+{
+	int exposure_max, exposure_def;
+
+	/* Honour the VBLANK limits when setting exposure. */
+	exposure_max = imx708_active_area.height + imx708->vblank->val -
+		IMX708_EXPOSURE_OFFSET;
+	exposure_def = min(exposure_max, imx708->exposure->val);
+	__v4l2_ctrl_modify_range(imx708->exposure, imx708->exposure->minimum,
+				 exposure_max, imx708->exposure->step,
+				 exposure_def);
+}
+
+static int imx708_set_frame_length(struct imx708 *imx708, unsigned int val)
+{
+	int ret = 0;
+
+	imx708->long_exp_shift = 0;
+
+	while (val > IMX708_FRAME_LENGTH_MAX) {
+		imx708->long_exp_shift++;
+		val >>= 1;
+	}
+
+	cci_write(imx708->cci, CCS_R_FRAME_LENGTH_LINES, val, &ret);
+	cci_write(imx708->cci, IMX708_LONG_EXP_SHIFT_REG,
+		  imx708->long_exp_shift, &ret);
+
+	return ret;
+}
+
+static int imx708_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct imx708 *imx708 =
+		container_of_const(ctrl->handler, struct imx708, ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	int ret = 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_VBLANK:
+		/*
+		 * The VBLANK control may change the limits of usable exposure,
+		 * so check and adjust if necessary.
+		 */
+		imx708_adjust_exposure_range(imx708);
+		break;
+
+	case V4L2_CID_HFLIP:
+	case V4L2_CID_VFLIP: {
+		struct v4l2_subdev_state *state =
+			v4l2_subdev_get_locked_active_state(&imx708->sd);
+		struct v4l2_mbus_framefmt *format;
+
+		format = v4l2_subdev_state_get_format(state, IMX708_SOURCE_PAD);
+		format->code = imx708_get_format_code(imx708);
+		break;
+	}
+	}
+
+	/*
+	 * Only apply control values when device is powered on (RPM ACTIVE)
+	 * and streaming (usage count != 0)
+	 */
+	if (!pm_runtime_get_if_in_use(&client->dev))
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_ANALOGUE_GAIN:
+		cci_write(imx708->cci, CCS_R_ANALOG_GAIN_CODE_GLOBAL,
+			  ctrl->val, &ret);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = imx708_set_frame_length(imx708,
+					      imx708_active_area.height +
+					      ctrl->val);
+		fallthrough; /* update exposure with new long_exp_shift */
+	case V4L2_CID_EXPOSURE:
+		cci_write(imx708->cci, CCS_R_COARSE_INTEGRATION_TIME,
+			  imx708->exposure->val >> imx708->long_exp_shift,
+			  &ret);
+		break;
+	case V4L2_CID_DIGITAL_GAIN:
+		cci_write(imx708->cci, CCS_R_DIGITAL_GAIN_GLOBAL, ctrl->val,
+			  &ret);
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		cci_write(imx708->cci, CCS_R_TEST_PATTERN_MODE,
+			  imx708_test_pattern_val[ctrl->val], &ret);
+		break;
+	case V4L2_CID_TEST_PATTERN_RED:
+		cci_write(imx708->cci, CCS_R_TEST_DATA_RED,
+			  ctrl->val, &ret);
+		break;
+	case V4L2_CID_TEST_PATTERN_GREENR:
+		cci_write(imx708->cci, CCS_R_TEST_DATA_GREENR,
+			  ctrl->val, &ret);
+		break;
+	case V4L2_CID_TEST_PATTERN_BLUE:
+		cci_write(imx708->cci, CCS_R_TEST_DATA_BLUE,
+			  ctrl->val, &ret);
+		break;
+	case V4L2_CID_TEST_PATTERN_GREENB:
+		cci_write(imx708->cci, CCS_R_TEST_DATA_GREENB,
+			  ctrl->val, &ret);
+		break;
+	case V4L2_CID_HFLIP:
+	case V4L2_CID_VFLIP:
+		cci_write(imx708->cci, CCS_R_IMAGE_ORIENTATION,
+			  imx708->hflip->val | imx708->vflip->val << 1, &ret);
+		break;
+	case V4L2_CID_NOTIFY_GAINS:
+		cci_write(imx708->cci, CCS_R_ABSOLUTE_GAIN_BLUE,
+			  ctrl->p_new.p_u32[0], &ret);
+		if (ret)
+			break;
+		cci_write(imx708->cci, CCS_R_ABSOLUTE_GAIN_RED,
+			  ctrl->p_new.p_u32[3], &ret);
+		break;
+	default:
+		dev_warn(&client->dev,
+			 "ctrl(id:0x%x,val:0x%x) is not handled\n",
+			 ctrl->id, ctrl->val);
+		ret = -EINVAL;
+		break;
+	}
+
+	pm_runtime_put_autosuspend(&client->dev);
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops imx708_ctrl_ops = {
+	.s_ctrl = imx708_set_ctrl,
+};
+
+static int imx708_enum_mbus_code(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct imx708 *imx708 = to_imx708(sd);
+
+	if (code->pad >= IMX708_NUM_PADS)
+		return -EINVAL;
+
+	if (code->index >= (ARRAY_SIZE(codes) / 4))
+		return -EINVAL;
+
+	code->code = imx708_get_format_code(imx708);
+
+	return 0;
+}
+
+static int imx708_enum_frame_size(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *sd_state,
+				  struct v4l2_subdev_frame_size_enum *fse)
+{
+	struct imx708 *imx708 = to_imx708(sd);
+	u32 code;
+
+	if (fse->pad >= IMX708_NUM_PADS)
+		return -EINVAL;
+
+	code = imx708_get_format_code(imx708);
+
+	if (fse->code != code)
+		return -EINVAL;
+
+	if (fse->index > 0)
+		return -EINVAL;
+
+	fse->min_width = imx708_active_area.width;
+	fse->max_width = fse->min_width;
+	fse->min_height = imx708_active_area.height;
+	fse->max_height = fse->min_height;
+
+	return 0;
+}
+
+static int imx708_init_state(struct v4l2_subdev *sd,
+			     struct v4l2_subdev_state *state)
+{
+	struct imx708 *imx708 = to_imx708(sd);
+	struct v4l2_mbus_framefmt *format;
+	struct v4l2_rect *crop;
+
+	/* Initialize the image pad format. */
+	format = v4l2_subdev_state_get_format(state, IMX708_SOURCE_PAD);
+	format->code = imx708_get_format_code(imx708);
+	format->width = imx708_active_area.width;
+	format->height = imx708_active_area.height;
+	format->field = V4L2_FIELD_NONE;
+	format->colorspace = V4L2_COLORSPACE_RAW;
+	format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+	format->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+	format->xfer_func = V4L2_XFER_FUNC_NONE;
+
+	/* Initialize the image pad crop. */
+	crop = v4l2_subdev_state_get_crop(state, IMX708_SOURCE_PAD);
+	*crop = imx708_active_area;
+
+	return 0;
+}
+
+static int imx708_get_selection(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *sd_state,
+				struct v4l2_subdev_selection *sel)
+{
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP: {
+		struct v4l2_rect *crop;
+
+		crop = v4l2_subdev_state_get_crop(sd_state, sel->pad);
+		sel->r = *crop;
+
+		return 0;
+	}
+
+	case V4L2_SEL_TGT_NATIVE_SIZE:
+		sel->r = imx708_native_area;
+
+		return 0;
+
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		sel->r = imx708_active_area;
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int imx708_program_window(struct imx708 *imx708,
+				 struct v4l2_subdev_state *state)
+{
+	const struct v4l2_rect *crop;
+	const struct v4l2_mbus_framefmt *format;
+	int ret = 0;
+	s32 x_start, y_start;
+
+	crop = v4l2_subdev_state_get_crop(state, IMX708_SOURCE_PAD);
+	format = v4l2_subdev_state_get_format(state, IMX708_SOURCE_PAD);
+
+	/* Line length */
+	cci_write(imx708->cci, CCS_R_LINE_LENGTH_PCK, IMX708_LINE_LENGTH,
+		  &ret);
+
+	/* Imaging area */
+	x_start = crop->left - imx708_active_area.left;
+	y_start = crop->top - imx708_active_area.top;
+	cci_write(imx708->cci, CCS_R_X_ADDR_START, x_start, &ret);
+	cci_write(imx708->cci, CCS_R_Y_ADDR_START, y_start, &ret);
+	cci_write(imx708->cci, CCS_R_X_ADDR_END, x_start + crop->width - 1,
+		  &ret);
+	cci_write(imx708->cci, CCS_R_Y_ADDR_END, y_start + crop->height - 1,
+		  &ret);
+
+	/* Binning (fixed: no binning) */
+	cci_write(imx708->cci, CCS_R_BINNING_MODE, 0x00, &ret);
+	cci_write(imx708->cci, CCS_R_BINNING_TYPE, 0x11, &ret);
+	cci_write(imx708->cci, CCS_R_BINNING_WEIGHTING, 0x0a, &ret);
+	cci_write(imx708->cci, IMX708_REG_BINNING_PRIORITY_H, 0x01, &ret);
+	cci_write(imx708->cci, IMX708_REG_BINNING_PRIORITY_V, 0x01, &ret);
+
+	/* Digital crop (fixed: no crop) */
+	cci_write(imx708->cci, CCS_R_DIGITAL_CROP_X_OFFSET, 0, &ret);
+	cci_write(imx708->cci, CCS_R_DIGITAL_CROP_Y_OFFSET, 0, &ret);
+	cci_write(imx708->cci, CCS_R_DIGITAL_CROP_IMAGE_WIDTH, format->width,
+		  &ret);
+	cci_write(imx708->cci, CCS_R_DIGITAL_CROP_IMAGE_HEIGHT, format->height,
+		  &ret);
+
+	/* Output size */
+	cci_write(imx708->cci, CCS_R_X_OUTPUT_SIZE, format->width, &ret);
+	cci_write(imx708->cci, CCS_R_Y_OUTPUT_SIZE, format->height, &ret);
+
+	return ret;
+}
+
+static int imx708_pll_calculate(struct imx708 *imx708, u64 link_freq)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	struct ccs_pll *pll = &imx708->pll;
+	const struct ccs_pll_limits imx708_pll_limits = {
+		.min_ext_clk_freq_hz = 6000000,
+		.max_ext_clk_freq_hz = 27000000,
+
+		.vt_fr = {
+			.min_pre_pll_clk_div = 1,
+			.max_pre_pll_clk_div = 4,
+			.min_pll_ip_clk_freq_hz = 6000000,
+			.max_pll_ip_clk_freq_hz = 12000000,
+			.min_pll_multiplier = 92,
+			.max_pll_multiplier = 360,
+			.min_pll_op_clk_freq_hz = 1100000000,
+			.max_pll_op_clk_freq_hz = 2160000000,
+		},
+		.vt_bk = {
+			/*
+			 * Datasheet suggests 2 or 4 (not 3) for VT_SYS_CLK_DIV.
+			 * It's hard to encode that here. We luckily don't hit
+			 * 3 for the currently used pixel rate.
+			 * TODO: Verify VT_SYS_CLK_DIV = 3.
+			 */
+			.min_sys_clk_div = 2,
+			.max_sys_clk_div = 4,
+			.min_sys_clk_freq_hz = 540000000,
+			.max_sys_clk_freq_hz = 1080000000,
+			/*
+			 * The CCS registers suggest MAX_VT_PIX_CLK_DIV = 10 and
+			 * MIN_VT_PIX_CLK_FREQ = 55MHz, but the datasheet
+			 * explicitly requires the divisor to be fixed to 5, so
+			 * stick to it below and update pix_clk_freq range
+			 * accordingly.
+			 */
+			.min_pix_clk_div = 5,
+			.max_pix_clk_div = 5,
+			.min_pix_clk_freq_hz = 108000000,
+			.max_pix_clk_freq_hz = 216000000,
+		},
+		.op_fr = {
+			.min_pre_pll_clk_div = 1,
+			.max_pre_pll_clk_div = 27,
+			.min_pll_ip_clk_freq_hz = 1000000,
+			.max_pll_ip_clk_freq_hz = 12000000,
+			/*
+			 * The CCS register suggests MIN_OP_PLL_MULTIPLIER = 92,
+			 * but the datasheet explicitly requires 105 for
+			 * DUAL_PLL mode.
+			 */
+			.min_pll_multiplier = 105,
+			.max_pll_multiplier = 2500,
+			.min_pll_op_clk_freq_hz = 1250000000,
+			.max_pll_op_clk_freq_hz = 2500000000,
+		},
+		.op_bk = {
+			/*
+			 * The CCS register suggests MAX_OP_SYS_CLK_DIV = 2,
+			 * but the datasheet table allows 1,2 and 4.
+			 * We stick to 1,2 to be safe.
+			 * TODO: Verify OP_SYS_CLK_DIV = 3 and 4.
+			 */
+			.min_sys_clk_div = 1,
+			.max_sys_clk_div = 2,
+			.min_sys_clk_freq_hz = 625000000,
+			.max_sys_clk_freq_hz = 2500000000,
+			/* RAW8 and RAW10 */
+			.min_pix_clk_div = 8,
+			.max_pix_clk_div = 10,
+			/*
+			 * The frequency range is derived from above
+			 * constraints, as the CCS regs (0x1170, 0x1174) are 0.
+			 */
+			.min_pix_clk_freq_hz = 62500000,
+			.max_pix_clk_freq_hz = 312500000,
+		},
+
+		.min_line_length_pck_bin = IMX708_LINE_LENGTH,
+		.min_line_length_pck = IMX708_LINE_LENGTH,
+	};
+
+	memset(pll, 0, sizeof(*pll));
+
+	pll->bus_type = CCS_PLL_BUS_TYPE_CSI2_DPHY;
+	pll->op_lanes = imx708->lanes;
+	pll->csi2.lanes = imx708->lanes;
+
+	/* CCS_NUM_OF_VT_LANES (0x1139) = 4 */
+	pll->vt_lanes = 4;
+
+	pll->binning_horizontal = 1;
+	pll->binning_vertical = 1;
+	pll->scale_m = 1;
+	pll->scale_n = 1;
+	pll->bits_per_pixel = 10;
+	pll->flags = CCS_PLL_FLAG_LANE_SPEED_MODEL
+		   | CCS_PLL_FLAG_DUAL_PLL
+		   | CCS_PLL_FLAG_FIFO_DERATING;
+	pll->link_freq = link_freq;
+	pll->ext_clk_freq_hz = imx708->inclk_freq;
+	pll->pixel_rate_pixel_array = IMX708_PIXEL_RATE;
+
+	return ccs_pll_calculate(&client->dev, &imx708_pll_limits, pll);
+}
+
+static int imx708_program_pll(struct imx708 *imx708)
+{
+	int ret = 0;
+
+	cci_write(imx708->cci, CCS_R_VT_PIX_CLK_DIV,
+		  imx708->pll.vt_bk.pix_clk_div, &ret);
+	cci_write(imx708->cci, CCS_R_VT_SYS_CLK_DIV,
+		  imx708->pll.vt_bk.sys_clk_div, &ret);
+	cci_write(imx708->cci, CCS_R_PRE_PLL_CLK_DIV,
+		  imx708->pll.vt_fr.pre_pll_clk_div, &ret);
+	cci_write(imx708->cci, CCS_R_PLL_MULTIPLIER,
+		  imx708->pll.vt_fr.pll_multiplier, &ret);
+	cci_write(imx708->cci, CCS_R_OP_PIX_CLK_DIV,
+		  imx708->pll.op_bk.pix_clk_div, &ret);
+	cci_write(imx708->cci, CCS_R_OP_SYS_CLK_DIV,
+		  imx708->pll.op_bk.sys_clk_div, &ret);
+	cci_write(imx708->cci, CCS_R_OP_PRE_PLL_CLK_DIV,
+		  imx708->pll.op_fr.pre_pll_clk_div, &ret);
+	cci_write(imx708->cci, CCS_R_OP_PLL_MULTIPLIER,
+		  imx708->pll.op_fr.pll_multiplier, &ret);
+	cci_write(imx708->cci, CCS_R_PLL_MODE, CCS_PLL_MODE_DUAL,
+		  &ret);
+
+	return ret;
+}
+
+static int imx708_enable_streams(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state, u32 pad,
+				 u64 mask)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct imx708 *imx708 = to_imx708(sd);
+	int ret = 0;
+
+	ret = pm_runtime_resume_and_get(&client->dev);
+	if (ret < 0)
+		return ret;
+
+	/* Program windowing registers from V4L2 state */
+	ret = imx708_program_window(imx708, state);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set mode\n", __func__);
+		goto err_rpm_put;
+	}
+
+	/* Program the PLL for the selected link frequency */
+	ret = imx708_program_pll(imx708);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to configure PLL\n", __func__);
+		goto err_rpm_put;
+	}
+
+	/* Apply customized values from user */
+	ret =  __v4l2_ctrl_handler_setup(imx708->sd.ctrl_handler);
+	if (ret)
+		goto err_rpm_put;
+
+	/* set stream on register */
+	cci_write(imx708->cci, CCS_R_MODE_SELECT,
+		  CCS_MODE_SELECT_STREAMING, &ret);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to start streaming\n",
+			__func__);
+		goto err_rpm_put;
+	}
+
+	/* vflip/hflip cannot change during streaming */
+	__v4l2_ctrl_grab(imx708->vflip, true);
+	__v4l2_ctrl_grab(imx708->hflip, true);
+
+	return 0;
+
+err_rpm_put:
+	pm_runtime_put_sync(&client->dev);
+
+	return ret;
+}
+
+static int imx708_disable_streams(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state,
+				  u32 pad, u64 mask)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct imx708 *imx708 = to_imx708(sd);
+	int ret = 0;
+
+	/* set stream off register */
+	cci_write(imx708->cci, CCS_R_MODE_SELECT,
+		  CCS_MODE_SELECT_SOFTWARE_STANDBY, &ret);
+	if (ret)
+		dev_err(&client->dev, "%s failed to set stream\n", __func__);
+
+	__v4l2_ctrl_grab(imx708->vflip, false);
+	__v4l2_ctrl_grab(imx708->hflip, false);
+
+	pm_runtime_put_autosuspend(&client->dev);
+
+	return ret;
+}
+
+static int imx708_write_common(struct imx708 *imx708)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	int i, ret = 0;
+	u64 val;
+
+	cci_multi_reg_write(imx708->cci, imx708_common_regs,
+			    ARRAY_SIZE(imx708_common_regs), &ret);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set common settings\n",
+			__func__);
+		return ret;
+	}
+
+	cci_write(imx708->cci, IMX708_REG_CLKLANE_BLANK,
+		  imx708->csi_flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK ?
+		  IMX708_CLKLANE_BLANK_NONCONT : 0, &ret);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set clock lane mode\n",
+			__func__);
+		return ret;
+	}
+
+	cci_read(imx708->cci, IMX708_REG_BASE_SPC_GAINS_L, &val, &ret);
+	if (ret == 0 && val == 0x40) {
+		for (i = 0; i < 54 && ret == 0; i++) {
+			cci_write(imx708->cci,
+				  CCI_REG8(CCI_REG_ADDR(IMX708_REG_BASE_SPC_GAINS_L) + i),
+				  pdaf_gains[0][i % 9], &ret);
+		}
+		for (i = 0; i < 54 && ret == 0; i++) {
+			cci_write(imx708->cci,
+				  CCI_REG8(CCI_REG_ADDR(IMX708_REG_BASE_SPC_GAINS_R) + i),
+				  pdaf_gains[1][i % 9], &ret);
+		}
+	}
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set PDAF gains\n",
+			__func__);
+		return ret;
+	}
+
+	return 0;
+}
+
+/* Power/clock management functions */
+static int imx708_power_on(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx708 *imx708 = to_imx708(sd);
+	int ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(imx708_supply_name),
+				    imx708->supplies);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable regulators\n",
+			__func__);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(imx708->inclk);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable clock\n",
+			__func__);
+		goto reg_off;
+	}
+
+	gpiod_set_value_cansleep(imx708->reset_gpio, 0);
+	usleep_range(IMX708_XCLR_MIN_DELAY_US,
+		     IMX708_XCLR_MIN_DELAY_US + IMX708_XCLR_DELAY_RANGE_US);
+
+	ret = imx708_write_common(imx708);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to write registers\n",
+			__func__);
+		goto clk_off;
+	}
+
+	return 0;
+
+clk_off:
+	gpiod_set_value_cansleep(imx708->reset_gpio, 1);
+	clk_disable_unprepare(imx708->inclk);
+
+reg_off:
+	regulator_bulk_disable(ARRAY_SIZE(imx708_supply_name),
+			       imx708->supplies);
+	return ret;
+}
+
+static int imx708_power_off(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx708 *imx708 = to_imx708(sd);
+
+	gpiod_set_value_cansleep(imx708->reset_gpio, 1);
+	regulator_bulk_disable(ARRAY_SIZE(imx708_supply_name),
+			       imx708->supplies);
+	clk_disable_unprepare(imx708->inclk);
+
+	return 0;
+}
+
+/* Verify chip ID */
+static int imx708_identify_module(struct imx708 *imx708)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	int ret = 0;
+	u64 val;
+
+	cci_read(imx708->cci, CCS_R_SENSOR_MODEL_ID, &val, &ret);
+	if (ret) {
+		dev_err(&client->dev, "failed to read chip id %x, with error %d\n",
+			IMX708_CHIP_ID, ret);
+		return ret;
+	}
+
+	if (val != IMX708_CHIP_ID) {
+		dev_err(&client->dev, "chip id mismatch: %x!=%llx\n",
+			IMX708_CHIP_ID, val);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_subdev_core_ops imx708_core_ops = {
+	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_video_ops imx708_video_ops = {
+	.s_stream = v4l2_subdev_s_stream_helper,
+};
+
+static const struct v4l2_subdev_pad_ops imx708_pad_ops = {
+	.enum_mbus_code = imx708_enum_mbus_code,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = v4l2_subdev_get_fmt,
+	.get_selection = imx708_get_selection,
+	.enum_frame_size = imx708_enum_frame_size,
+	.enable_streams = imx708_enable_streams,
+	.disable_streams = imx708_disable_streams,
+};
+
+static const struct v4l2_subdev_ops imx708_subdev_ops = {
+	.core = &imx708_core_ops,
+	.video = &imx708_video_ops,
+	.pad = &imx708_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops imx708_internal_ops = {
+	.init_state = imx708_init_state,
+};
+
+static const struct v4l2_ctrl_config imx708_notify_gains_ctrl = {
+	.ops = &imx708_ctrl_ops,
+	.id = V4L2_CID_NOTIFY_GAINS,
+	.type = V4L2_CTRL_TYPE_U32,
+	.min = IMX708_COLOUR_BALANCE_MIN,
+	.max = IMX708_COLOUR_BALANCE_MAX,
+	.step = IMX708_COLOUR_BALANCE_STEP,
+	.def = IMX708_COLOUR_BALANCE_DEFAULT,
+	.dims = { 4 },
+	.elem_size = sizeof(u32),
+};
+
+/* Initialize control handlers */
+static int imx708_init_controls(struct imx708 *imx708)
+{
+	struct v4l2_ctrl_handler *ctrl_hdlr;
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	struct v4l2_fwnode_device_properties props;
+	struct v4l2_ctrl *link_freq;
+	unsigned int i;
+	s32 hblank, vblank_max, exposure_max;
+	int ret;
+
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret < 0)
+		return ret;
+
+	ctrl_hdlr = &imx708->ctrl_handler;
+	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 17);
+	if (ret)
+		return ret;
+
+	ret = imx708_pll_calculate(imx708,
+				   link_freqs[__ffs(imx708->link_freq_bitmap)]);
+	if (ret) {
+		v4l2_ctrl_handler_free(ctrl_hdlr);
+		dev_err(imx708->sd.dev, "PLL calculations failed: %d\n", ret);
+		return ret;
+	}
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_PIXEL_RATE,
+			  imx708->pll.pixel_rate_pixel_array,
+			  imx708->pll.pixel_rate_pixel_array, 1,
+			  imx708->pll.pixel_rate_pixel_array);
+
+	link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx708_ctrl_ops,
+					   V4L2_CID_LINK_FREQ,
+					   ARRAY_SIZE(link_freqs) - 1,
+					   __ffs(imx708->link_freq_bitmap),
+					   link_freqs);
+
+	/* Frame Time = 2^LONG_EXP_SHIFT * REG_FRAME_LENGTH */
+	vblank_max = ((1 << IMX708_LONG_EXP_SHIFT_MAX) *
+		      IMX708_FRAME_LENGTH_MAX) - imx708_active_area.height;
+	imx708->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+					   V4L2_CID_VBLANK, IMX708_VBLANK_MIN,
+					   vblank_max, 1, IMX708_VBLANK_MIN);
+
+	hblank = IMX708_LINE_LENGTH - imx708_active_area.width;
+	imx708->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+					   V4L2_CID_HBLANK, hblank, hblank, 1,
+					   hblank);
+
+	/* Max Exposure = FRAME_LENGTH - OFFSET */
+	exposure_max = (imx708_active_area.height + IMX708_VBLANK_MIN) -
+		IMX708_EXPOSURE_OFFSET;
+	imx708->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+					     V4L2_CID_EXPOSURE,
+					     IMX708_EXPOSURE_MIN,
+					     exposure_max,
+					     IMX708_EXPOSURE_STEP,
+					     IMX708_EXPOSURE_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+			  IMX708_ANA_GAIN_MIN, IMX708_ANA_GAIN_MAX,
+			  IMX708_ANA_GAIN_STEP, IMX708_ANA_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+			  IMX708_DGTL_GAIN_MIN, IMX708_DGTL_GAIN_MAX,
+			  IMX708_DGTL_GAIN_STEP, IMX708_DGTL_GAIN_DEFAULT);
+
+	imx708->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+					  V4L2_CID_HFLIP, 0, 1, 1, 0);
+
+	imx708->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+					  V4L2_CID_VFLIP, 0, 1, 1, 0);
+	v4l2_ctrl_cluster(2, &imx708->hflip);
+
+	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx708_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(imx708_test_pattern_menu) - 1,
+				     0, 0, imx708_test_pattern_menu);
+	for (i = 0; i < 4; i++) {
+		/*
+		 * The assumption is that
+		 * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1
+		 * V4L2_CID_TEST_PATTERN_BLUE   == V4L2_CID_TEST_PATTERN_RED + 2
+		 * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3
+		 */
+		v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+				  V4L2_CID_TEST_PATTERN_RED + i,
+				  IMX708_TEST_PATTERN_COLOUR_MIN,
+				  IMX708_TEST_PATTERN_COLOUR_MAX,
+				  IMX708_TEST_PATTERN_COLOUR_STEP,
+				  IMX708_TEST_PATTERN_COLOUR_MAX);
+		/* The "Solid color" pattern is white by default */
+	}
+
+	v4l2_ctrl_new_custom(ctrl_hdlr, &imx708_notify_gains_ctrl, NULL);
+
+	v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx708_ctrl_ops, &props);
+
+	if (ctrl_hdlr->error) {
+		ret = ctrl_hdlr->error;
+		dev_err(&client->dev, "%s control init failed (%d)\n",
+			__func__, ret);
+		v4l2_ctrl_handler_free(ctrl_hdlr);
+
+		return ret;
+	}
+
+	link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	imx708->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	imx708->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+	imx708->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	imx708->sd.ctrl_handler = ctrl_hdlr;
+
+	return 0;
+}
+
+static int imx708_check_hwcfg(struct device *dev, struct imx708 *imx708)
+{
+	struct fwnode_handle *endpoint;
+	struct v4l2_fwnode_endpoint ep_cfg = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY
+	};
+	int ret = -EINVAL;
+
+	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+	if (!endpoint) {
+		dev_err(dev, "endpoint node not found\n");
+		return -EINVAL;
+	}
+
+	if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) {
+		dev_err(dev, "could not parse endpoint\n");
+		goto error_out;
+	}
+
+	/* Check the number of MIPI CSI2 data lanes */
+	if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) {
+		dev_err(dev, "only 2 data lanes are currently supported\n");
+		goto error_out;
+	}
+
+	imx708->lanes = ep_cfg.bus.mipi_csi2.num_data_lanes;
+	imx708->csi_flags = ep_cfg.bus.mipi_csi2.flags;
+
+	/* Check the link frequency set in device tree */
+	ret = v4l2_link_freq_to_bitmap(dev, ep_cfg.link_frequencies,
+				       ep_cfg.nr_of_link_frequencies,
+				       link_freqs, ARRAY_SIZE(link_freqs),
+				       &imx708->link_freq_bitmap);
+
+error_out:
+	v4l2_fwnode_endpoint_free(&ep_cfg);
+	fwnode_handle_put(endpoint);
+
+	return ret;
+}
+
+static int imx708_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct imx708 *imx708;
+	int ret;
+
+	imx708 = devm_kzalloc(&client->dev, sizeof(*imx708), GFP_KERNEL);
+	if (!imx708)
+		return -ENOMEM;
+
+	v4l2_i2c_subdev_init(&imx708->sd, client, &imx708_subdev_ops);
+
+	imx708->cci = devm_cci_regmap_init_i2c(client, 16);
+	if (IS_ERR(imx708->cci))
+		return dev_err_probe(dev, PTR_ERR(imx708->cci),
+				     "failed to init CCI\n");
+
+	if (imx708_check_hwcfg(dev, imx708))
+		return -EINVAL;
+
+	imx708->inclk = devm_v4l2_sensor_clk_get(dev, NULL);
+	if (IS_ERR(imx708->inclk))
+		return dev_err_probe(dev, PTR_ERR(imx708->inclk),
+				     "failed to get inclk\n");
+
+	imx708->inclk_freq = clk_get_rate(imx708->inclk);
+	if (imx708->inclk_freq != IMX708_INCLK_FREQ)
+		return dev_err_probe(dev, -EINVAL,
+				     "inclk frequency not supported: %d Hz\n",
+				     imx708->inclk_freq);
+
+	for (int i = 0; i < ARRAY_SIZE(imx708_supply_name); i++)
+		imx708->supplies[i].supply = imx708_supply_name[i];
+
+	ret = devm_regulator_bulk_get(&client->dev,
+				      ARRAY_SIZE(imx708_supply_name),
+				      imx708->supplies);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to get regulators\n");
+
+	imx708->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+						     GPIOD_OUT_HIGH);
+	if (IS_ERR(imx708->reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(imx708->reset_gpio),
+				     "failed to get reset GPIO\n");
+
+	ret = imx708_power_on(dev);
+	if (ret)
+		return ret;
+
+	ret = imx708_identify_module(imx708);
+	if (ret)
+		goto error_power_off;
+
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+
+	ret = imx708_init_controls(imx708);
+	if (ret)
+		goto error_pm_runtime;
+
+	imx708->sd.internal_ops = &imx708_internal_ops;
+	imx708->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	imx708->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+	imx708->pad[IMX708_SOURCE_PAD].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&imx708->sd.entity, IMX708_NUM_PADS,
+				     imx708->pad);
+	if (ret) {
+		dev_err_probe(dev, ret, "failed to init entity pads\n");
+		goto error_handler_free;
+	}
+
+	imx708->sd.state_lock = imx708->ctrl_handler.lock;
+	ret = v4l2_subdev_init_finalize(&imx708->sd);
+	if (ret) {
+		dev_err_probe(dev, ret, "failed to finalize subdev\n");
+		goto error_media_entity;
+	}
+
+	ret = v4l2_async_register_subdev_sensor(&imx708->sd);
+	if (ret < 0) {
+		dev_err_probe(dev, ret, "failed to register sensor sub-device\n");
+		goto error_subdev_cleanup;
+	}
+
+	pm_runtime_idle(dev);
+	pm_runtime_set_autosuspend_delay(dev, 5000);
+	pm_runtime_use_autosuspend(dev);
+
+	return 0;
+
+error_subdev_cleanup:
+	v4l2_subdev_cleanup(&imx708->sd);
+
+error_media_entity:
+	media_entity_cleanup(&imx708->sd.entity);
+
+error_handler_free:
+	v4l2_ctrl_handler_free(imx708->sd.ctrl_handler);
+
+error_pm_runtime:
+	pm_runtime_disable(dev);
+	pm_runtime_set_suspended(dev);
+
+error_power_off:
+	imx708_power_off(&client->dev);
+
+	return ret;
+}
+
+static void imx708_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx708 *imx708 = to_imx708(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	v4l2_subdev_cleanup(sd);
+	media_entity_cleanup(&sd->entity);
+	v4l2_ctrl_handler_free(imx708->sd.ctrl_handler);
+
+	pm_runtime_disable(&client->dev);
+	if (!pm_runtime_status_suspended(&client->dev))
+		imx708_power_off(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+static const struct of_device_id imx708_dt_ids[] = {
+	{ .compatible = "sony,imx708" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx708_dt_ids);
+
+static const struct dev_pm_ops imx708_pm_ops = {
+	SET_RUNTIME_PM_OPS(imx708_power_off, imx708_power_on, NULL)
+};
+
+static struct i2c_driver imx708_i2c_driver = {
+	.driver = {
+		.name = "imx708",
+		.of_match_table	= imx708_dt_ids,
+		.pm = pm_ptr(&imx708_pm_ops),
+	},
+	.probe = imx708_probe,
+	.remove = imx708_remove,
+};
+
+module_i2c_driver(imx708_i2c_driver);
+
+MODULE_AUTHOR("Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>");
+MODULE_AUTHOR("David Plowman <david.plowman@raspberrypi.com>");
+MODULE_AUTHOR("Jai Luthra <jai.luthra@ideasonboard.com>");
+MODULE_DESCRIPTION("Sony IMX708 sensor driver");
+MODULE_LICENSE("GPL");

-- 
2.54.0


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

* Re: [PATCH v2 1/3] dt-bindings: media: i2c: Add imx708 image sensor
  2026-08-07 12:55 ` [PATCH v2 1/3] dt-bindings: media: i2c: Add imx708 image sensor Jai Luthra
@ 2026-08-07 13:04   ` sashiko-bot
  0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-08-07 13:04 UTC (permalink / raw)
  To: Jai Luthra; +Cc: conor+dt, media-ci, robh, devicetree

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Swapped descriptions and voltages for VDIG and VDDL supplies risk hardware damage.
- [Medium] Missing Common Schema Reference blocks standard video interface properties.
--

--- Patch [1]: [PATCH v2 1/3] dt-bindings: media: i2c: Add imx708 image sensor ---
commit 2d548d9db5698aed5ad6188c54ce0ae5aa972fd7
Author: Jai Luthra <jai.luthra@ideasonboard.com>

dt-bindings: media: i2c: Add imx708 image sensor

Adds device tree bindings for the Sony IMX708 image sensor. It defines the
required properties, regulators, port structure, and includes an example
configuration.

> diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml
> new file mode 100644
> index 0000000000000..e41b3ca537063
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml
> @@ -0,0 +1,120 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
[ ... ]
> +properties:
> +  compatible:
> +    const: sony,imx708
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    maxItems: 1
> +
> +  vdig-supply:
> +    description:
> +      Digital I/O voltage supply, 1.1 volts

[Severity: High]
Are the voltages and descriptions for vdig-supply and vddl-supply swapped
here? Standard Sony CMOS sensors typically use 1.8V for I/O (vdig) and
1.1V/1.2V for the core (vddl). Supplying 1.8V to a 1.1V digital core might
cause permanent hardware damage if integrators follow this description.

> +
> +  vana1-supply:
> +    description:
> +      Analog1 voltage supply, 2.8 volts
> +
> +  vana2-supply:
> +    description:
> +      Analog2 voltage supply, 1.8 volts
> +
> +  vddl-supply:
> +    description:
> +      Digital core voltage supply, 1.8 volts
[ ... ]
> +required:
> +  - compatible
> +  - reg
> +  - clocks
> +  - vdig-supply
> +  - vana1-supply
> +  - vana2-supply
> +  - vddl-supply
> +  - port
> +
> +additionalProperties: false

[Severity: Medium]
Will this strict rejection of additional properties prevent device trees from
specifying standard physical attributes like orientation and rotation?

Typically, an allOf block including a reference to
/schemas/media/video-interface-devices.yaml# is added near the properties
block to inherit these common hardware properties, and unevaluatedProperties
is used instead of additionalProperties. Without the common reference, valid
hardware descriptions may fail validation.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-imx708-v2-0-7d3d7b57779d@ideasonboard.com?part=1

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

* Re: [PATCH v2 3/3] media: i2c: Add a driver for Sony IMX708
  2026-08-07 12:55 ` [PATCH v2 3/3] media: i2c: Add a driver for Sony IMX708 Jai Luthra
@ 2026-08-07 13:12   ` sashiko-bot
  0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-08-07 13:12 UTC (permalink / raw)
  To: Jai Luthra; +Cc: devicetree, robh, conor+dt, media-ci

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Incorrect hardware power-off sequence: clock disabled after regulators.
- [Medium] Incorrect Runtime PM initialization ordering in `imx708_probe()` bypasses autosuspend.
- [Medium] Loss of precision in frame length calculation allows exposure to exceed physical hardware limits.
- [Medium] Missing system sleep PM operations causes a power leak during system suspend.
- [Medium] Lockless data race on V4L2 control values during TRY format enumeration.
--

--- Patch [3]: [PATCH v2 3/3] media: i2c: Add a driver for Sony IMX708 ---
commit 0f39548715745ff8d2825112d41ad4cfb31c5791
Author: Jai Luthra <jai.luthra@ideasonboard.com>
Subject: media: i2c: Add a driver for Sony IMX708

This commit adds a V4L2 subdevice driver for the Sony IMX708 11.9 Megapixel
CMOS sensor. It supports capturing the full active area up to 14.35fps
with features including MIPI RAW10 output, analog gain, and VBLANK control.

> diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c
> new file mode 100644
> index 0000000000000..7d5b8bdc2b437
> --- /dev/null
> +++ b/drivers/media/i2c/imx708.c
[ ... ]
> +static u32 imx708_get_format_code(struct imx708 *imx708)
> +{
> +	unsigned int i;
> +
> +	i = (imx708->vflip->val ? 2 : 0) |
> +	    (imx708->hflip->val ? 1 : 0);

[Severity: Medium]
Could this result in a data race during format enumeration? When userspace
calls VIDIOC_SUBDEV_ENUM_MBUS_CODE with V4L2_SUBDEV_FORMAT_TRY, the V4L2
core acquires the TRY state lock, which is independent of the control
handler lock. If another thread modifies the flip controls via VIDIOC_S_CTRL
concurrently, it could cause an inconsistent read of vflip->val and
hflip->val in imx708_get_format_code(), leading to incorrect format
enumeration results.

[ ... ]
> +static int imx708_set_ctrl(struct v4l2_ctrl *ctrl)
> +{
[ ... ]
> +	case V4L2_CID_VBLANK:
> +		ret = imx708_set_frame_length(imx708,
> +					      imx708_active_area.height +
> +					      ctrl->val);
> +		fallthrough; /* update exposure with new long_exp_shift */
> +	case V4L2_CID_EXPOSURE:
> +		cci_write(imx708->cci, CCS_R_COARSE_INTEGRATION_TIME,
> +			  imx708->exposure->val >> imx708->long_exp_shift,
> +			  &ret);
> +		break;

[Severity: Medium]
Will this precision loss allow the programmed exposure time to exceed
physical hardware limits? If VBLANK is set such that the active area plus
the blanking exceeds 65535, long_exp_shift becomes greater than 0. If
long_exp_shift is 7, and the frame length modulo 128 is 48 or greater,
integer division truncates the shifted exposure value so it exactly equals
the shifted frame length in imx708_set_ctrl(). This leaves a 0-line blanking
margin instead of the required 48 lines, which could cause sensor
malfunction or I2C errors.

[ ... ]
> +static int imx708_power_off(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct imx708 *imx708 = to_imx708(sd);
> +
> +	gpiod_set_value_cansleep(imx708->reset_gpio, 1);
> +	regulator_bulk_disable(ARRAY_SIZE(imx708_supply_name),
> +			       imx708->supplies);
> +	clk_disable_unprepare(imx708->inclk);

[Severity: High]
Does this disable the regulators before the clock? Calling
regulator_bulk_disable() while the external clock is still running can
backpower the unpowered sensor IC through the clock pin's ESD protection
diodes. This backwards sequencing in imx708_power_off() might cause
latch-up, excessive current draw, or physical damage to the hardware.

[ ... ]
> +static int imx708_probe(struct i2c_client *client)
> +{
[ ... ]
> +	ret = v4l2_async_register_subdev_sensor(&imx708->sd);
> +	if (ret < 0) {
> +		dev_err_probe(dev, ret, "failed to register sensor sub-device\n");
> +		goto error_subdev_cleanup;
> +	}
> +
> +	pm_runtime_idle(dev);
> +	pm_runtime_set_autosuspend_delay(dev, 5000);
> +	pm_runtime_use_autosuspend(dev);
> +
> +	return 0;

[Severity: Medium]
Might this bypass the autosuspend delay? By calling pm_runtime_idle() before
pm_runtime_use_autosuspend() is enabled in imx708_probe(), the PM core sees
a 0 usage count and immediately invokes the synchronous suspend callback.
This causes the sensor to be powered off immediately during probe instead of
remaining active for the intended 5000ms delay.

[ ... ]
> +static const struct dev_pm_ops imx708_pm_ops = {
> +	SET_RUNTIME_PM_OPS(imx708_power_off, imx708_power_on, NULL)
> +};

[Severity: Medium]
Is it intentional to omit the system sleep operations here? Since the PM
operations structure only sets SET_RUNTIME_PM_OPS, the PM core will not
force the device to suspend during system sleep. If the host system suspends
while the camera is active, the sensor regulators and clocks will remain
fully powered on, causing a power leak.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-imx708-v2-0-7d3d7b57779d@ideasonboard.com?part=3

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

* Re: [PATCH v2 2/3] media: ccs-pll: Support optional input for VT pixel rate
  2026-08-07 12:55 ` [PATCH v2 2/3] media: ccs-pll: Support optional input for VT pixel rate Jai Luthra
@ 2026-08-07 13:15   ` sashiko-bot
  0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-08-07 13:15 UTC (permalink / raw)
  To: Jai Luthra; +Cc: media-ci, conor+dt, robh, devicetree

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] State reuse logic bug: Repurposing the output field `pixel_rate_pixel_array` as an optional input causes subsequent PLL recalculations to reuse the previous achieved rate, breaking dynamic adaptation.
- [High] Missing validation: The DUAL_PLL configuration path bypasses hardware limit checks for FIFO derating and overrating.
--

--- Patch [2]: [PATCH v2 2/3] media: ccs-pll: Support optional input for VT pixel rate ---
commit 61f223eeeb41f300a2760175cdb5d9c15c35bf95
Author: Jai Luthra <jai.luthra@ideasonboard.com>

media: ccs-pll: Support optional input for VT pixel rate

This commit adds support for an optional target VT pixel rate input when
the PLL is configured in DUAL_MODE. It utilizes the pixel_rate_pixel_array
field to pass this input target, falling back to pixel_rate_csi if it is
not provided.

> When the PLL is configured in DUAL_MODE (separate VT and OP trees) and
> the sensor's FIFOs allow derating and/or overrating, the VT pixel rate
> might be higher or lower than OP pixel rate.

[Severity: High]
Does this bypass the hardware capability validation for FIFO derating and
overrating?

When CCS_PLL_FLAG_DUAL_PLL is set, ccs_pll_calculate() breaks out of the loop
early and returns successfully after calling ccs_pll_calculate_vt_tree(). This
completely skips the call to check_ext_bounds().

Could this allow arbitrary diverging VT pixel rates to be applied that exceed
the sensor's FIFO capabilities without throwing an error, leading to FIFO
underflows or overflows?

> diff --git a/drivers/media/i2c/ccs-pll.c b/drivers/media/i2c/ccs-pll.c
> index 1605cfa5db19d..9aa164994cc73 100644
> --- a/drivers/media/i2c/ccs-pll.c
> +++ b/drivers/media/i2c/ccs-pll.c

[ ... ]

> @@ -388,6 +388,14 @@ __ccs_pll_calculate_vt_tree(struct device *dev,
>  	return 0;
>  }
>  
> +static inline u32 ccs_pll_vt_pixel_rate(const struct ccs_pll *pll)
> +{
> +	if (pll->pixel_rate_pixel_array)
> +		return pll->pixel_rate_pixel_array;

[Severity: High]
Does this cause a state reuse issue for subsequent PLL recalculations?

This helper treats pixel_rate_pixel_array as an input target. However,
__ccs_pll_calculate_vt_tree() writes the achieved VT rate back into this
same field:

drivers/media/i2c/ccs-pll.c:__ccs_pll_calculate_vt_tree() {
    ...
    pll->pixel_rate_pixel_array =
        pll_bk->pix_clk_freq_hz * pll->vt_lanes;
    ...
}

Callers like ccs_pll_update() in ccs-core.c maintain the struct ccs_pll across
multiple calls (such as during V4L2 link frequency changes) without zeroing
this field.

Since this helper unconditionally prefers pixel_rate_pixel_array if non-zero,
won't subsequent configuration changes silently use the stale achieved rate
from a previous calculation instead of the intended target?

> +
> +	return pll->pixel_rate_csi;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-imx708-v2-0-7d3d7b57779d@ideasonboard.com?part=2

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

end of thread, other threads:[~2026-08-07 13:15 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-07 12:55 [PATCH v2 0/3] media: Add bindings and driver for Sony IMX708 Jai Luthra
2026-08-07 12:55 ` [PATCH v2 1/3] dt-bindings: media: i2c: Add imx708 image sensor Jai Luthra
2026-08-07 13:04   ` sashiko-bot
2026-08-07 12:55 ` [PATCH v2 2/3] media: ccs-pll: Support optional input for VT pixel rate Jai Luthra
2026-08-07 13:15   ` sashiko-bot
2026-08-07 12:55 ` [PATCH v2 3/3] media: i2c: Add a driver for Sony IMX708 Jai Luthra
2026-08-07 13:12   ` sashiko-bot

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