Devicetree
 help / color / mirror / Atom feed
From: Jai Luthra <jai.luthra@ideasonboard.com>
To: Mauro Carvalho Chehab <mchehab@kernel.org>,
	 Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	 Conor Dooley <conor+dt@kernel.org>,
	 Sakari Ailus <sakari.ailus@linux.intel.com>
Cc: Dave Stevenson <dave.stevenson@raspberrypi.com>,
	 Naushir Patuck <naush@raspberrypi.com>,
	 David Plowman <david.plowman@raspberrypi.com>,
	 Kieran Bingham <kieran.bingham@ideasonboard.com>,
	 Laurent Pinchart <laurent.pinchart@ideasonboard.com>,
	 linux-media@vger.kernel.org, devicetree@vger.kernel.org,
	 Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>,
	 Jai Luthra <jai.luthra@ideasonboard.com>,
	 Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
Subject: [PATCH v2 3/3] media: i2c: Add a driver for Sony IMX708
Date: Fri, 07 Aug 2026 18:25:20 +0530	[thread overview]
Message-ID: <20260807-imx708-v2-3-7d3d7b57779d@ideasonboard.com> (raw)
In-Reply-To: <20260807-imx708-v2-0-7d3d7b57779d@ideasonboard.com>

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


  parent reply	other threads:[~2026-08-07 12:56 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` Jai Luthra [this message]
2026-08-07 13:12   ` [PATCH v2 3/3] media: i2c: Add a driver for Sony IMX708 sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260807-imx708-v2-3-7d3d7b57779d@ideasonboard.com \
    --to=jai.luthra@ideasonboard.com \
    --cc=conor+dt@kernel.org \
    --cc=dave.stevenson@raspberrypi.com \
    --cc=david.plowman@raspberrypi.com \
    --cc=devicetree@vger.kernel.org \
    --cc=kernel-list@raspberrypi.com \
    --cc=kieran.bingham@ideasonboard.com \
    --cc=krzk+dt@kernel.org \
    --cc=laurent.pinchart@ideasonboard.com \
    --cc=linux-media@vger.kernel.org \
    --cc=mchehab@kernel.org \
    --cc=naush@raspberrypi.com \
    --cc=nick.hollinghurst@raspberrypi.com \
    --cc=robh@kernel.org \
    --cc=sakari.ailus@linux.intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox