linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
@ 2025-05-15 22:41 Pratap Nirujogi
  0 siblings, 0 replies; 60+ messages in thread
From: Pratap Nirujogi @ 2025-05-15 22:41 UTC (permalink / raw)
  To: mchehab, sakari.ailus, laurent.pinchart, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson
  Cc: linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, Bin Du, Pratap Nirujogi

From: Bin Du <Bin.Du@amd.com>

Add driver for OmniVision 5.2M OV05C10 sensor. This driver
supports only the full size normal 2888x1808@30fps 2-lane
sensor profile.

Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
Co-developed-by: Bin Du <bin.du@amd.com>
Signed-off-by: Bin Du <bin.du@amd.com>
Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
---
Changes v2 -> v3:

* Update "refclk" property variable as "clock-frequency".
* Update sensor GPIO connector id name.
* Fix sensor v4l2 compliance issue.
* Fix license info.
* Address review comments.

 MAINTAINERS                 |    8 +
 drivers/media/i2c/Kconfig   |   10 +
 drivers/media/i2c/Makefile  |    1 +
 drivers/media/i2c/ov05c10.c | 1061 +++++++++++++++++++++++++++++++++++
 4 files changed, 1080 insertions(+)
 create mode 100644 drivers/media/i2c/ov05c10.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 3563492e4eba..a7c4b4820f75 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17937,6 +17937,14 @@ T:	git git://linuxtv.org/media.git
 F:	Documentation/devicetree/bindings/media/i2c/ovti,ov02a10.yaml
 F:	drivers/media/i2c/ov02a10.c
 
+OMNIVISION OV05C10 SENSOR DRIVER
+M:	Nirujogi Pratap <pratap.nirujogi@amd.com>
+M:	Bin Du <bin.du@amd.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+T:	git git://linuxtv.org/media.git
+F:	drivers/media/i2c/ov05c10.c
+
 OMNIVISION OV08D10 SENSOR DRIVER
 M:	Jimmy Su <jimmy.su@intel.com>
 L:	linux-media@vger.kernel.org
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index e45ba127069f..c2d7bc9e950a 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -356,6 +356,16 @@ config VIDEO_OV02A10
 	  To compile this driver as a module, choose M here: the
 	  module will be called ov02a10.
 
+config VIDEO_OV05C10
+	tristate "OmniVision OV05C10 sensor support"
+	select V4L2_CCI_I2C
+	help
+	  This is a Video4Linux2 sensor driver for the OmniVision
+	  OV05C10 camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called OV05C10.
+
 config VIDEO_OV08D10
         tristate "OmniVision OV08D10 sensor support"
         help
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 6c23a4463527..a3ec22164389 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -83,6 +83,7 @@ obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o
 obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o
 obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
 obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
+obj-$(CONFIG_VIDEO_OV05C10) += ov05c10.o
 obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
 obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
 obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
diff --git a/drivers/media/i2c/ov05c10.c b/drivers/media/i2c/ov05c10.c
new file mode 100644
index 000000000000..9a1e493c4073
--- /dev/null
+++ b/drivers/media/i2c/ov05c10.c
@@ -0,0 +1,1061 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright (C) 2025 Advanced Micro Devices, Inc.
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/units.h>
+#include <media/v4l2-cci.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+
+#define DRV_NAME			"ov05c10"
+#define OV05C10_REF_CLK			(24 * HZ_PER_MHZ)
+
+#define MODE_WIDTH  2888
+#define MODE_HEIGHT 1808
+
+#define PAGE_NUM_MASK			0xff000000
+#define PAGE_NUM_SHIFT			24
+#define REG_ADDR_MASK			0x00ffffff
+
+#define OV05C10_SYSCTL_PAGE		(0 << PAGE_NUM_SHIFT)
+#define OV05C10_CISCTL_PAGE		(1 << PAGE_NUM_SHIFT)
+#define OV05C10_ISPCTL_PAGE		(4 << PAGE_NUM_SHIFT)
+
+/* Chip ID */
+#define OV05C10_REG_CHIP_ID		(CCI_REG24(0x00) | OV05C10_SYSCTL_PAGE)
+#define OV05C10_CHIP_ID			0x43055610
+
+/* Control registers */
+#define OV05C10_REG_TRIGGER		(CCI_REG8(0x01) | OV05C10_CISCTL_PAGE)
+#define OV05C_REG_TRIGGER_START		BIT(0)
+
+/* Exposure control */
+#define OV05C10_REG_EXPOSURE		(CCI_REG24(0x02) | OV05C10_CISCTL_PAGE)
+#define OV05C10_EXPOSURE_MAX_MARGIN	33
+#define OV05C10_EXPOSURE_MIN		4
+#define OV05C10_EXPOSURE_STEP		1
+#define OV05C10_EXPOSURE_DEFAULT	0x40
+
+/* V_TIMING internal */
+#define OV05C10_REG_VTS			(CCI_REG16(0x05) | OV05C10_CISCTL_PAGE)
+#define OV05C10_VTS_30FPS		1860
+#define OV05C10_VTS_MAX			0x7fff
+
+/* Test Pattern Control */
+#define OV05C10_REG_TEST_PATTERN	(CCI_REG8(0x12) | OV05C10_ISPCTL_PAGE)
+#define OV05C10_TEST_PATTERN_ENABLE	BIT(0)
+#define OV05C10_REG_TEST_PATTERN_CTL	(CCI_REG8(0xf3) | OV05C10_ISPCTL_PAGE)
+#define OV05C10_REG_TEST_PATTERN_XXX	BIT(0)
+
+/* Digital gain control */
+#define OV05C10_REG_DGTL_GAIN_H		(CCI_REG8(0x21) | OV05C10_CISCTL_PAGE)
+#define OV05C10_REG_DGTL_GAIN_L		(CCI_REG8(0x22) | OV05C10_CISCTL_PAGE)
+
+#define OV05C10_DGTL_GAIN_MIN		0x40
+#define OV05C10_DGTL_GAIN_MAX		0xff
+#define OV05C10_DGTL_GAIN_DEFAULT	0x40
+#define OV05C10_DGTL_GAIN_STEP		1
+
+#define OV05C10_DGTL_GAIN_L_MASK	0xff
+#define OV05C10_DGTL_GAIN_H_SHIFT	8
+#define OV05C10_DGTL_GAIN_H_MASK	0xff00
+
+/* Analog gain control */
+#define OV05C10_REG_ANALOG_GAIN		(CCI_REG8(0x24) | OV05C10_CISCTL_PAGE)
+#define OV05C10_ANA_GAIN_MIN		0x80
+#define OV05C10_ANA_GAIN_MAX		0x07c0
+#define OV05C10_ANA_GAIN_STEP		1
+#define OV05C10_ANA_GAIN_DEFAULT	0x80
+
+/* H TIMING internal */
+#define OV05C10_REG_HTS			(CCI_REG16(0x37) | OV05C10_CISCTL_PAGE)
+#define OV05C10_HTS_30FPS		0x0280
+
+/* Page selection */
+#define OV05C10_REG_PAGE_CTL		CCI_REG8(0xfd)
+
+#define NUM_OF_PADS 1
+
+#define OV05C10_GET_PAGE_NUM(reg)	(((reg) & PAGE_NUM_MASK) >>\
+					 PAGE_NUM_SHIFT)
+#define OV05C10_GET_REG_ADDR(reg)	((reg) & REG_ADDR_MASK)
+
+enum {
+	OV05C10_LINK_FREQ_900MHZ_INDEX,
+};
+
+struct ov05c10_reg_list {
+	u32 num_of_regs;
+	const struct cci_reg_sequence *regs;
+};
+
+/* Mode : resolution and related config&values */
+struct ov05c10_mode {
+	/* Frame width */
+	u32 width;
+	/* Frame height */
+	u32 height;
+	/* number of lanes */
+	u32 lanes;
+
+	/* V-timing */
+	u32 vts_def;
+	u32 vts_min;
+
+	/* HTS */
+	u32 hts;
+
+	/* Index of Link frequency config to be used */
+	u32 link_freq_index;
+
+	/* Default register values */
+	struct ov05c10_reg_list reg_list;
+};
+
+static const s64 ov05c10_link_frequencies[] = {
+	925 * HZ_PER_MHZ,
+};
+
+/* 2888x1808 30fps, 1800mbps, 2lane, 24mhz */
+static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
+	{ CCI_REG8(0xfd),  0x00 },
+	{ CCI_REG8(0x20),  0x00 },
+	{ CCI_REG8(0xfd),  0x00 },
+	{ CCI_REG8(0x20),  0x0b },
+	{ CCI_REG8(0xc1),  0x09 },
+	{ CCI_REG8(0x21),  0x06 },
+	{ CCI_REG8(0x14),  0x78 },
+	{ CCI_REG8(0xe7),  0x03 },
+	{ CCI_REG8(0xe7),  0x00 },
+	{ CCI_REG8(0x21),  0x00 },
+	{ CCI_REG8(0xfd),  0x01 },
+	{ CCI_REG8(0x03),  0x00 },
+	{ CCI_REG8(0x04),  0x06 },
+	{ CCI_REG8(0x05),  0x07 },
+	{ CCI_REG8(0x06),  0x44 },
+	{ CCI_REG8(0x07),  0x08 },
+	{ CCI_REG8(0x1b),  0x01 },
+	{ CCI_REG8(0x24),  0xff },
+	{ CCI_REG8(0x32),  0x03 },
+	{ CCI_REG8(0x42),  0x5d },
+	{ CCI_REG8(0x43),  0x08 },
+	{ CCI_REG8(0x44),  0x81 },
+	{ CCI_REG8(0x46),  0x5f },
+	{ CCI_REG8(0x48),  0x18 },
+	{ CCI_REG8(0x49),  0x04 },
+	{ CCI_REG8(0x5c),  0x18 },
+	{ CCI_REG8(0x5e),  0x13 },
+	{ CCI_REG8(0x70),  0x15 },
+	{ CCI_REG8(0x77),  0x35 },
+	{ CCI_REG8(0x79),  0x00 },
+	{ CCI_REG8(0x7b),  0x08 },
+	{ CCI_REG8(0x7d),  0x08 },
+	{ CCI_REG8(0x7e),  0x08 },
+	{ CCI_REG8(0x7f),  0x08 },
+	{ CCI_REG8(0x90),  0x37 },
+	{ CCI_REG8(0x91),  0x05 },
+	{ CCI_REG8(0x92),  0x18 },
+	{ CCI_REG8(0x93),  0x27 },
+	{ CCI_REG8(0x94),  0x05 },
+	{ CCI_REG8(0x95),  0x38 },
+	{ CCI_REG8(0x9b),  0x00 },
+	{ CCI_REG8(0x9c),  0x06 },
+	{ CCI_REG8(0x9d),  0x28 },
+	{ CCI_REG8(0x9e),  0x06 },
+	{ CCI_REG8(0xb2),  0x0f },
+	{ CCI_REG8(0xb3),  0x29 },
+	{ CCI_REG8(0xbf),  0x3c },
+	{ CCI_REG8(0xc2),  0x04 },
+	{ CCI_REG8(0xc4),  0x00 },
+	{ CCI_REG8(0xca),  0x20 },
+	{ CCI_REG8(0xcb),  0x20 },
+	{ CCI_REG8(0xcc),  0x28 },
+	{ CCI_REG8(0xcd),  0x28 },
+	{ CCI_REG8(0xce),  0x20 },
+	{ CCI_REG8(0xcf),  0x20 },
+	{ CCI_REG8(0xd0),  0x2a },
+	{ CCI_REG8(0xd1),  0x2a },
+	{ CCI_REG8(0xfd),  0x0f },
+	{ CCI_REG8(0x00),  0x00 },
+	{ CCI_REG8(0x01),  0xa0 },
+	{ CCI_REG8(0x02),  0x48 },
+	{ CCI_REG8(0x07),  0x8f },
+	{ CCI_REG8(0x08),  0x70 },
+	{ CCI_REG8(0x09),  0x01 },
+	{ CCI_REG8(0x0b),  0x40 },
+	{ CCI_REG8(0x0d),  0x07 },
+	{ CCI_REG8(0x11),  0x33 },
+	{ CCI_REG8(0x12),  0x77 },
+	{ CCI_REG8(0x13),  0x66 },
+	{ CCI_REG8(0x14),  0x65 },
+	{ CCI_REG8(0x15),  0x37 },
+	{ CCI_REG8(0x16),  0xbf },
+	{ CCI_REG8(0x17),  0xff },
+	{ CCI_REG8(0x18),  0xff },
+	{ CCI_REG8(0x19),  0x12 },
+	{ CCI_REG8(0x1a),  0x10 },
+	{ CCI_REG8(0x1c),  0x77 },
+	{ CCI_REG8(0x1d),  0x77 },
+	{ CCI_REG8(0x20),  0x0f },
+	{ CCI_REG8(0x21),  0x0f },
+	{ CCI_REG8(0x22),  0x0f },
+	{ CCI_REG8(0x23),  0x0f },
+	{ CCI_REG8(0x2b),  0x20 },
+	{ CCI_REG8(0x2c),  0x20 },
+	{ CCI_REG8(0x2d),  0x04 },
+	{ CCI_REG8(0xfd),  0x03 },
+	{ CCI_REG8(0x9d),  0x0f },
+	{ CCI_REG8(0x9f),  0x40 },
+	{ CCI_REG8(0xfd),  0x00 },
+	{ CCI_REG8(0x20),  0x1b },
+	{ CCI_REG8(0xfd),  0x04 },
+	{ CCI_REG8(0x19),  0x60 },
+	{ CCI_REG8(0xfd),  0x02 },
+	{ CCI_REG8(0x75),  0x05 },
+	{ CCI_REG8(0x7f),  0x06 },
+	{ CCI_REG8(0x9a),  0x03 },
+	{ CCI_REG8(0xa2),  0x07 },
+	{ CCI_REG8(0xa3),  0x10 },
+	{ CCI_REG8(0xa5),  0x02 },
+	{ CCI_REG8(0xa6),  0x0b },
+	{ CCI_REG8(0xa7),  0x48 },
+	{ CCI_REG8(0xfd),  0x07 },
+	{ CCI_REG8(0x42),  0x00 },
+	{ CCI_REG8(0x43),  0x80 },
+	{ CCI_REG8(0x44),  0x00 },
+	{ CCI_REG8(0x45),  0x80 },
+	{ CCI_REG8(0x46),  0x00 },
+	{ CCI_REG8(0x47),  0x80 },
+	{ CCI_REG8(0x48),  0x00 },
+	{ CCI_REG8(0x49),  0x80 },
+	{ CCI_REG8(0x00),  0xf7 },
+	{ CCI_REG8(0xfd),  0x00 },
+	{ CCI_REG8(0xe7),  0x03 },
+	{ CCI_REG8(0xe7),  0x00 },
+	{ CCI_REG8(0xfd),  0x00 },
+	{ CCI_REG8(0x93),  0x18 },
+	{ CCI_REG8(0x94),  0xff },
+	{ CCI_REG8(0x95),  0xbd },
+	{ CCI_REG8(0x96),  0x1a },
+	{ CCI_REG8(0x98),  0x04 },
+	{ CCI_REG8(0x99),  0x08 },
+	{ CCI_REG8(0x9b),  0x10 },
+	{ CCI_REG8(0x9c),  0x3f },
+	{ CCI_REG8(0xa1),  0x05 },
+	{ CCI_REG8(0xa4),  0x2f },
+	{ CCI_REG8(0xc0),  0x0c },
+	{ CCI_REG8(0xc1),  0x08 },
+	{ CCI_REG8(0xc2),  0x00 },
+	{ CCI_REG8(0xb6),  0x20 },
+	{ CCI_REG8(0xbb),  0x80 },
+	{ CCI_REG8(0xfd),  0x00 },
+	{ CCI_REG8(0xa0),  0x01 },
+	{ CCI_REG8(0xfd),  0x01 },
+};
+
+static const struct cci_reg_sequence mode_OV05C10_stream_on_regs[] = {
+	{ CCI_REG8(0xfd), 0x01 },
+	{ CCI_REG8(0x33), 0x03 },
+	{ CCI_REG8(0x01), 0x02 },
+	{ CCI_REG8(0xfd), 0x00 },
+	{ CCI_REG8(0x20), 0x1f },
+	{ CCI_REG8(0xfd), 0x01 },
+};
+
+static const struct cci_reg_sequence mode_OV05C10_stream_off_regs[] = {
+	{ CCI_REG8(0xfd), 0x00 },
+	{ CCI_REG8(0x20), 0x5b },
+	{ CCI_REG8(0xfd), 0x01 },
+	{ CCI_REG8(0x33), 0x02 },
+	{ CCI_REG8(0x01), 0x02 },
+};
+
+static const char * const ov05c10_test_pattern_menu[] = {
+	"Disabled",
+	"Vertical Color Bar Type 1",
+	"Vertical Color Bar Type 2",
+	"Vertical Color Bar Type 3",
+	"Vertical Color Bar Type 4"
+};
+
+/* Configurations for supported link frequencies */
+#define OV05C10_LINK_FREQ_900MHZ	(900 * HZ_PER_MHZ)
+
+/* Number of lanes supported */
+#define OV05C10_DATA_LANES		2
+
+/* Bits per sample of sensor output */
+#define OV05C10_BITS_PER_SAMPLE		10
+
+/*
+ * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
+ * data rate => double data rate; number of lanes => 2; bits per pixel => 10
+ */
+static u64 link_freq_to_pixel_rate(u64 f, u32 lane_nr)
+{
+	f *= 2 * lane_nr;
+	do_div(f, OV05C10_BITS_PER_SAMPLE);
+
+	return f;
+}
+
+/* Menu items for LINK_FREQ V4L2 control */
+static const s64 ov05c10_link_freq_menu_items[] = {
+	OV05C10_LINK_FREQ_900MHZ,
+};
+
+/* Mode configs, currently, only support 1 mode */
+static const struct ov05c10_mode supported_mode = {
+	.width = MODE_WIDTH,
+	.height = MODE_HEIGHT,
+	.vts_def = OV05C10_VTS_30FPS,
+	.vts_min = OV05C10_VTS_30FPS,
+	.hts = 640,
+	.lanes = 2,
+	.reg_list = {
+		.num_of_regs = ARRAY_SIZE(ov05c10_2888x1808_regs),
+		.regs = ov05c10_2888x1808_regs,
+	},
+	.link_freq_index = OV05C10_LINK_FREQ_900MHZ_INDEX,
+};
+
+struct ov05c10 {
+	struct v4l2_subdev sd;
+	struct media_pad pad;
+
+	/* V4L2 control handler */
+	struct v4l2_ctrl_handler ctrl_handler;
+
+	/* V4L2 Controls */
+	struct v4l2_ctrl *link_freq;
+	struct v4l2_ctrl *pixel_rate;
+	struct v4l2_ctrl *vblank;
+	struct v4l2_ctrl *hblank;
+	struct v4l2_ctrl *exposure;
+
+	struct regmap *regmap;
+
+	/* gpio descriptor */
+	struct gpio_desc *enable_gpio;
+
+	/* Current page for sensor register control */
+	int cur_page;
+};
+
+#define to_ov05c10(_sd)	container_of(_sd, struct ov05c10, sd)
+
+static int ov05c10_init_state(struct v4l2_subdev *sd,
+			      struct v4l2_subdev_state *sd_state)
+{
+	struct v4l2_mbus_framefmt *frame_fmt;
+	struct v4l2_subdev_format fmt = {
+		.which = V4L2_SUBDEV_FORMAT_TRY,
+		.format = {
+			.width = MODE_WIDTH,
+			.height = MODE_HEIGHT,
+			.code = MEDIA_BUS_FMT_SGRBG10_1X10,
+			.field = V4L2_FIELD_NONE,
+		}
+	};
+
+	frame_fmt = v4l2_subdev_state_get_format(sd_state, 0);
+	*frame_fmt = fmt.format;
+	return 0;
+}
+
+static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page, int *err)
+{
+	int ret = 0;
+
+	if (err && *err)
+		return *err;
+
+	if (page != ov05c10->cur_page) {
+		cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, &ret);
+		if (!ret)
+			ov05c10->cur_page = page;
+	}
+
+	if (err)
+		*err = ret;
+
+	return ret;
+}
+
+/* refer to the implementation of cci_read */
+static int ov05c10_reg_read(struct ov05c10 *ov05c10, u32 reg,
+			    u64 *val, int *err)
+{
+	u32 page;
+	u32 addr;
+	int ret = 0;
+
+	if (err && *err)
+		return *err;
+
+	page = OV05C10_GET_PAGE_NUM(reg);
+	addr = OV05C10_GET_REG_ADDR(reg);
+	ov05c10_switch_page(ov05c10, page, &ret);
+	cci_read(ov05c10->regmap, addr, val, &ret);
+	if (err)
+		*err = ret;
+
+	return ret;
+}
+
+/* refer to the implementation of cci_write */
+static int ov05c10_reg_write(struct ov05c10 *ov05c10, u32 reg,
+			     u64 val, int *err)
+{
+	u32 page;
+	u32 addr;
+	int ret = 0;
+
+	if (err && *err)
+		return *err;
+
+	page = OV05C10_GET_PAGE_NUM(reg);
+	addr = OV05C10_GET_REG_ADDR(reg);
+	ov05c10_switch_page(ov05c10, page, &ret);
+	cci_write(ov05c10->regmap, addr, val, &ret);
+	if (err)
+		*err = ret;
+
+	return ret;
+}
+
+static int ov05c10_update_vblank(struct ov05c10 *ov05c10, u32 vblank)
+{
+	const struct ov05c10_mode *mode = &supported_mode;
+	u64 val;
+	int ret = 0;
+
+	val = mode->height + vblank;
+	ov05c10_reg_write(ov05c10, OV05C10_REG_VTS, val, &ret);
+	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
+			  OV05C_REG_TRIGGER_START, &ret);
+
+	return ret;
+}
+
+static int ov05c10_update_exposure(struct ov05c10 *ov05c10, u32 exposure)
+{
+	int ret = 0;
+
+	ov05c10_reg_write(ov05c10, OV05C10_REG_EXPOSURE, exposure, &ret);
+	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
+			  OV05C_REG_TRIGGER_START, &ret);
+
+	return ret;
+}
+
+static int ov05c10_update_analog_gain(struct ov05c10 *ov05c10, u32 a_gain)
+{
+	int ret = 0;
+
+	ov05c10_reg_write(ov05c10, OV05C10_REG_ANALOG_GAIN, a_gain, &ret);
+	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
+			  OV05C_REG_TRIGGER_START, &ret);
+
+	return ret;
+}
+
+static int ov05c10_update_digital_gain(struct ov05c10 *ov05c10, u32 d_gain)
+{
+	u64 val;
+	int ret = 0;
+
+	val = d_gain & OV05C10_DGTL_GAIN_L_MASK;
+	ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_L, val, &ret);
+
+	val = (d_gain & OV05C10_DGTL_GAIN_H_MASK) >> OV05C10_DGTL_GAIN_H_SHIFT;
+	ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_H, val, &ret);
+
+	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
+			  OV05C_REG_TRIGGER_START, &ret);
+
+	return ret;
+}
+
+static int ov05c10_enable_test_pattern(struct ov05c10 *ov05c10, u32 pattern)
+{
+	u64 val;
+	int ret = 0;
+
+	if (pattern) {
+		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
+				 &val, &ret);
+		ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
+				  val | OV05C10_REG_TEST_PATTERN_XXX, &ret);
+		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
+		val |= OV05C10_TEST_PATTERN_ENABLE;
+	} else {
+		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
+		val &= ~OV05C10_TEST_PATTERN_ENABLE;
+	}
+
+	ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN, val, &ret);
+	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
+			  OV05C_REG_TRIGGER_START, &ret);
+
+	return ret;
+}
+
+static int ov05c10_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct ov05c10 *ov05c10 = container_of(ctrl->handler,
+					       struct ov05c10, ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
+	const struct ov05c10_mode *mode = &supported_mode;
+	s64 max;
+	int ret = 0;
+
+	/* Propagate change of current control to all related controls */
+	if (ctrl->id == V4L2_CID_VBLANK) {
+		s64 cur_exp = ov05c10->exposure->cur.val;
+
+		/* Update max exposure while meeting expected vblanking */
+		max = mode->height + ctrl->val - OV05C10_EXPOSURE_MAX_MARGIN;
+		cur_exp = clamp(cur_exp, ov05c10->exposure->minimum, max);
+		ret = __v4l2_ctrl_modify_range(ov05c10->exposure,
+					       ov05c10->exposure->minimum,
+					       max, ov05c10->exposure->step,
+					       cur_exp);
+		if (!ret)
+			return ret;
+	}
+
+	/*
+	 * Applying V4L2 control value only happens
+	 * when power is up for streaming
+	 */
+	if (!pm_runtime_get_if_in_use(&client->dev))
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_ANALOGUE_GAIN:
+		ret = ov05c10_update_analog_gain(ov05c10, ctrl->val);
+		break;
+	case V4L2_CID_DIGITAL_GAIN:
+		ret = ov05c10_update_digital_gain(ov05c10, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		ret = ov05c10_update_exposure(ov05c10, ctrl->val);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = ov05c10_update_vblank(ov05c10, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		ret = ov05c10_enable_test_pattern(ov05c10, ctrl->val);
+		break;
+	default:
+		ret = -ENOTTY;
+		dev_err(&client->dev,
+			"ctrl(id:0x%x,val:0x%x) is not handled\n",
+			ctrl->id, ctrl->val);
+		break;
+	}
+
+	pm_runtime_put(&client->dev);
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops ov05c10_ctrl_ops = {
+	.s_ctrl = ov05c10_set_ctrl,
+};
+
+static int ov05c10_enum_mbus_code(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *sd_state,
+				  struct v4l2_subdev_mbus_code_enum *code)
+{
+	/* Only one bayer order(GRBG) is supported */
+	if (code->index > 0)
+		return -EINVAL;
+
+	code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
+
+	return 0;
+}
+
+static int ov05c10_enum_frame_size(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *sd_state,
+				   struct v4l2_subdev_frame_size_enum *fse)
+{
+	/* ov05c10 driver currently only supports 1 mode*/
+	if (fse->index != 0)
+		return -EINVAL;
+
+	if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
+		return -EINVAL;
+
+	fse->min_width = supported_mode.width;
+	fse->max_width = fse->min_width;
+	fse->min_height = supported_mode.height;
+	fse->max_height = fse->min_height;
+
+	return 0;
+}
+
+static void ov05c10_update_pad_format(const struct ov05c10_mode *mode,
+				      struct v4l2_subdev_format *fmt)
+{
+	fmt->format.width = mode->width;
+	fmt->format.height = mode->height;
+	fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
+	fmt->format.field = V4L2_FIELD_NONE;
+}
+
+static int ov05c10_set_pad_format(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *sd_state,
+				  struct v4l2_subdev_format *fmt)
+{
+	struct v4l2_mbus_framefmt *framefmt;
+	struct ov05c10 *ov05c10 = to_ov05c10(sd);
+	const struct ov05c10_mode *mode;
+	s32 vblank_def;
+	s32 vblank_min;
+	s64 pixel_rate;
+	s64 link_freq;
+	s64 h_blank;
+
+	/* Only one raw bayer(GRBG) order is supported */
+	if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10)
+		fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
+
+	mode = &supported_mode;
+	ov05c10_update_pad_format(mode, fmt);
+	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+		framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
+		*framefmt = fmt->format;
+	} else {
+		__v4l2_ctrl_s_ctrl(ov05c10->link_freq, mode->link_freq_index);
+		link_freq = ov05c10_link_freq_menu_items[mode->link_freq_index];
+		pixel_rate = link_freq_to_pixel_rate(link_freq,
+						     mode->lanes);
+		__v4l2_ctrl_s_ctrl_int64(ov05c10->pixel_rate, pixel_rate);
+
+		/* Update limits and set FPS to default */
+		vblank_def = mode->vts_def - mode->height;
+		vblank_min = mode->vts_min - mode->height;
+		__v4l2_ctrl_modify_range(ov05c10->vblank, vblank_min,
+					 OV05C10_VTS_MAX - mode->height,
+					 1, vblank_def);
+		__v4l2_ctrl_s_ctrl(ov05c10->vblank, vblank_def);
+		h_blank = mode->hts;
+		__v4l2_ctrl_modify_range(ov05c10->hblank, h_blank,
+					 h_blank, 1, h_blank);
+	}
+
+	return 0;
+}
+
+static int ov05c10_start_streaming(struct ov05c10 *ov05c10)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
+	const struct ov05c10_mode *mode = &supported_mode;
+	const struct ov05c10_reg_list *reg_list;
+	int ret = 0;
+
+	/* Apply default values of current mode */
+	reg_list = &mode->reg_list;
+	cci_multi_reg_write(ov05c10->regmap, reg_list->regs,
+			    reg_list->num_of_regs, &ret);
+	if (ret) {
+		dev_err(&client->dev, "fail to set mode, ret: %d\n", ret);
+		return ret;
+	}
+
+	/* Apply customized values from user */
+	ret =  __v4l2_ctrl_handler_setup(ov05c10->sd.ctrl_handler);
+	if (ret) {
+		dev_err(&client->dev, "failed to setup v4l2 handler %d\n", ret);
+		return ret;
+	}
+
+	cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_on_regs,
+			    ARRAY_SIZE(mode_OV05C10_stream_on_regs), &ret);
+	if (ret)
+		dev_err(&client->dev, "fail to start the streaming\n");
+
+	return ret;
+}
+
+static int ov05c10_stop_streaming(struct ov05c10 *ov05c10)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
+	int ret = 0;
+
+	cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_off_regs,
+			    ARRAY_SIZE(mode_OV05C10_stream_off_regs), &ret);
+	if (ret)
+		dev_err(&client->dev, "fail to stop the streaming\n");
+
+	return ret;
+}
+
+static void ov05c10_sensor_power_set(struct ov05c10 *ov05c10, bool on)
+{
+	if (on) {
+		gpiod_set_value(ov05c10->enable_gpio, 0);
+		usleep_range(10, 20);
+
+		gpiod_set_value(ov05c10->enable_gpio, 1);
+		usleep_range(1000, 2000);
+	} else {
+		gpiod_set_value(ov05c10->enable_gpio, 0);
+		usleep_range(10, 20);
+	}
+}
+
+static int ov05c10_enable_streams(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state, u32 pad,
+				  u64 streams_mask)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct ov05c10 *ov05c10 = to_ov05c10(sd);
+	int ret = 0;
+
+	ret = pm_runtime_resume_and_get(&client->dev);
+	if (ret < 0)
+		return ret;
+
+	ov05c10->cur_page = -1;
+
+	ret = ov05c10_start_streaming(ov05c10);
+	if (ret)
+		goto err_rpm_put;
+
+	return 0;
+
+err_rpm_put:
+	pm_runtime_put(&client->dev);
+	return ret;
+}
+
+static int ov05c10_disable_streams(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state, u32 pad,
+				   u64 streams_mask)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct ov05c10 *ov05c10 = to_ov05c10(sd);
+
+	ov05c10_stop_streaming(ov05c10);
+	pm_runtime_put(&client->dev);
+
+	return 0;
+}
+
+static const struct v4l2_subdev_video_ops ov05c10_video_ops = {
+	.s_stream = v4l2_subdev_s_stream_helper,
+};
+
+static const struct v4l2_subdev_pad_ops ov05c10_pad_ops = {
+	.enum_mbus_code = ov05c10_enum_mbus_code,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = ov05c10_set_pad_format,
+	.enum_frame_size = ov05c10_enum_frame_size,
+	.enable_streams = ov05c10_enable_streams,
+	.disable_streams = ov05c10_disable_streams,
+};
+
+static const struct v4l2_subdev_ops ov05c10_subdev_ops = {
+	.video = &ov05c10_video_ops,
+	.pad = &ov05c10_pad_ops,
+};
+
+static const struct media_entity_operations ov05c10_subdev_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+static const struct v4l2_subdev_internal_ops ov05c10_internal_ops = {
+	.init_state = ov05c10_init_state,
+};
+
+static int ov05c10_init_controls(struct ov05c10 *ov05c10)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
+	const struct ov05c10_mode *mode = &supported_mode;
+	struct v4l2_fwnode_device_properties props;
+	struct v4l2_ctrl_handler *ctrl_hdlr;
+	s64 pixel_rate_max;
+	s64 exposure_max;
+	s64 vblank_def;
+	s64 vblank_min;
+	u32 max_items;
+	s64 hblank;
+	int ret;
+
+	ret = v4l2_ctrl_handler_init(&ov05c10->ctrl_handler, 10);
+	if (ret)
+		return ret;
+
+	ctrl_hdlr = &ov05c10->ctrl_handler;
+
+	max_items = ARRAY_SIZE(ov05c10_link_freq_menu_items) - 1;
+	ov05c10->link_freq =
+		v4l2_ctrl_new_int_menu(ctrl_hdlr,
+				       NULL,
+				       V4L2_CID_LINK_FREQ,
+				       max_items,
+				       0,
+				       ov05c10_link_freq_menu_items);
+	if (ov05c10->link_freq)
+		ov05c10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	pixel_rate_max =
+		link_freq_to_pixel_rate(ov05c10_link_freq_menu_items[0],
+					supported_mode.lanes);
+	ov05c10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
+						V4L2_CID_PIXEL_RATE,
+						0, pixel_rate_max,
+						1, pixel_rate_max);
+
+	vblank_def = mode->vts_def - mode->height;
+	vblank_min = mode->vts_min - mode->height;
+	ov05c10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
+					    V4L2_CID_VBLANK,
+					    vblank_min,
+					    OV05C10_VTS_MAX - mode->height,
+					    1, vblank_def);
+
+	hblank = (mode->hts > mode->width) ? (mode->hts - mode->width) : 0;
+	ov05c10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
+					    V4L2_CID_HBLANK,
+					    hblank, hblank, 1, hblank);
+	if (ov05c10->hblank)
+		ov05c10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	exposure_max = mode->vts_def - OV05C10_EXPOSURE_MAX_MARGIN;
+	ov05c10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
+					      V4L2_CID_EXPOSURE,
+					      OV05C10_EXPOSURE_MIN,
+					      exposure_max,
+					      OV05C10_EXPOSURE_STEP,
+					      exposure_max);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+			  OV05C10_ANA_GAIN_MIN, OV05C10_ANA_GAIN_MAX,
+			  OV05C10_ANA_GAIN_STEP, OV05C10_ANA_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+			  OV05C10_DGTL_GAIN_MIN, OV05C10_DGTL_GAIN_MAX,
+			  OV05C10_DGTL_GAIN_STEP, OV05C10_DGTL_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov05c10_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(ov05c10_test_pattern_menu) - 1,
+				     0, 0, ov05c10_test_pattern_menu);
+
+	if (ctrl_hdlr->error) {
+		ret = ctrl_hdlr->error;
+		dev_err(&client->dev, "V4L2 control init failed (%d)\n", ret);
+		goto err_hdl_free;
+	}
+
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret)
+		goto err_hdl_free;
+
+	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov05c10_ctrl_ops,
+					      &props);
+	if (ret)
+		goto err_hdl_free;
+
+	ov05c10->sd.ctrl_handler = ctrl_hdlr;
+
+	return 0;
+
+err_hdl_free:
+	v4l2_ctrl_handler_free(ctrl_hdlr);
+
+	return ret;
+}
+
+static int ov05c10_parse_endpoint(struct device *dev,
+				  struct fwnode_handle *fwnode)
+{
+	struct v4l2_fwnode_endpoint bus_cfg = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY
+	};
+	struct fwnode_handle *ep;
+	unsigned long bitmap;
+	int ret;
+
+	ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
+	if (!ep) {
+		dev_err(dev, "Failed to get next endpoint\n");
+		return -ENXIO;
+	}
+
+	ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
+	fwnode_handle_put(ep);
+	if (ret)
+		return ret;
+
+	if (bus_cfg.bus.mipi_csi2.num_data_lanes != supported_mode.lanes) {
+		dev_err(dev,
+			"number of CSI2 data lanes %d is not supported\n",
+			bus_cfg.bus.mipi_csi2.num_data_lanes);
+		ret = -EINVAL;
+		goto err_endpoint_free;
+	}
+
+	ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
+				       bus_cfg.nr_of_link_frequencies,
+				       ov05c10_link_frequencies,
+				       ARRAY_SIZE(ov05c10_link_frequencies),
+				       &bitmap);
+	if (ret)
+		dev_err(dev, "v4l2_link_freq_to_bitmap fail with %d\n", ret);
+err_endpoint_free:
+	v4l2_fwnode_endpoint_free(&bus_cfg);
+
+	return ret;
+}
+
+static int ov05c10_probe(struct i2c_client *client)
+{
+	struct ov05c10 *ov05c10;
+	u32 clkfreq;
+	int ret;
+
+	ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), GFP_KERNEL);
+	if (!ov05c10)
+		return -ENOMEM;
+
+	struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
+
+	ret = fwnode_property_read_u32(fwnode, "clock-frequency", &clkfreq);
+	if (ret)
+		return  dev_err_probe(&client->dev, -EINVAL,
+				      "fail to get clock freq\n");
+	if (clkfreq != OV05C10_REF_CLK)
+		return dev_err_probe(&client->dev, -EINVAL,
+				     "fail invalid clock freq %u, %lu expected\n",
+				     clkfreq, OV05C10_REF_CLK);
+
+	ret = ov05c10_parse_endpoint(&client->dev, fwnode);
+	if (ret)
+		return dev_err_probe(&client->dev, -EINVAL,
+				     "fail to parse endpoint\n");
+
+	ov05c10->enable_gpio = devm_gpiod_get(&client->dev, "enable",
+					      GPIOD_OUT_LOW);
+	if (IS_ERR(ov05c10->enable_gpio))
+		return dev_err_probe(&client->dev,
+				     PTR_ERR(ov05c10->enable_gpio),
+				     "fail to get enable gpio\n");
+
+	v4l2_i2c_subdev_init(&ov05c10->sd, client, &ov05c10_subdev_ops);
+
+	ov05c10->regmap = devm_cci_regmap_init_i2c(client, 8);
+	if (IS_ERR(ov05c10->regmap))
+		return dev_err_probe(&client->dev, PTR_ERR(ov05c10->regmap),
+				     "fail to init cci\n");
+
+	ov05c10->cur_page = -1;
+
+	ret = ov05c10_init_controls(ov05c10);
+	if (ret)
+		return dev_err_probe(&client->dev, ret, "fail to init ctl\n");
+
+	ov05c10->sd.internal_ops = &ov05c10_internal_ops;
+	ov05c10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	ov05c10->sd.entity.ops = &ov05c10_subdev_entity_ops;
+	ov05c10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+	ov05c10->pad.flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&ov05c10->sd.entity, NUM_OF_PADS,
+				     &ov05c10->pad);
+	if (ret)
+		goto err_hdl_free;
+
+	ret = v4l2_subdev_init_finalize(&ov05c10->sd);
+	if (ret < 0)
+		goto err_media_entity_cleanup;
+
+	ret = v4l2_async_register_subdev_sensor(&ov05c10->sd);
+	if (ret)
+		goto err_media_entity_cleanup;
+
+	pm_runtime_set_active(&client->dev);
+	pm_runtime_enable(&client->dev);
+	pm_runtime_idle(&client->dev);
+	pm_runtime_set_autosuspend_delay(&client->dev, 1000);
+	pm_runtime_use_autosuspend(&client->dev);
+	return 0;
+
+err_media_entity_cleanup:
+	media_entity_cleanup(&ov05c10->sd.entity);
+
+err_hdl_free:
+	v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
+
+	return ret;
+}
+
+static void ov05c10_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ov05c10 *ov05c10 = to_ov05c10(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
+
+	pm_runtime_disable(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+static int ov05c10_runtime_resume(struct device *dev)
+{
+	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+	struct ov05c10 *ov05c10 = to_ov05c10(sd);
+
+	ov05c10_sensor_power_set(ov05c10, true);
+	return 0;
+}
+
+static int ov05c10_runtime_suspend(struct device *dev)
+{
+	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+	struct ov05c10 *ov05c10 = to_ov05c10(sd);
+
+	ov05c10_sensor_power_set(ov05c10, false);
+	return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(ov05c10_pm_ops, ov05c10_runtime_suspend,
+				 ov05c10_runtime_resume, NULL);
+
+static const struct i2c_device_id ov05c10_i2c_ids[] = {
+	{"ov05c10", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ov05c10_i2c_ids);
+
+static struct i2c_driver ov05c10_i2c_driver = {
+	.driver = {
+		.name = DRV_NAME,
+		.pm = pm_ptr(&ov05c10_pm_ops),
+	},
+	.id_table = ov05c10_i2c_ids,
+	.probe = ov05c10_probe,
+	.remove = ov05c10_remove,
+};
+
+module_i2c_driver(ov05c10_i2c_driver);
+
+MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
+MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@amd.com>");
+MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
+MODULE_DESCRIPTION("OmniVision OV05C1010 sensor driver");
+MODULE_LICENSE("GPL");
-- 
2.43.0


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

* [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
@ 2025-06-09 19:42 Pratap Nirujogi
  2025-06-13  4:55 ` Hao Yao
  2025-06-15  0:09 ` Laurent Pinchart
  0 siblings, 2 replies; 60+ messages in thread
From: Pratap Nirujogi @ 2025-06-09 19:42 UTC (permalink / raw)
  To: mchehab, sakari.ailus, laurent.pinchart, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen
  Cc: linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, Pratap Nirujogi

Add driver for OmniVision 5.2M OV05C10 sensor. This driver
supports only the full size normal 2888x1808@30fps 2-lane
sensor profile.

Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
Co-developed-by: Bin Du <bin.du@amd.com>
Signed-off-by: Bin Du <bin.du@amd.com>
Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
---
Changes v2 -> v3:

* Update "refclk" property variable as "clock-frequency".
* Update sensor GPIO connector id name.
* Fix sensor v4l2 compliance issue.
* Fix license info.
* Address review comments.

 MAINTAINERS                 |    8 +
 drivers/media/i2c/Kconfig   |   10 +
 drivers/media/i2c/Makefile  |    1 +
 drivers/media/i2c/ov05c10.c | 1061 +++++++++++++++++++++++++++++++++++
 4 files changed, 1080 insertions(+)
 create mode 100644 drivers/media/i2c/ov05c10.c

diff --git a/MAINTAINERS b/MAINTAINERS
index a92290fffa16..caca25d00bf2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18303,6 +18303,14 @@ T:	git git://linuxtv.org/media.git
 F:	Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml
 F:	drivers/media/i2c/ov02e10.c
 
+OMNIVISION OV05C10 SENSOR DRIVER
+M:	Nirujogi Pratap <pratap.nirujogi@amd.com>
+M:	Bin Du <bin.du@amd.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+T:	git git://linuxtv.org/media.git
+F:	drivers/media/i2c/ov05c10.c
+
 OMNIVISION OV08D10 SENSOR DRIVER
 M:	Jimmy Su <jimmy.su@intel.com>
 L:	linux-media@vger.kernel.org
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index e68202954a8f..1662fb29d75c 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -377,6 +377,16 @@ config VIDEO_OV02C10
 	  To compile this driver as a module, choose M here: the
 	  module will be called ov02c10.
 
+config VIDEO_OV05C10
+	tristate "OmniVision OV05C10 sensor support"
+	select V4L2_CCI_I2C
+	help
+	  This is a Video4Linux2 sensor driver for the OmniVision
+	  OV05C10 camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called OV05C10.
+
 config VIDEO_OV08D10
         tristate "OmniVision OV08D10 sensor support"
         help
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 5873d29433ee..b4a1d721a7f2 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -85,6 +85,7 @@ obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
 obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
 obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o
 obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o
+obj-$(CONFIG_VIDEO_OV05C10) += ov05c10.o
 obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
 obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
 obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
diff --git a/drivers/media/i2c/ov05c10.c b/drivers/media/i2c/ov05c10.c
new file mode 100644
index 000000000000..9a1e493c4073
--- /dev/null
+++ b/drivers/media/i2c/ov05c10.c
@@ -0,0 +1,1061 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright (C) 2025 Advanced Micro Devices, Inc.
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/units.h>
+#include <media/v4l2-cci.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+
+#define DRV_NAME			"ov05c10"
+#define OV05C10_REF_CLK			(24 * HZ_PER_MHZ)
+
+#define MODE_WIDTH  2888
+#define MODE_HEIGHT 1808
+
+#define PAGE_NUM_MASK			0xff000000
+#define PAGE_NUM_SHIFT			24
+#define REG_ADDR_MASK			0x00ffffff
+
+#define OV05C10_SYSCTL_PAGE		(0 << PAGE_NUM_SHIFT)
+#define OV05C10_CISCTL_PAGE		(1 << PAGE_NUM_SHIFT)
+#define OV05C10_ISPCTL_PAGE		(4 << PAGE_NUM_SHIFT)
+
+/* Chip ID */
+#define OV05C10_REG_CHIP_ID		(CCI_REG24(0x00) | OV05C10_SYSCTL_PAGE)
+#define OV05C10_CHIP_ID			0x43055610
+
+/* Control registers */
+#define OV05C10_REG_TRIGGER		(CCI_REG8(0x01) | OV05C10_CISCTL_PAGE)
+#define OV05C_REG_TRIGGER_START		BIT(0)
+
+/* Exposure control */
+#define OV05C10_REG_EXPOSURE		(CCI_REG24(0x02) | OV05C10_CISCTL_PAGE)
+#define OV05C10_EXPOSURE_MAX_MARGIN	33
+#define OV05C10_EXPOSURE_MIN		4
+#define OV05C10_EXPOSURE_STEP		1
+#define OV05C10_EXPOSURE_DEFAULT	0x40
+
+/* V_TIMING internal */
+#define OV05C10_REG_VTS			(CCI_REG16(0x05) | OV05C10_CISCTL_PAGE)
+#define OV05C10_VTS_30FPS		1860
+#define OV05C10_VTS_MAX			0x7fff
+
+/* Test Pattern Control */
+#define OV05C10_REG_TEST_PATTERN	(CCI_REG8(0x12) | OV05C10_ISPCTL_PAGE)
+#define OV05C10_TEST_PATTERN_ENABLE	BIT(0)
+#define OV05C10_REG_TEST_PATTERN_CTL	(CCI_REG8(0xf3) | OV05C10_ISPCTL_PAGE)
+#define OV05C10_REG_TEST_PATTERN_XXX	BIT(0)
+
+/* Digital gain control */
+#define OV05C10_REG_DGTL_GAIN_H		(CCI_REG8(0x21) | OV05C10_CISCTL_PAGE)
+#define OV05C10_REG_DGTL_GAIN_L		(CCI_REG8(0x22) | OV05C10_CISCTL_PAGE)
+
+#define OV05C10_DGTL_GAIN_MIN		0x40
+#define OV05C10_DGTL_GAIN_MAX		0xff
+#define OV05C10_DGTL_GAIN_DEFAULT	0x40
+#define OV05C10_DGTL_GAIN_STEP		1
+
+#define OV05C10_DGTL_GAIN_L_MASK	0xff
+#define OV05C10_DGTL_GAIN_H_SHIFT	8
+#define OV05C10_DGTL_GAIN_H_MASK	0xff00
+
+/* Analog gain control */
+#define OV05C10_REG_ANALOG_GAIN		(CCI_REG8(0x24) | OV05C10_CISCTL_PAGE)
+#define OV05C10_ANA_GAIN_MIN		0x80
+#define OV05C10_ANA_GAIN_MAX		0x07c0
+#define OV05C10_ANA_GAIN_STEP		1
+#define OV05C10_ANA_GAIN_DEFAULT	0x80
+
+/* H TIMING internal */
+#define OV05C10_REG_HTS			(CCI_REG16(0x37) | OV05C10_CISCTL_PAGE)
+#define OV05C10_HTS_30FPS		0x0280
+
+/* Page selection */
+#define OV05C10_REG_PAGE_CTL		CCI_REG8(0xfd)
+
+#define NUM_OF_PADS 1
+
+#define OV05C10_GET_PAGE_NUM(reg)	(((reg) & PAGE_NUM_MASK) >>\
+					 PAGE_NUM_SHIFT)
+#define OV05C10_GET_REG_ADDR(reg)	((reg) & REG_ADDR_MASK)
+
+enum {
+	OV05C10_LINK_FREQ_900MHZ_INDEX,
+};
+
+struct ov05c10_reg_list {
+	u32 num_of_regs;
+	const struct cci_reg_sequence *regs;
+};
+
+/* Mode : resolution and related config&values */
+struct ov05c10_mode {
+	/* Frame width */
+	u32 width;
+	/* Frame height */
+	u32 height;
+	/* number of lanes */
+	u32 lanes;
+
+	/* V-timing */
+	u32 vts_def;
+	u32 vts_min;
+
+	/* HTS */
+	u32 hts;
+
+	/* Index of Link frequency config to be used */
+	u32 link_freq_index;
+
+	/* Default register values */
+	struct ov05c10_reg_list reg_list;
+};
+
+static const s64 ov05c10_link_frequencies[] = {
+	925 * HZ_PER_MHZ,
+};
+
+/* 2888x1808 30fps, 1800mbps, 2lane, 24mhz */
+static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
+	{ CCI_REG8(0xfd),  0x00 },
+	{ CCI_REG8(0x20),  0x00 },
+	{ CCI_REG8(0xfd),  0x00 },
+	{ CCI_REG8(0x20),  0x0b },
+	{ CCI_REG8(0xc1),  0x09 },
+	{ CCI_REG8(0x21),  0x06 },
+	{ CCI_REG8(0x14),  0x78 },
+	{ CCI_REG8(0xe7),  0x03 },
+	{ CCI_REG8(0xe7),  0x00 },
+	{ CCI_REG8(0x21),  0x00 },
+	{ CCI_REG8(0xfd),  0x01 },
+	{ CCI_REG8(0x03),  0x00 },
+	{ CCI_REG8(0x04),  0x06 },
+	{ CCI_REG8(0x05),  0x07 },
+	{ CCI_REG8(0x06),  0x44 },
+	{ CCI_REG8(0x07),  0x08 },
+	{ CCI_REG8(0x1b),  0x01 },
+	{ CCI_REG8(0x24),  0xff },
+	{ CCI_REG8(0x32),  0x03 },
+	{ CCI_REG8(0x42),  0x5d },
+	{ CCI_REG8(0x43),  0x08 },
+	{ CCI_REG8(0x44),  0x81 },
+	{ CCI_REG8(0x46),  0x5f },
+	{ CCI_REG8(0x48),  0x18 },
+	{ CCI_REG8(0x49),  0x04 },
+	{ CCI_REG8(0x5c),  0x18 },
+	{ CCI_REG8(0x5e),  0x13 },
+	{ CCI_REG8(0x70),  0x15 },
+	{ CCI_REG8(0x77),  0x35 },
+	{ CCI_REG8(0x79),  0x00 },
+	{ CCI_REG8(0x7b),  0x08 },
+	{ CCI_REG8(0x7d),  0x08 },
+	{ CCI_REG8(0x7e),  0x08 },
+	{ CCI_REG8(0x7f),  0x08 },
+	{ CCI_REG8(0x90),  0x37 },
+	{ CCI_REG8(0x91),  0x05 },
+	{ CCI_REG8(0x92),  0x18 },
+	{ CCI_REG8(0x93),  0x27 },
+	{ CCI_REG8(0x94),  0x05 },
+	{ CCI_REG8(0x95),  0x38 },
+	{ CCI_REG8(0x9b),  0x00 },
+	{ CCI_REG8(0x9c),  0x06 },
+	{ CCI_REG8(0x9d),  0x28 },
+	{ CCI_REG8(0x9e),  0x06 },
+	{ CCI_REG8(0xb2),  0x0f },
+	{ CCI_REG8(0xb3),  0x29 },
+	{ CCI_REG8(0xbf),  0x3c },
+	{ CCI_REG8(0xc2),  0x04 },
+	{ CCI_REG8(0xc4),  0x00 },
+	{ CCI_REG8(0xca),  0x20 },
+	{ CCI_REG8(0xcb),  0x20 },
+	{ CCI_REG8(0xcc),  0x28 },
+	{ CCI_REG8(0xcd),  0x28 },
+	{ CCI_REG8(0xce),  0x20 },
+	{ CCI_REG8(0xcf),  0x20 },
+	{ CCI_REG8(0xd0),  0x2a },
+	{ CCI_REG8(0xd1),  0x2a },
+	{ CCI_REG8(0xfd),  0x0f },
+	{ CCI_REG8(0x00),  0x00 },
+	{ CCI_REG8(0x01),  0xa0 },
+	{ CCI_REG8(0x02),  0x48 },
+	{ CCI_REG8(0x07),  0x8f },
+	{ CCI_REG8(0x08),  0x70 },
+	{ CCI_REG8(0x09),  0x01 },
+	{ CCI_REG8(0x0b),  0x40 },
+	{ CCI_REG8(0x0d),  0x07 },
+	{ CCI_REG8(0x11),  0x33 },
+	{ CCI_REG8(0x12),  0x77 },
+	{ CCI_REG8(0x13),  0x66 },
+	{ CCI_REG8(0x14),  0x65 },
+	{ CCI_REG8(0x15),  0x37 },
+	{ CCI_REG8(0x16),  0xbf },
+	{ CCI_REG8(0x17),  0xff },
+	{ CCI_REG8(0x18),  0xff },
+	{ CCI_REG8(0x19),  0x12 },
+	{ CCI_REG8(0x1a),  0x10 },
+	{ CCI_REG8(0x1c),  0x77 },
+	{ CCI_REG8(0x1d),  0x77 },
+	{ CCI_REG8(0x20),  0x0f },
+	{ CCI_REG8(0x21),  0x0f },
+	{ CCI_REG8(0x22),  0x0f },
+	{ CCI_REG8(0x23),  0x0f },
+	{ CCI_REG8(0x2b),  0x20 },
+	{ CCI_REG8(0x2c),  0x20 },
+	{ CCI_REG8(0x2d),  0x04 },
+	{ CCI_REG8(0xfd),  0x03 },
+	{ CCI_REG8(0x9d),  0x0f },
+	{ CCI_REG8(0x9f),  0x40 },
+	{ CCI_REG8(0xfd),  0x00 },
+	{ CCI_REG8(0x20),  0x1b },
+	{ CCI_REG8(0xfd),  0x04 },
+	{ CCI_REG8(0x19),  0x60 },
+	{ CCI_REG8(0xfd),  0x02 },
+	{ CCI_REG8(0x75),  0x05 },
+	{ CCI_REG8(0x7f),  0x06 },
+	{ CCI_REG8(0x9a),  0x03 },
+	{ CCI_REG8(0xa2),  0x07 },
+	{ CCI_REG8(0xa3),  0x10 },
+	{ CCI_REG8(0xa5),  0x02 },
+	{ CCI_REG8(0xa6),  0x0b },
+	{ CCI_REG8(0xa7),  0x48 },
+	{ CCI_REG8(0xfd),  0x07 },
+	{ CCI_REG8(0x42),  0x00 },
+	{ CCI_REG8(0x43),  0x80 },
+	{ CCI_REG8(0x44),  0x00 },
+	{ CCI_REG8(0x45),  0x80 },
+	{ CCI_REG8(0x46),  0x00 },
+	{ CCI_REG8(0x47),  0x80 },
+	{ CCI_REG8(0x48),  0x00 },
+	{ CCI_REG8(0x49),  0x80 },
+	{ CCI_REG8(0x00),  0xf7 },
+	{ CCI_REG8(0xfd),  0x00 },
+	{ CCI_REG8(0xe7),  0x03 },
+	{ CCI_REG8(0xe7),  0x00 },
+	{ CCI_REG8(0xfd),  0x00 },
+	{ CCI_REG8(0x93),  0x18 },
+	{ CCI_REG8(0x94),  0xff },
+	{ CCI_REG8(0x95),  0xbd },
+	{ CCI_REG8(0x96),  0x1a },
+	{ CCI_REG8(0x98),  0x04 },
+	{ CCI_REG8(0x99),  0x08 },
+	{ CCI_REG8(0x9b),  0x10 },
+	{ CCI_REG8(0x9c),  0x3f },
+	{ CCI_REG8(0xa1),  0x05 },
+	{ CCI_REG8(0xa4),  0x2f },
+	{ CCI_REG8(0xc0),  0x0c },
+	{ CCI_REG8(0xc1),  0x08 },
+	{ CCI_REG8(0xc2),  0x00 },
+	{ CCI_REG8(0xb6),  0x20 },
+	{ CCI_REG8(0xbb),  0x80 },
+	{ CCI_REG8(0xfd),  0x00 },
+	{ CCI_REG8(0xa0),  0x01 },
+	{ CCI_REG8(0xfd),  0x01 },
+};
+
+static const struct cci_reg_sequence mode_OV05C10_stream_on_regs[] = {
+	{ CCI_REG8(0xfd), 0x01 },
+	{ CCI_REG8(0x33), 0x03 },
+	{ CCI_REG8(0x01), 0x02 },
+	{ CCI_REG8(0xfd), 0x00 },
+	{ CCI_REG8(0x20), 0x1f },
+	{ CCI_REG8(0xfd), 0x01 },
+};
+
+static const struct cci_reg_sequence mode_OV05C10_stream_off_regs[] = {
+	{ CCI_REG8(0xfd), 0x00 },
+	{ CCI_REG8(0x20), 0x5b },
+	{ CCI_REG8(0xfd), 0x01 },
+	{ CCI_REG8(0x33), 0x02 },
+	{ CCI_REG8(0x01), 0x02 },
+};
+
+static const char * const ov05c10_test_pattern_menu[] = {
+	"Disabled",
+	"Vertical Color Bar Type 1",
+	"Vertical Color Bar Type 2",
+	"Vertical Color Bar Type 3",
+	"Vertical Color Bar Type 4"
+};
+
+/* Configurations for supported link frequencies */
+#define OV05C10_LINK_FREQ_900MHZ	(900 * HZ_PER_MHZ)
+
+/* Number of lanes supported */
+#define OV05C10_DATA_LANES		2
+
+/* Bits per sample of sensor output */
+#define OV05C10_BITS_PER_SAMPLE		10
+
+/*
+ * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
+ * data rate => double data rate; number of lanes => 2; bits per pixel => 10
+ */
+static u64 link_freq_to_pixel_rate(u64 f, u32 lane_nr)
+{
+	f *= 2 * lane_nr;
+	do_div(f, OV05C10_BITS_PER_SAMPLE);
+
+	return f;
+}
+
+/* Menu items for LINK_FREQ V4L2 control */
+static const s64 ov05c10_link_freq_menu_items[] = {
+	OV05C10_LINK_FREQ_900MHZ,
+};
+
+/* Mode configs, currently, only support 1 mode */
+static const struct ov05c10_mode supported_mode = {
+	.width = MODE_WIDTH,
+	.height = MODE_HEIGHT,
+	.vts_def = OV05C10_VTS_30FPS,
+	.vts_min = OV05C10_VTS_30FPS,
+	.hts = 640,
+	.lanes = 2,
+	.reg_list = {
+		.num_of_regs = ARRAY_SIZE(ov05c10_2888x1808_regs),
+		.regs = ov05c10_2888x1808_regs,
+	},
+	.link_freq_index = OV05C10_LINK_FREQ_900MHZ_INDEX,
+};
+
+struct ov05c10 {
+	struct v4l2_subdev sd;
+	struct media_pad pad;
+
+	/* V4L2 control handler */
+	struct v4l2_ctrl_handler ctrl_handler;
+
+	/* V4L2 Controls */
+	struct v4l2_ctrl *link_freq;
+	struct v4l2_ctrl *pixel_rate;
+	struct v4l2_ctrl *vblank;
+	struct v4l2_ctrl *hblank;
+	struct v4l2_ctrl *exposure;
+
+	struct regmap *regmap;
+
+	/* gpio descriptor */
+	struct gpio_desc *enable_gpio;
+
+	/* Current page for sensor register control */
+	int cur_page;
+};
+
+#define to_ov05c10(_sd)	container_of(_sd, struct ov05c10, sd)
+
+static int ov05c10_init_state(struct v4l2_subdev *sd,
+			      struct v4l2_subdev_state *sd_state)
+{
+	struct v4l2_mbus_framefmt *frame_fmt;
+	struct v4l2_subdev_format fmt = {
+		.which = V4L2_SUBDEV_FORMAT_TRY,
+		.format = {
+			.width = MODE_WIDTH,
+			.height = MODE_HEIGHT,
+			.code = MEDIA_BUS_FMT_SGRBG10_1X10,
+			.field = V4L2_FIELD_NONE,
+		}
+	};
+
+	frame_fmt = v4l2_subdev_state_get_format(sd_state, 0);
+	*frame_fmt = fmt.format;
+	return 0;
+}
+
+static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page, int *err)
+{
+	int ret = 0;
+
+	if (err && *err)
+		return *err;
+
+	if (page != ov05c10->cur_page) {
+		cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, &ret);
+		if (!ret)
+			ov05c10->cur_page = page;
+	}
+
+	if (err)
+		*err = ret;
+
+	return ret;
+}
+
+/* refer to the implementation of cci_read */
+static int ov05c10_reg_read(struct ov05c10 *ov05c10, u32 reg,
+			    u64 *val, int *err)
+{
+	u32 page;
+	u32 addr;
+	int ret = 0;
+
+	if (err && *err)
+		return *err;
+
+	page = OV05C10_GET_PAGE_NUM(reg);
+	addr = OV05C10_GET_REG_ADDR(reg);
+	ov05c10_switch_page(ov05c10, page, &ret);
+	cci_read(ov05c10->regmap, addr, val, &ret);
+	if (err)
+		*err = ret;
+
+	return ret;
+}
+
+/* refer to the implementation of cci_write */
+static int ov05c10_reg_write(struct ov05c10 *ov05c10, u32 reg,
+			     u64 val, int *err)
+{
+	u32 page;
+	u32 addr;
+	int ret = 0;
+
+	if (err && *err)
+		return *err;
+
+	page = OV05C10_GET_PAGE_NUM(reg);
+	addr = OV05C10_GET_REG_ADDR(reg);
+	ov05c10_switch_page(ov05c10, page, &ret);
+	cci_write(ov05c10->regmap, addr, val, &ret);
+	if (err)
+		*err = ret;
+
+	return ret;
+}
+
+static int ov05c10_update_vblank(struct ov05c10 *ov05c10, u32 vblank)
+{
+	const struct ov05c10_mode *mode = &supported_mode;
+	u64 val;
+	int ret = 0;
+
+	val = mode->height + vblank;
+	ov05c10_reg_write(ov05c10, OV05C10_REG_VTS, val, &ret);
+	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
+			  OV05C_REG_TRIGGER_START, &ret);
+
+	return ret;
+}
+
+static int ov05c10_update_exposure(struct ov05c10 *ov05c10, u32 exposure)
+{
+	int ret = 0;
+
+	ov05c10_reg_write(ov05c10, OV05C10_REG_EXPOSURE, exposure, &ret);
+	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
+			  OV05C_REG_TRIGGER_START, &ret);
+
+	return ret;
+}
+
+static int ov05c10_update_analog_gain(struct ov05c10 *ov05c10, u32 a_gain)
+{
+	int ret = 0;
+
+	ov05c10_reg_write(ov05c10, OV05C10_REG_ANALOG_GAIN, a_gain, &ret);
+	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
+			  OV05C_REG_TRIGGER_START, &ret);
+
+	return ret;
+}
+
+static int ov05c10_update_digital_gain(struct ov05c10 *ov05c10, u32 d_gain)
+{
+	u64 val;
+	int ret = 0;
+
+	val = d_gain & OV05C10_DGTL_GAIN_L_MASK;
+	ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_L, val, &ret);
+
+	val = (d_gain & OV05C10_DGTL_GAIN_H_MASK) >> OV05C10_DGTL_GAIN_H_SHIFT;
+	ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_H, val, &ret);
+
+	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
+			  OV05C_REG_TRIGGER_START, &ret);
+
+	return ret;
+}
+
+static int ov05c10_enable_test_pattern(struct ov05c10 *ov05c10, u32 pattern)
+{
+	u64 val;
+	int ret = 0;
+
+	if (pattern) {
+		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
+				 &val, &ret);
+		ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
+				  val | OV05C10_REG_TEST_PATTERN_XXX, &ret);
+		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
+		val |= OV05C10_TEST_PATTERN_ENABLE;
+	} else {
+		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
+		val &= ~OV05C10_TEST_PATTERN_ENABLE;
+	}
+
+	ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN, val, &ret);
+	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
+			  OV05C_REG_TRIGGER_START, &ret);
+
+	return ret;
+}
+
+static int ov05c10_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct ov05c10 *ov05c10 = container_of(ctrl->handler,
+					       struct ov05c10, ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
+	const struct ov05c10_mode *mode = &supported_mode;
+	s64 max;
+	int ret = 0;
+
+	/* Propagate change of current control to all related controls */
+	if (ctrl->id == V4L2_CID_VBLANK) {
+		s64 cur_exp = ov05c10->exposure->cur.val;
+
+		/* Update max exposure while meeting expected vblanking */
+		max = mode->height + ctrl->val - OV05C10_EXPOSURE_MAX_MARGIN;
+		cur_exp = clamp(cur_exp, ov05c10->exposure->minimum, max);
+		ret = __v4l2_ctrl_modify_range(ov05c10->exposure,
+					       ov05c10->exposure->minimum,
+					       max, ov05c10->exposure->step,
+					       cur_exp);
+		if (!ret)
+			return ret;
+	}
+
+	/*
+	 * Applying V4L2 control value only happens
+	 * when power is up for streaming
+	 */
+	if (!pm_runtime_get_if_in_use(&client->dev))
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_ANALOGUE_GAIN:
+		ret = ov05c10_update_analog_gain(ov05c10, ctrl->val);
+		break;
+	case V4L2_CID_DIGITAL_GAIN:
+		ret = ov05c10_update_digital_gain(ov05c10, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		ret = ov05c10_update_exposure(ov05c10, ctrl->val);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = ov05c10_update_vblank(ov05c10, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		ret = ov05c10_enable_test_pattern(ov05c10, ctrl->val);
+		break;
+	default:
+		ret = -ENOTTY;
+		dev_err(&client->dev,
+			"ctrl(id:0x%x,val:0x%x) is not handled\n",
+			ctrl->id, ctrl->val);
+		break;
+	}
+
+	pm_runtime_put(&client->dev);
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops ov05c10_ctrl_ops = {
+	.s_ctrl = ov05c10_set_ctrl,
+};
+
+static int ov05c10_enum_mbus_code(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *sd_state,
+				  struct v4l2_subdev_mbus_code_enum *code)
+{
+	/* Only one bayer order(GRBG) is supported */
+	if (code->index > 0)
+		return -EINVAL;
+
+	code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
+
+	return 0;
+}
+
+static int ov05c10_enum_frame_size(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *sd_state,
+				   struct v4l2_subdev_frame_size_enum *fse)
+{
+	/* ov05c10 driver currently only supports 1 mode*/
+	if (fse->index != 0)
+		return -EINVAL;
+
+	if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
+		return -EINVAL;
+
+	fse->min_width = supported_mode.width;
+	fse->max_width = fse->min_width;
+	fse->min_height = supported_mode.height;
+	fse->max_height = fse->min_height;
+
+	return 0;
+}
+
+static void ov05c10_update_pad_format(const struct ov05c10_mode *mode,
+				      struct v4l2_subdev_format *fmt)
+{
+	fmt->format.width = mode->width;
+	fmt->format.height = mode->height;
+	fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
+	fmt->format.field = V4L2_FIELD_NONE;
+}
+
+static int ov05c10_set_pad_format(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *sd_state,
+				  struct v4l2_subdev_format *fmt)
+{
+	struct v4l2_mbus_framefmt *framefmt;
+	struct ov05c10 *ov05c10 = to_ov05c10(sd);
+	const struct ov05c10_mode *mode;
+	s32 vblank_def;
+	s32 vblank_min;
+	s64 pixel_rate;
+	s64 link_freq;
+	s64 h_blank;
+
+	/* Only one raw bayer(GRBG) order is supported */
+	if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10)
+		fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
+
+	mode = &supported_mode;
+	ov05c10_update_pad_format(mode, fmt);
+	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+		framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
+		*framefmt = fmt->format;
+	} else {
+		__v4l2_ctrl_s_ctrl(ov05c10->link_freq, mode->link_freq_index);
+		link_freq = ov05c10_link_freq_menu_items[mode->link_freq_index];
+		pixel_rate = link_freq_to_pixel_rate(link_freq,
+						     mode->lanes);
+		__v4l2_ctrl_s_ctrl_int64(ov05c10->pixel_rate, pixel_rate);
+
+		/* Update limits and set FPS to default */
+		vblank_def = mode->vts_def - mode->height;
+		vblank_min = mode->vts_min - mode->height;
+		__v4l2_ctrl_modify_range(ov05c10->vblank, vblank_min,
+					 OV05C10_VTS_MAX - mode->height,
+					 1, vblank_def);
+		__v4l2_ctrl_s_ctrl(ov05c10->vblank, vblank_def);
+		h_blank = mode->hts;
+		__v4l2_ctrl_modify_range(ov05c10->hblank, h_blank,
+					 h_blank, 1, h_blank);
+	}
+
+	return 0;
+}
+
+static int ov05c10_start_streaming(struct ov05c10 *ov05c10)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
+	const struct ov05c10_mode *mode = &supported_mode;
+	const struct ov05c10_reg_list *reg_list;
+	int ret = 0;
+
+	/* Apply default values of current mode */
+	reg_list = &mode->reg_list;
+	cci_multi_reg_write(ov05c10->regmap, reg_list->regs,
+			    reg_list->num_of_regs, &ret);
+	if (ret) {
+		dev_err(&client->dev, "fail to set mode, ret: %d\n", ret);
+		return ret;
+	}
+
+	/* Apply customized values from user */
+	ret =  __v4l2_ctrl_handler_setup(ov05c10->sd.ctrl_handler);
+	if (ret) {
+		dev_err(&client->dev, "failed to setup v4l2 handler %d\n", ret);
+		return ret;
+	}
+
+	cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_on_regs,
+			    ARRAY_SIZE(mode_OV05C10_stream_on_regs), &ret);
+	if (ret)
+		dev_err(&client->dev, "fail to start the streaming\n");
+
+	return ret;
+}
+
+static int ov05c10_stop_streaming(struct ov05c10 *ov05c10)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
+	int ret = 0;
+
+	cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_off_regs,
+			    ARRAY_SIZE(mode_OV05C10_stream_off_regs), &ret);
+	if (ret)
+		dev_err(&client->dev, "fail to stop the streaming\n");
+
+	return ret;
+}
+
+static void ov05c10_sensor_power_set(struct ov05c10 *ov05c10, bool on)
+{
+	if (on) {
+		gpiod_set_value(ov05c10->enable_gpio, 0);
+		usleep_range(10, 20);
+
+		gpiod_set_value(ov05c10->enable_gpio, 1);
+		usleep_range(1000, 2000);
+	} else {
+		gpiod_set_value(ov05c10->enable_gpio, 0);
+		usleep_range(10, 20);
+	}
+}
+
+static int ov05c10_enable_streams(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state, u32 pad,
+				  u64 streams_mask)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct ov05c10 *ov05c10 = to_ov05c10(sd);
+	int ret = 0;
+
+	ret = pm_runtime_resume_and_get(&client->dev);
+	if (ret < 0)
+		return ret;
+
+	ov05c10->cur_page = -1;
+
+	ret = ov05c10_start_streaming(ov05c10);
+	if (ret)
+		goto err_rpm_put;
+
+	return 0;
+
+err_rpm_put:
+	pm_runtime_put(&client->dev);
+	return ret;
+}
+
+static int ov05c10_disable_streams(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state, u32 pad,
+				   u64 streams_mask)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct ov05c10 *ov05c10 = to_ov05c10(sd);
+
+	ov05c10_stop_streaming(ov05c10);
+	pm_runtime_put(&client->dev);
+
+	return 0;
+}
+
+static const struct v4l2_subdev_video_ops ov05c10_video_ops = {
+	.s_stream = v4l2_subdev_s_stream_helper,
+};
+
+static const struct v4l2_subdev_pad_ops ov05c10_pad_ops = {
+	.enum_mbus_code = ov05c10_enum_mbus_code,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = ov05c10_set_pad_format,
+	.enum_frame_size = ov05c10_enum_frame_size,
+	.enable_streams = ov05c10_enable_streams,
+	.disable_streams = ov05c10_disable_streams,
+};
+
+static const struct v4l2_subdev_ops ov05c10_subdev_ops = {
+	.video = &ov05c10_video_ops,
+	.pad = &ov05c10_pad_ops,
+};
+
+static const struct media_entity_operations ov05c10_subdev_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+static const struct v4l2_subdev_internal_ops ov05c10_internal_ops = {
+	.init_state = ov05c10_init_state,
+};
+
+static int ov05c10_init_controls(struct ov05c10 *ov05c10)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
+	const struct ov05c10_mode *mode = &supported_mode;
+	struct v4l2_fwnode_device_properties props;
+	struct v4l2_ctrl_handler *ctrl_hdlr;
+	s64 pixel_rate_max;
+	s64 exposure_max;
+	s64 vblank_def;
+	s64 vblank_min;
+	u32 max_items;
+	s64 hblank;
+	int ret;
+
+	ret = v4l2_ctrl_handler_init(&ov05c10->ctrl_handler, 10);
+	if (ret)
+		return ret;
+
+	ctrl_hdlr = &ov05c10->ctrl_handler;
+
+	max_items = ARRAY_SIZE(ov05c10_link_freq_menu_items) - 1;
+	ov05c10->link_freq =
+		v4l2_ctrl_new_int_menu(ctrl_hdlr,
+				       NULL,
+				       V4L2_CID_LINK_FREQ,
+				       max_items,
+				       0,
+				       ov05c10_link_freq_menu_items);
+	if (ov05c10->link_freq)
+		ov05c10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	pixel_rate_max =
+		link_freq_to_pixel_rate(ov05c10_link_freq_menu_items[0],
+					supported_mode.lanes);
+	ov05c10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
+						V4L2_CID_PIXEL_RATE,
+						0, pixel_rate_max,
+						1, pixel_rate_max);
+
+	vblank_def = mode->vts_def - mode->height;
+	vblank_min = mode->vts_min - mode->height;
+	ov05c10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
+					    V4L2_CID_VBLANK,
+					    vblank_min,
+					    OV05C10_VTS_MAX - mode->height,
+					    1, vblank_def);
+
+	hblank = (mode->hts > mode->width) ? (mode->hts - mode->width) : 0;
+	ov05c10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
+					    V4L2_CID_HBLANK,
+					    hblank, hblank, 1, hblank);
+	if (ov05c10->hblank)
+		ov05c10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	exposure_max = mode->vts_def - OV05C10_EXPOSURE_MAX_MARGIN;
+	ov05c10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
+					      V4L2_CID_EXPOSURE,
+					      OV05C10_EXPOSURE_MIN,
+					      exposure_max,
+					      OV05C10_EXPOSURE_STEP,
+					      exposure_max);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+			  OV05C10_ANA_GAIN_MIN, OV05C10_ANA_GAIN_MAX,
+			  OV05C10_ANA_GAIN_STEP, OV05C10_ANA_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+			  OV05C10_DGTL_GAIN_MIN, OV05C10_DGTL_GAIN_MAX,
+			  OV05C10_DGTL_GAIN_STEP, OV05C10_DGTL_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov05c10_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(ov05c10_test_pattern_menu) - 1,
+				     0, 0, ov05c10_test_pattern_menu);
+
+	if (ctrl_hdlr->error) {
+		ret = ctrl_hdlr->error;
+		dev_err(&client->dev, "V4L2 control init failed (%d)\n", ret);
+		goto err_hdl_free;
+	}
+
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret)
+		goto err_hdl_free;
+
+	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov05c10_ctrl_ops,
+					      &props);
+	if (ret)
+		goto err_hdl_free;
+
+	ov05c10->sd.ctrl_handler = ctrl_hdlr;
+
+	return 0;
+
+err_hdl_free:
+	v4l2_ctrl_handler_free(ctrl_hdlr);
+
+	return ret;
+}
+
+static int ov05c10_parse_endpoint(struct device *dev,
+				  struct fwnode_handle *fwnode)
+{
+	struct v4l2_fwnode_endpoint bus_cfg = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY
+	};
+	struct fwnode_handle *ep;
+	unsigned long bitmap;
+	int ret;
+
+	ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
+	if (!ep) {
+		dev_err(dev, "Failed to get next endpoint\n");
+		return -ENXIO;
+	}
+
+	ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
+	fwnode_handle_put(ep);
+	if (ret)
+		return ret;
+
+	if (bus_cfg.bus.mipi_csi2.num_data_lanes != supported_mode.lanes) {
+		dev_err(dev,
+			"number of CSI2 data lanes %d is not supported\n",
+			bus_cfg.bus.mipi_csi2.num_data_lanes);
+		ret = -EINVAL;
+		goto err_endpoint_free;
+	}
+
+	ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
+				       bus_cfg.nr_of_link_frequencies,
+				       ov05c10_link_frequencies,
+				       ARRAY_SIZE(ov05c10_link_frequencies),
+				       &bitmap);
+	if (ret)
+		dev_err(dev, "v4l2_link_freq_to_bitmap fail with %d\n", ret);
+err_endpoint_free:
+	v4l2_fwnode_endpoint_free(&bus_cfg);
+
+	return ret;
+}
+
+static int ov05c10_probe(struct i2c_client *client)
+{
+	struct ov05c10 *ov05c10;
+	u32 clkfreq;
+	int ret;
+
+	ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), GFP_KERNEL);
+	if (!ov05c10)
+		return -ENOMEM;
+
+	struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
+
+	ret = fwnode_property_read_u32(fwnode, "clock-frequency", &clkfreq);
+	if (ret)
+		return  dev_err_probe(&client->dev, -EINVAL,
+				      "fail to get clock freq\n");
+	if (clkfreq != OV05C10_REF_CLK)
+		return dev_err_probe(&client->dev, -EINVAL,
+				     "fail invalid clock freq %u, %lu expected\n",
+				     clkfreq, OV05C10_REF_CLK);
+
+	ret = ov05c10_parse_endpoint(&client->dev, fwnode);
+	if (ret)
+		return dev_err_probe(&client->dev, -EINVAL,
+				     "fail to parse endpoint\n");
+
+	ov05c10->enable_gpio = devm_gpiod_get(&client->dev, "enable",
+					      GPIOD_OUT_LOW);
+	if (IS_ERR(ov05c10->enable_gpio))
+		return dev_err_probe(&client->dev,
+				     PTR_ERR(ov05c10->enable_gpio),
+				     "fail to get enable gpio\n");
+
+	v4l2_i2c_subdev_init(&ov05c10->sd, client, &ov05c10_subdev_ops);
+
+	ov05c10->regmap = devm_cci_regmap_init_i2c(client, 8);
+	if (IS_ERR(ov05c10->regmap))
+		return dev_err_probe(&client->dev, PTR_ERR(ov05c10->regmap),
+				     "fail to init cci\n");
+
+	ov05c10->cur_page = -1;
+
+	ret = ov05c10_init_controls(ov05c10);
+	if (ret)
+		return dev_err_probe(&client->dev, ret, "fail to init ctl\n");
+
+	ov05c10->sd.internal_ops = &ov05c10_internal_ops;
+	ov05c10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	ov05c10->sd.entity.ops = &ov05c10_subdev_entity_ops;
+	ov05c10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+	ov05c10->pad.flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&ov05c10->sd.entity, NUM_OF_PADS,
+				     &ov05c10->pad);
+	if (ret)
+		goto err_hdl_free;
+
+	ret = v4l2_subdev_init_finalize(&ov05c10->sd);
+	if (ret < 0)
+		goto err_media_entity_cleanup;
+
+	ret = v4l2_async_register_subdev_sensor(&ov05c10->sd);
+	if (ret)
+		goto err_media_entity_cleanup;
+
+	pm_runtime_set_active(&client->dev);
+	pm_runtime_enable(&client->dev);
+	pm_runtime_idle(&client->dev);
+	pm_runtime_set_autosuspend_delay(&client->dev, 1000);
+	pm_runtime_use_autosuspend(&client->dev);
+	return 0;
+
+err_media_entity_cleanup:
+	media_entity_cleanup(&ov05c10->sd.entity);
+
+err_hdl_free:
+	v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
+
+	return ret;
+}
+
+static void ov05c10_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ov05c10 *ov05c10 = to_ov05c10(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
+
+	pm_runtime_disable(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+static int ov05c10_runtime_resume(struct device *dev)
+{
+	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+	struct ov05c10 *ov05c10 = to_ov05c10(sd);
+
+	ov05c10_sensor_power_set(ov05c10, true);
+	return 0;
+}
+
+static int ov05c10_runtime_suspend(struct device *dev)
+{
+	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+	struct ov05c10 *ov05c10 = to_ov05c10(sd);
+
+	ov05c10_sensor_power_set(ov05c10, false);
+	return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(ov05c10_pm_ops, ov05c10_runtime_suspend,
+				 ov05c10_runtime_resume, NULL);
+
+static const struct i2c_device_id ov05c10_i2c_ids[] = {
+	{"ov05c10", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ov05c10_i2c_ids);
+
+static struct i2c_driver ov05c10_i2c_driver = {
+	.driver = {
+		.name = DRV_NAME,
+		.pm = pm_ptr(&ov05c10_pm_ops),
+	},
+	.id_table = ov05c10_i2c_ids,
+	.probe = ov05c10_probe,
+	.remove = ov05c10_remove,
+};
+
+module_i2c_driver(ov05c10_i2c_driver);
+
+MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
+MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@amd.com>");
+MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
+MODULE_DESCRIPTION("OmniVision OV05C1010 sensor driver");
+MODULE_LICENSE("GPL");
-- 
2.43.0


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-09 19:42 [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver Pratap Nirujogi
@ 2025-06-13  4:55 ` Hao Yao
  2025-06-13 11:02   ` Kieran Bingham
                     ` (2 more replies)
  2025-06-15  0:09 ` Laurent Pinchart
  1 sibling, 3 replies; 60+ messages in thread
From: Hao Yao @ 2025-06-13  4:55 UTC (permalink / raw)
  To: Pratap Nirujogi, sakari.ailus
  Cc: mchehab, laurent.pinchart, hverkuil, bryan.odonoghue, krzk,
	dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen, linux-media,
	linux-kernel, benjamin.chan, bin.du, grosikop, king.li, dantony,
	vengutta, dongcheng.yan, jason.z.chen, jimmy.su

Hi Pratap,

Thanks for your patch.

This patch is written for your camera sensor module, which seems very 
different from those already applied on Dell laptops (some of "Dell Pro" 
series). Looking into the driver, I think this version will break the 
devices using ov05c10 sensor.

I think this patch is better to be validated on existing devices, but 
please do some fixes before we can do validation. Please check my 
comments inline.


On 2025/6/10 03:42, Pratap Nirujogi wrote:
> Add driver for OmniVision 5.2M OV05C10 sensor. This driver
> supports only the full size normal 2888x1808@30fps 2-lane
> sensor profile.
> 
> Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> Co-developed-by: Bin Du <bin.du@amd.com>
> Signed-off-by: Bin Du <bin.du@amd.com>
> Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
> ---
> Changes v2 -> v3:
> 
> * Update "refclk" property variable as "clock-frequency".
> * Update sensor GPIO connector id name.
> * Fix sensor v4l2 compliance issue.
> * Fix license info.
> * Address review comments.
> 
>   MAINTAINERS                 |    8 +
>   drivers/media/i2c/Kconfig   |   10 +
>   drivers/media/i2c/Makefile  |    1 +
>   drivers/media/i2c/ov05c10.c | 1061 +++++++++++++++++++++++++++++++++++
>   4 files changed, 1080 insertions(+)
>   create mode 100644 drivers/media/i2c/ov05c10.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a92290fffa16..caca25d00bf2 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18303,6 +18303,14 @@ T:	git git://linuxtv.org/media.git
>   F:	Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml
>   F:	drivers/media/i2c/ov02e10.c
>   
> +OMNIVISION OV05C10 SENSOR DRIVER
> +M:	Nirujogi Pratap <pratap.nirujogi@amd.com>
> +M:	Bin Du <bin.du@amd.com>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained
> +T:	git git://linuxtv.org/media.git
> +F:	drivers/media/i2c/ov05c10.c
> +
>   OMNIVISION OV08D10 SENSOR DRIVER
>   M:	Jimmy Su <jimmy.su@intel.com>
>   L:	linux-media@vger.kernel.org
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index e68202954a8f..1662fb29d75c 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -377,6 +377,16 @@ config VIDEO_OV02C10
>   	  To compile this driver as a module, choose M here: the
>   	  module will be called ov02c10.
>   
> +config VIDEO_OV05C10
> +	tristate "OmniVision OV05C10 sensor support"
> +	select V4L2_CCI_I2C
> +	help
> +	  This is a Video4Linux2 sensor driver for the OmniVision
> +	  OV05C10 camera.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called OV05C10.
> +
>   config VIDEO_OV08D10
>           tristate "OmniVision OV08D10 sensor support"
>           help
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 5873d29433ee..b4a1d721a7f2 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -85,6 +85,7 @@ obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
>   obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
>   obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o
>   obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o
> +obj-$(CONFIG_VIDEO_OV05C10) += ov05c10.o
>   obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
>   obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
>   obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
> diff --git a/drivers/media/i2c/ov05c10.c b/drivers/media/i2c/ov05c10.c
> new file mode 100644
> index 000000000000..9a1e493c4073
> --- /dev/null
> +++ b/drivers/media/i2c/ov05c10.c
> @@ -0,0 +1,1061 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +// Copyright (C) 2025 Advanced Micro Devices, Inc.
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/units.h>
> +#include <media/v4l2-cci.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +
> +#define DRV_NAME			"ov05c10"
> +#define OV05C10_REF_CLK			(24 * HZ_PER_MHZ)

Seems your module use 24 MHz clock input. The Dell's modules always use 
19.2MHz, which means your the PLL settings will not work on Dell's.

> +
> +#define MODE_WIDTH  2888
> +#define MODE_HEIGHT 1808
> +
> +#define PAGE_NUM_MASK			0xff000000
> +#define PAGE_NUM_SHIFT			24
> +#define REG_ADDR_MASK			0x00ffffff
> +
> +#define OV05C10_SYSCTL_PAGE		(0 << PAGE_NUM_SHIFT)
> +#define OV05C10_CISCTL_PAGE		(1 << PAGE_NUM_SHIFT)
> +#define OV05C10_ISPCTL_PAGE		(4 << PAGE_NUM_SHIFT)
> +
> +/* Chip ID */
> +#define OV05C10_REG_CHIP_ID		(CCI_REG24(0x00) | OV05C10_SYSCTL_PAGE)
> +#define OV05C10_CHIP_ID			0x43055610
> +
> +/* Control registers */
> +#define OV05C10_REG_TRIGGER		(CCI_REG8(0x01) | OV05C10_CISCTL_PAGE)
> +#define OV05C_REG_TRIGGER_START		BIT(0)
> +
> +/* Exposure control */
> +#define OV05C10_REG_EXPOSURE		(CCI_REG24(0x02) | OV05C10_CISCTL_PAGE)
> +#define OV05C10_EXPOSURE_MAX_MARGIN	33
> +#define OV05C10_EXPOSURE_MIN		4
> +#define OV05C10_EXPOSURE_STEP		1
> +#define OV05C10_EXPOSURE_DEFAULT	0x40
> +
> +/* V_TIMING internal */
> +#define OV05C10_REG_VTS			(CCI_REG16(0x05) | OV05C10_CISCTL_PAGE)
> +#define OV05C10_VTS_30FPS		1860
> +#define OV05C10_VTS_MAX			0x7fff
> +
> +/* Test Pattern Control */
> +#define OV05C10_REG_TEST_PATTERN	(CCI_REG8(0x12) | OV05C10_ISPCTL_PAGE)
> +#define OV05C10_TEST_PATTERN_ENABLE	BIT(0)
> +#define OV05C10_REG_TEST_PATTERN_CTL	(CCI_REG8(0xf3) | OV05C10_ISPCTL_PAGE)
> +#define OV05C10_REG_TEST_PATTERN_XXX	BIT(0)
> +
> +/* Digital gain control */
> +#define OV05C10_REG_DGTL_GAIN_H		(CCI_REG8(0x21) | OV05C10_CISCTL_PAGE)
> +#define OV05C10_REG_DGTL_GAIN_L		(CCI_REG8(0x22) | OV05C10_CISCTL_PAGE)
> +
> +#define OV05C10_DGTL_GAIN_MIN		0x40
> +#define OV05C10_DGTL_GAIN_MAX		0xff
> +#define OV05C10_DGTL_GAIN_DEFAULT	0x40
> +#define OV05C10_DGTL_GAIN_STEP		1
> +
> +#define OV05C10_DGTL_GAIN_L_MASK	0xff
> +#define OV05C10_DGTL_GAIN_H_SHIFT	8
> +#define OV05C10_DGTL_GAIN_H_MASK	0xff00
> +
> +/* Analog gain control */
> +#define OV05C10_REG_ANALOG_GAIN		(CCI_REG8(0x24) | OV05C10_CISCTL_PAGE)
> +#define OV05C10_ANA_GAIN_MIN		0x80
> +#define OV05C10_ANA_GAIN_MAX		0x07c0
> +#define OV05C10_ANA_GAIN_STEP		1
> +#define OV05C10_ANA_GAIN_DEFAULT	0x80
> +
> +/* H TIMING internal */
> +#define OV05C10_REG_HTS			(CCI_REG16(0x37) | OV05C10_CISCTL_PAGE)
> +#define OV05C10_HTS_30FPS		0x0280
> +
> +/* Page selection */
> +#define OV05C10_REG_PAGE_CTL		CCI_REG8(0xfd)
> +
> +#define NUM_OF_PADS 1
> +
> +#define OV05C10_GET_PAGE_NUM(reg)	(((reg) & PAGE_NUM_MASK) >>\
> +					 PAGE_NUM_SHIFT)
> +#define OV05C10_GET_REG_ADDR(reg)	((reg) & REG_ADDR_MASK)
> +
> +enum {
> +	OV05C10_LINK_FREQ_900MHZ_INDEX,
> +};
> +
> +struct ov05c10_reg_list {
> +	u32 num_of_regs;
> +	const struct cci_reg_sequence *regs;
> +};
> +
> +/* Mode : resolution and related config&values */
> +struct ov05c10_mode {
> +	/* Frame width */
> +	u32 width;
> +	/* Frame height */
> +	u32 height;
> +	/* number of lanes */
> +	u32 lanes;
> +
> +	/* V-timing */
> +	u32 vts_def;
> +	u32 vts_min;
> +
> +	/* HTS */
> +	u32 hts;
> +
> +	/* Index of Link frequency config to be used */
> +	u32 link_freq_index;
> +
> +	/* Default register values */
> +	struct ov05c10_reg_list reg_list;
> +};
> +
> +static const s64 ov05c10_link_frequencies[] = {
> +	925 * HZ_PER_MHZ,
> +};

Is it 900 MHz, or 925 MHz?

> +
> +/* 2888x1808 30fps, 1800mbps, 2lane, 24mhz */

Currently Dell's devices with ov05c10 use a CV chip to passthrough MIPI 
CSI signals, but it supports max 750 MHz link frequency. That's why this 
version:
https://github.com/intel/ipu6-drivers/blob/master/drivers/media/i2c/ov05c10.c
uses 480 MHz link frequency and a different resolution setting 
(2800x1576). At least the setting in out-of-tree Github driver should be 
merged into this version.

> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
> +	{ CCI_REG8(0xfd),  0x00 },
> +	{ CCI_REG8(0x20),  0x00 },
> +	{ CCI_REG8(0xfd),  0x00 },
> +	{ CCI_REG8(0x20),  0x0b },
> +	{ CCI_REG8(0xc1),  0x09 },
> +	{ CCI_REG8(0x21),  0x06 },
> +	{ CCI_REG8(0x14),  0x78 },
> +	{ CCI_REG8(0xe7),  0x03 },
> +	{ CCI_REG8(0xe7),  0x00 },
> +	{ CCI_REG8(0x21),  0x00 },
> +	{ CCI_REG8(0xfd),  0x01 },
> +	{ CCI_REG8(0x03),  0x00 },
> +	{ CCI_REG8(0x04),  0x06 },
> +	{ CCI_REG8(0x05),  0x07 },
> +	{ CCI_REG8(0x06),  0x44 },
> +	{ CCI_REG8(0x07),  0x08 },
> +	{ CCI_REG8(0x1b),  0x01 },
> +	{ CCI_REG8(0x24),  0xff },
> +	{ CCI_REG8(0x32),  0x03 },
> +	{ CCI_REG8(0x42),  0x5d },
> +	{ CCI_REG8(0x43),  0x08 },
> +	{ CCI_REG8(0x44),  0x81 },
> +	{ CCI_REG8(0x46),  0x5f },
> +	{ CCI_REG8(0x48),  0x18 },
> +	{ CCI_REG8(0x49),  0x04 },
> +	{ CCI_REG8(0x5c),  0x18 },
> +	{ CCI_REG8(0x5e),  0x13 },
> +	{ CCI_REG8(0x70),  0x15 },
> +	{ CCI_REG8(0x77),  0x35 },
> +	{ CCI_REG8(0x79),  0x00 },
> +	{ CCI_REG8(0x7b),  0x08 },
> +	{ CCI_REG8(0x7d),  0x08 },
> +	{ CCI_REG8(0x7e),  0x08 },
> +	{ CCI_REG8(0x7f),  0x08 },
> +	{ CCI_REG8(0x90),  0x37 },
> +	{ CCI_REG8(0x91),  0x05 },
> +	{ CCI_REG8(0x92),  0x18 },
> +	{ CCI_REG8(0x93),  0x27 },
> +	{ CCI_REG8(0x94),  0x05 },
> +	{ CCI_REG8(0x95),  0x38 },
> +	{ CCI_REG8(0x9b),  0x00 },
> +	{ CCI_REG8(0x9c),  0x06 },
> +	{ CCI_REG8(0x9d),  0x28 },
> +	{ CCI_REG8(0x9e),  0x06 },
> +	{ CCI_REG8(0xb2),  0x0f },
> +	{ CCI_REG8(0xb3),  0x29 },
> +	{ CCI_REG8(0xbf),  0x3c },
> +	{ CCI_REG8(0xc2),  0x04 },
> +	{ CCI_REG8(0xc4),  0x00 },
> +	{ CCI_REG8(0xca),  0x20 },
> +	{ CCI_REG8(0xcb),  0x20 },
> +	{ CCI_REG8(0xcc),  0x28 },
> +	{ CCI_REG8(0xcd),  0x28 },
> +	{ CCI_REG8(0xce),  0x20 },
> +	{ CCI_REG8(0xcf),  0x20 },
> +	{ CCI_REG8(0xd0),  0x2a },
> +	{ CCI_REG8(0xd1),  0x2a },
> +	{ CCI_REG8(0xfd),  0x0f },
> +	{ CCI_REG8(0x00),  0x00 },
> +	{ CCI_REG8(0x01),  0xa0 },
> +	{ CCI_REG8(0x02),  0x48 },
> +	{ CCI_REG8(0x07),  0x8f },
> +	{ CCI_REG8(0x08),  0x70 },
> +	{ CCI_REG8(0x09),  0x01 },
> +	{ CCI_REG8(0x0b),  0x40 },
> +	{ CCI_REG8(0x0d),  0x07 },
> +	{ CCI_REG8(0x11),  0x33 },
> +	{ CCI_REG8(0x12),  0x77 },
> +	{ CCI_REG8(0x13),  0x66 },
> +	{ CCI_REG8(0x14),  0x65 },
> +	{ CCI_REG8(0x15),  0x37 },
> +	{ CCI_REG8(0x16),  0xbf },
> +	{ CCI_REG8(0x17),  0xff },
> +	{ CCI_REG8(0x18),  0xff },
> +	{ CCI_REG8(0x19),  0x12 },
> +	{ CCI_REG8(0x1a),  0x10 },
> +	{ CCI_REG8(0x1c),  0x77 },
> +	{ CCI_REG8(0x1d),  0x77 },
> +	{ CCI_REG8(0x20),  0x0f },
> +	{ CCI_REG8(0x21),  0x0f },
> +	{ CCI_REG8(0x22),  0x0f },
> +	{ CCI_REG8(0x23),  0x0f },
> +	{ CCI_REG8(0x2b),  0x20 },
> +	{ CCI_REG8(0x2c),  0x20 },
> +	{ CCI_REG8(0x2d),  0x04 },
> +	{ CCI_REG8(0xfd),  0x03 },
> +	{ CCI_REG8(0x9d),  0x0f },
> +	{ CCI_REG8(0x9f),  0x40 },
> +	{ CCI_REG8(0xfd),  0x00 },
> +	{ CCI_REG8(0x20),  0x1b },
> +	{ CCI_REG8(0xfd),  0x04 },
> +	{ CCI_REG8(0x19),  0x60 },
> +	{ CCI_REG8(0xfd),  0x02 },
> +	{ CCI_REG8(0x75),  0x05 },
> +	{ CCI_REG8(0x7f),  0x06 },
> +	{ CCI_REG8(0x9a),  0x03 },
> +	{ CCI_REG8(0xa2),  0x07 },
> +	{ CCI_REG8(0xa3),  0x10 },
> +	{ CCI_REG8(0xa5),  0x02 },
> +	{ CCI_REG8(0xa6),  0x0b },
> +	{ CCI_REG8(0xa7),  0x48 },
> +	{ CCI_REG8(0xfd),  0x07 },
> +	{ CCI_REG8(0x42),  0x00 },
> +	{ CCI_REG8(0x43),  0x80 },
> +	{ CCI_REG8(0x44),  0x00 },
> +	{ CCI_REG8(0x45),  0x80 },
> +	{ CCI_REG8(0x46),  0x00 },
> +	{ CCI_REG8(0x47),  0x80 },
> +	{ CCI_REG8(0x48),  0x00 },
> +	{ CCI_REG8(0x49),  0x80 },
> +	{ CCI_REG8(0x00),  0xf7 },
> +	{ CCI_REG8(0xfd),  0x00 },
> +	{ CCI_REG8(0xe7),  0x03 },
> +	{ CCI_REG8(0xe7),  0x00 },
> +	{ CCI_REG8(0xfd),  0x00 },
> +	{ CCI_REG8(0x93),  0x18 },
> +	{ CCI_REG8(0x94),  0xff },
> +	{ CCI_REG8(0x95),  0xbd },
> +	{ CCI_REG8(0x96),  0x1a },
> +	{ CCI_REG8(0x98),  0x04 },
> +	{ CCI_REG8(0x99),  0x08 },
> +	{ CCI_REG8(0x9b),  0x10 },
> +	{ CCI_REG8(0x9c),  0x3f },
> +	{ CCI_REG8(0xa1),  0x05 },
> +	{ CCI_REG8(0xa4),  0x2f },
> +	{ CCI_REG8(0xc0),  0x0c },
> +	{ CCI_REG8(0xc1),  0x08 },
> +	{ CCI_REG8(0xc2),  0x00 },
> +	{ CCI_REG8(0xb6),  0x20 },
> +	{ CCI_REG8(0xbb),  0x80 },
> +	{ CCI_REG8(0xfd),  0x00 },
> +	{ CCI_REG8(0xa0),  0x01 },
> +	{ CCI_REG8(0xfd),  0x01 },
> +};
> +
> +static const struct cci_reg_sequence mode_OV05C10_stream_on_regs[] = {
> +	{ CCI_REG8(0xfd), 0x01 },
> +	{ CCI_REG8(0x33), 0x03 },
> +	{ CCI_REG8(0x01), 0x02 },
> +	{ CCI_REG8(0xfd), 0x00 },
> +	{ CCI_REG8(0x20), 0x1f },
> +	{ CCI_REG8(0xfd), 0x01 },
> +};
> +
> +static const struct cci_reg_sequence mode_OV05C10_stream_off_regs[] = {
> +	{ CCI_REG8(0xfd), 0x00 },
> +	{ CCI_REG8(0x20), 0x5b },
> +	{ CCI_REG8(0xfd), 0x01 },
> +	{ CCI_REG8(0x33), 0x02 },
> +	{ CCI_REG8(0x01), 0x02 },
> +};
> +
> +static const char * const ov05c10_test_pattern_menu[] = {
> +	"Disabled",
> +	"Vertical Color Bar Type 1",
> +	"Vertical Color Bar Type 2",
> +	"Vertical Color Bar Type 3",
> +	"Vertical Color Bar Type 4"
> +};
> +
> +/* Configurations for supported link frequencies */
> +#define OV05C10_LINK_FREQ_900MHZ	(900 * HZ_PER_MHZ)
> +
> +/* Number of lanes supported */
> +#define OV05C10_DATA_LANES		2
> +
> +/* Bits per sample of sensor output */
> +#define OV05C10_BITS_PER_SAMPLE		10
> +
> +/*
> + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
> + * data rate => double data rate; number of lanes => 2; bits per pixel => 10
> + */
> +static u64 link_freq_to_pixel_rate(u64 f, u32 lane_nr)
> +{
> +	f *= 2 * lane_nr;
> +	do_div(f, OV05C10_BITS_PER_SAMPLE);
> +
> +	return f;
> +}
> +
> +/* Menu items for LINK_FREQ V4L2 control */
> +static const s64 ov05c10_link_freq_menu_items[] = {
> +	OV05C10_LINK_FREQ_900MHZ,
> +};
> +
> +/* Mode configs, currently, only support 1 mode */
> +static const struct ov05c10_mode supported_mode = {
> +	.width = MODE_WIDTH,
> +	.height = MODE_HEIGHT,
> +	.vts_def = OV05C10_VTS_30FPS,
> +	.vts_min = OV05C10_VTS_30FPS,
> +	.hts = 640,
> +	.lanes = 2,
> +	.reg_list = {
> +		.num_of_regs = ARRAY_SIZE(ov05c10_2888x1808_regs),
> +		.regs = ov05c10_2888x1808_regs,
> +	},
> +	.link_freq_index = OV05C10_LINK_FREQ_900MHZ_INDEX,
> +};
> +
> +struct ov05c10 {
> +	struct v4l2_subdev sd;
> +	struct media_pad pad;
> +
> +	/* V4L2 control handler */
> +	struct v4l2_ctrl_handler ctrl_handler;
> +
> +	/* V4L2 Controls */
> +	struct v4l2_ctrl *link_freq;
> +	struct v4l2_ctrl *pixel_rate;
> +	struct v4l2_ctrl *vblank;
> +	struct v4l2_ctrl *hblank;
> +	struct v4l2_ctrl *exposure;
> +
> +	struct regmap *regmap;
> +
> +	/* gpio descriptor */
> +	struct gpio_desc *enable_gpio;
> +
> +	/* Current page for sensor register control */
> +	int cur_page;
> +};
> +
> +#define to_ov05c10(_sd)	container_of(_sd, struct ov05c10, sd)
> +
> +static int ov05c10_init_state(struct v4l2_subdev *sd,
> +			      struct v4l2_subdev_state *sd_state)
> +{
> +	struct v4l2_mbus_framefmt *frame_fmt;
> +	struct v4l2_subdev_format fmt = {
> +		.which = V4L2_SUBDEV_FORMAT_TRY,
> +		.format = {
> +			.width = MODE_WIDTH,
> +			.height = MODE_HEIGHT,
> +			.code = MEDIA_BUS_FMT_SGRBG10_1X10,
> +			.field = V4L2_FIELD_NONE,
> +		}
> +	};
> +
> +	frame_fmt = v4l2_subdev_state_get_format(sd_state, 0);
> +	*frame_fmt = fmt.format;
> +	return 0;
> +}
> +
> +static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page, int *err)

Seems nobody cares the return value of ov05c10_switch_page() or 
ov05c10_reg_write(), etc.. It should be better to use void return, or 
use return value instead of int *err.

> +{
> +	int ret = 0;
> +
> +	if (err && *err)
> +		return *err;
> +
> +	if (page != ov05c10->cur_page) {
> +		cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, &ret);
> +		if (!ret)
> +			ov05c10->cur_page = page;
> +	}
> +
> +	if (err)
> +		*err = ret;
> +
> +	return ret;
> +}
> +
> +/* refer to the implementation of cci_read */
> +static int ov05c10_reg_read(struct ov05c10 *ov05c10, u32 reg,
> +			    u64 *val, int *err)
> +{
> +	u32 page;
> +	u32 addr;
> +	int ret = 0;
> +
> +	if (err && *err)
> +		return *err;
> +
> +	page = OV05C10_GET_PAGE_NUM(reg);
> +	addr = OV05C10_GET_REG_ADDR(reg);
> +	ov05c10_switch_page(ov05c10, page, &ret);
> +	cci_read(ov05c10->regmap, addr, val, &ret);
> +	if (err)
> +		*err = ret;
> +
> +	return ret;
> +}
> +
> +/* refer to the implementation of cci_write */
> +static int ov05c10_reg_write(struct ov05c10 *ov05c10, u32 reg,
> +			     u64 val, int *err)
> +{
> +	u32 page;
> +	u32 addr;
> +	int ret = 0;
> +
> +	if (err && *err)
> +		return *err;
> +
> +	page = OV05C10_GET_PAGE_NUM(reg);
> +	addr = OV05C10_GET_REG_ADDR(reg);
> +	ov05c10_switch_page(ov05c10, page, &ret);
> +	cci_write(ov05c10->regmap, addr, val, &ret);
> +	if (err)
> +		*err = ret;
> +
> +	return ret;
> +}
> +
> +static int ov05c10_update_vblank(struct ov05c10 *ov05c10, u32 vblank)
> +{
> +	const struct ov05c10_mode *mode = &supported_mode;
> +	u64 val;
> +	int ret = 0;
> +
> +	val = mode->height + vblank;
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_VTS, val, &ret);
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> +			  OV05C_REG_TRIGGER_START, &ret);
> +
> +	return ret;
> +}

I remembered that the ov05c10 VTS control (P1:0x05~0x06) is a bit weird. 
This register seems take the increment of VTS value, so direct write of 
VTS value will not set it properly. Does this version make AE working on 
your platform?

> +
> +static int ov05c10_update_exposure(struct ov05c10 *ov05c10, u32 exposure)
> +{
> +	int ret = 0;
> +
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_EXPOSURE, exposure, &ret);
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> +			  OV05C_REG_TRIGGER_START, &ret);
> +
> +	return ret;
> +}
> +
> +static int ov05c10_update_analog_gain(struct ov05c10 *ov05c10, u32 a_gain)
> +{
> +	int ret = 0;
> +
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_ANALOG_GAIN, a_gain, &ret);
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> +			  OV05C_REG_TRIGGER_START, &ret);
> +
> +	return ret;
> +}
> +
> +static int ov05c10_update_digital_gain(struct ov05c10 *ov05c10, u32 d_gain)
> +{
> +	u64 val;
> +	int ret = 0;
> +
> +	val = d_gain & OV05C10_DGTL_GAIN_L_MASK;
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_L, val, &ret);
> +
> +	val = (d_gain & OV05C10_DGTL_GAIN_H_MASK) >> OV05C10_DGTL_GAIN_H_SHIFT;
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_H, val, &ret);
> +
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> +			  OV05C_REG_TRIGGER_START, &ret);
> +
> +	return ret;
> +}
> +
> +static int ov05c10_enable_test_pattern(struct ov05c10 *ov05c10, u32 pattern)
> +{
> +	u64 val;
> +	int ret = 0;
> +
> +	if (pattern) {
> +		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
> +				 &val, &ret);
> +		ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
> +				  val | OV05C10_REG_TEST_PATTERN_XXX, &ret);
> +		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
> +		val |= OV05C10_TEST_PATTERN_ENABLE;
> +	} else {
> +		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
> +		val &= ~OV05C10_TEST_PATTERN_ENABLE;
> +	}
> +
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN, val, &ret);
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> +			  OV05C_REG_TRIGGER_START, &ret);
> +
> +	return ret;
> +}
> +
> +static int ov05c10_set_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct ov05c10 *ov05c10 = container_of(ctrl->handler,
> +					       struct ov05c10, ctrl_handler);
> +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> +	const struct ov05c10_mode *mode = &supported_mode;
> +	s64 max;
> +	int ret = 0;
> +
> +	/* Propagate change of current control to all related controls */
> +	if (ctrl->id == V4L2_CID_VBLANK) {
> +		s64 cur_exp = ov05c10->exposure->cur.val;
> +
> +		/* Update max exposure while meeting expected vblanking */
> +		max = mode->height + ctrl->val - OV05C10_EXPOSURE_MAX_MARGIN;
> +		cur_exp = clamp(cur_exp, ov05c10->exposure->minimum, max);
> +		ret = __v4l2_ctrl_modify_range(ov05c10->exposure,
> +					       ov05c10->exposure->minimum,
> +					       max, ov05c10->exposure->step,
> +					       cur_exp);
> +		if (!ret)
> +			return ret;
> +	}
> +
> +	/*
> +	 * Applying V4L2 control value only happens
> +	 * when power is up for streaming
> +	 */
> +	if (!pm_runtime_get_if_in_use(&client->dev))
> +		return 0;
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_ANALOGUE_GAIN:
> +		ret = ov05c10_update_analog_gain(ov05c10, ctrl->val);
> +		break;
> +	case V4L2_CID_DIGITAL_GAIN:
> +		ret = ov05c10_update_digital_gain(ov05c10, ctrl->val);
> +		break;
> +	case V4L2_CID_EXPOSURE:
> +		ret = ov05c10_update_exposure(ov05c10, ctrl->val);
> +		break;
> +	case V4L2_CID_VBLANK:
> +		ret = ov05c10_update_vblank(ov05c10, ctrl->val);
> +		break;
> +	case V4L2_CID_TEST_PATTERN:
> +		ret = ov05c10_enable_test_pattern(ov05c10, ctrl->val);
> +		break;
> +	default:
> +		ret = -ENOTTY;
> +		dev_err(&client->dev,
> +			"ctrl(id:0x%x,val:0x%x) is not handled\n",
> +			ctrl->id, ctrl->val);
> +		break;
> +	}
> +
> +	pm_runtime_put(&client->dev);
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops ov05c10_ctrl_ops = {
> +	.s_ctrl = ov05c10_set_ctrl,
> +};
> +
> +static int ov05c10_enum_mbus_code(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *sd_state,
> +				  struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	/* Only one bayer order(GRBG) is supported */
> +	if (code->index > 0)
> +		return -EINVAL;
> +
> +	code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
> +
> +	return 0;
> +}
> +
> +static int ov05c10_enum_frame_size(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *sd_state,
> +				   struct v4l2_subdev_frame_size_enum *fse)
> +{
> +	/* ov05c10 driver currently only supports 1 mode*/
> +	if (fse->index != 0)
> +		return -EINVAL;
> +
> +	if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
> +		return -EINVAL;
> +
> +	fse->min_width = supported_mode.width;
> +	fse->max_width = fse->min_width;
> +	fse->min_height = supported_mode.height;
> +	fse->max_height = fse->min_height;
> +
> +	return 0;
> +}
> +
> +static void ov05c10_update_pad_format(const struct ov05c10_mode *mode,
> +				      struct v4l2_subdev_format *fmt)
> +{
> +	fmt->format.width = mode->width;
> +	fmt->format.height = mode->height;
> +	fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
> +	fmt->format.field = V4L2_FIELD_NONE;
> +}
> +
> +static int ov05c10_set_pad_format(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *sd_state,
> +				  struct v4l2_subdev_format *fmt)
> +{
> +	struct v4l2_mbus_framefmt *framefmt;
> +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> +	const struct ov05c10_mode *mode;
> +	s32 vblank_def;
> +	s32 vblank_min;
> +	s64 pixel_rate;
> +	s64 link_freq;
> +	s64 h_blank;
> +
> +	/* Only one raw bayer(GRBG) order is supported */
> +	if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10)
> +		fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
> +
> +	mode = &supported_mode;
> +	ov05c10_update_pad_format(mode, fmt);
> +	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> +		framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
> +		*framefmt = fmt->format;
> +	} else {
> +		__v4l2_ctrl_s_ctrl(ov05c10->link_freq, mode->link_freq_index);
> +		link_freq = ov05c10_link_freq_menu_items[mode->link_freq_index];
> +		pixel_rate = link_freq_to_pixel_rate(link_freq,
> +						     mode->lanes);
> +		__v4l2_ctrl_s_ctrl_int64(ov05c10->pixel_rate, pixel_rate);
> +
> +		/* Update limits and set FPS to default */
> +		vblank_def = mode->vts_def - mode->height;
> +		vblank_min = mode->vts_min - mode->height;
> +		__v4l2_ctrl_modify_range(ov05c10->vblank, vblank_min,
> +					 OV05C10_VTS_MAX - mode->height,
> +					 1, vblank_def);
> +		__v4l2_ctrl_s_ctrl(ov05c10->vblank, vblank_def);
> +		h_blank = mode->hts;
> +		__v4l2_ctrl_modify_range(ov05c10->hblank, h_blank,
> +					 h_blank, 1, h_blank);
> +	}
> +
> +	return 0;
> +}
> +
> +static int ov05c10_start_streaming(struct ov05c10 *ov05c10)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> +	const struct ov05c10_mode *mode = &supported_mode;
> +	const struct ov05c10_reg_list *reg_list;
> +	int ret = 0;
> +
> +	/* Apply default values of current mode */
> +	reg_list = &mode->reg_list;
> +	cci_multi_reg_write(ov05c10->regmap, reg_list->regs,
> +			    reg_list->num_of_regs, &ret);
> +	if (ret) {
> +		dev_err(&client->dev, "fail to set mode, ret: %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* Apply customized values from user */
> +	ret =  __v4l2_ctrl_handler_setup(ov05c10->sd.ctrl_handler);
> +	if (ret) {
> +		dev_err(&client->dev, "failed to setup v4l2 handler %d\n", ret);
> +		return ret;
> +	}
> +
> +	cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_on_regs,
> +			    ARRAY_SIZE(mode_OV05C10_stream_on_regs), &ret);
> +	if (ret)
> +		dev_err(&client->dev, "fail to start the streaming\n");
> +
> +	return ret;
> +}
> +
> +static int ov05c10_stop_streaming(struct ov05c10 *ov05c10)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> +	int ret = 0;
> +
> +	cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_off_regs,
> +			    ARRAY_SIZE(mode_OV05C10_stream_off_regs), &ret);
> +	if (ret)
> +		dev_err(&client->dev, "fail to stop the streaming\n");
> +
> +	return ret;
> +}
> +
> +static void ov05c10_sensor_power_set(struct ov05c10 *ov05c10, bool on)
> +{
> +	if (on) {
> +		gpiod_set_value(ov05c10->enable_gpio, 0);
> +		usleep_range(10, 20);
> +
> +		gpiod_set_value(ov05c10->enable_gpio, 1);
> +		usleep_range(1000, 2000);

According to the datasheet, ov05c10 needs at least 8 ms to work after 
its XSHUTDN pin pulled to high. 1 ms maybe too quick, did you tested it? 
Or the enable_gpio is actually not the XSHUTDN pin?

On Intel platforms, if the sensor driver controls the module power, 
ususally it requires GPIO "reset", regulator "avdd" and clk "img_clk" 
assigned by kernel driver intel_skl_int3472_discrete. I'm not sure 
whether any devices on market using this power control solution, but if 
any, missing those resources will stop them from powering-up cameras.

> +	} else {
> +		gpiod_set_value(ov05c10->enable_gpio, 0);
> +		usleep_range(10, 20);
> +	}
> +}
> +
> +static int ov05c10_enable_streams(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state, u32 pad,
> +				  u64 streams_mask)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(sd);
> +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> +	int ret = 0;
> +
> +	ret = pm_runtime_resume_and_get(&client->dev);
> +	if (ret < 0)
> +		return ret;
> +
> +	ov05c10->cur_page = -1;
> +
> +	ret = ov05c10_start_streaming(ov05c10);
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	return 0;
> +
> +err_rpm_put:
> +	pm_runtime_put(&client->dev);
> +	return ret;
> +}
> +
> +static int ov05c10_disable_streams(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state, u32 pad,
> +				   u64 streams_mask)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(sd);
> +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> +
> +	ov05c10_stop_streaming(ov05c10);
> +	pm_runtime_put(&client->dev);
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_subdev_video_ops ov05c10_video_ops = {
> +	.s_stream = v4l2_subdev_s_stream_helper,
> +};
> +
> +static const struct v4l2_subdev_pad_ops ov05c10_pad_ops = {
> +	.enum_mbus_code = ov05c10_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = ov05c10_set_pad_format,
> +	.enum_frame_size = ov05c10_enum_frame_size,
> +	.enable_streams = ov05c10_enable_streams,
> +	.disable_streams = ov05c10_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops ov05c10_subdev_ops = {
> +	.video = &ov05c10_video_ops,
> +	.pad = &ov05c10_pad_ops,
> +};
> +
> +static const struct media_entity_operations ov05c10_subdev_entity_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +static const struct v4l2_subdev_internal_ops ov05c10_internal_ops = {
> +	.init_state = ov05c10_init_state,
> +};
> +
> +static int ov05c10_init_controls(struct ov05c10 *ov05c10)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> +	const struct ov05c10_mode *mode = &supported_mode;
> +	struct v4l2_fwnode_device_properties props;
> +	struct v4l2_ctrl_handler *ctrl_hdlr;
> +	s64 pixel_rate_max;
> +	s64 exposure_max;
> +	s64 vblank_def;
> +	s64 vblank_min;
> +	u32 max_items;
> +	s64 hblank;
> +	int ret;
> +
> +	ret = v4l2_ctrl_handler_init(&ov05c10->ctrl_handler, 10);
> +	if (ret)
> +		return ret;
> +
> +	ctrl_hdlr = &ov05c10->ctrl_handler;
> +
> +	max_items = ARRAY_SIZE(ov05c10_link_freq_menu_items) - 1;
> +	ov05c10->link_freq =
> +		v4l2_ctrl_new_int_menu(ctrl_hdlr,
> +				       NULL,
> +				       V4L2_CID_LINK_FREQ,
> +				       max_items,
> +				       0,
> +				       ov05c10_link_freq_menu_items);
> +	if (ov05c10->link_freq)
> +		ov05c10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +
> +	pixel_rate_max =
> +		link_freq_to_pixel_rate(ov05c10_link_freq_menu_items[0],
> +					supported_mode.lanes);
> +	ov05c10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
> +						V4L2_CID_PIXEL_RATE,
> +						0, pixel_rate_max,
> +						1, pixel_rate_max);
> +
> +	vblank_def = mode->vts_def - mode->height;
> +	vblank_min = mode->vts_min - mode->height;
> +	ov05c10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
> +					    V4L2_CID_VBLANK,
> +					    vblank_min,
> +					    OV05C10_VTS_MAX - mode->height,
> +					    1, vblank_def);
> +
> +	hblank = (mode->hts > mode->width) ? (mode->hts - mode->width) : 0;

Here your hts uses 640 but width is 2888, which means hblank is set to 0 
here. This is wrong, please fix your configuration.

> +	ov05c10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
> +					    V4L2_CID_HBLANK,
> +					    hblank, hblank, 1, hblank);
> +	if (ov05c10->hblank)
> +		ov05c10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +
> +	exposure_max = mode->vts_def - OV05C10_EXPOSURE_MAX_MARGIN;
> +	ov05c10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
> +					      V4L2_CID_EXPOSURE,
> +					      OV05C10_EXPOSURE_MIN,
> +					      exposure_max,
> +					      OV05C10_EXPOSURE_STEP,
> +					      exposure_max);
> +
> +	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
> +			  OV05C10_ANA_GAIN_MIN, OV05C10_ANA_GAIN_MAX,
> +			  OV05C10_ANA_GAIN_STEP, OV05C10_ANA_GAIN_DEFAULT);
> +
> +	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
> +			  OV05C10_DGTL_GAIN_MIN, OV05C10_DGTL_GAIN_MAX,
> +			  OV05C10_DGTL_GAIN_STEP, OV05C10_DGTL_GAIN_DEFAULT);
> +
> +	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov05c10_ctrl_ops,
> +				     V4L2_CID_TEST_PATTERN,
> +				     ARRAY_SIZE(ov05c10_test_pattern_menu) - 1,
> +				     0, 0, ov05c10_test_pattern_menu);
> +
> +	if (ctrl_hdlr->error) {
> +		ret = ctrl_hdlr->error;
> +		dev_err(&client->dev, "V4L2 control init failed (%d)\n", ret);
> +		goto err_hdl_free;
> +	}
> +
> +	ret = v4l2_fwnode_device_parse(&client->dev, &props);
> +	if (ret)
> +		goto err_hdl_free;
> +
> +	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov05c10_ctrl_ops,
> +					      &props);
> +	if (ret)
> +		goto err_hdl_free;
> +
> +	ov05c10->sd.ctrl_handler = ctrl_hdlr;
> +
> +	return 0;
> +
> +err_hdl_free:
> +	v4l2_ctrl_handler_free(ctrl_hdlr);
> +
> +	return ret;
> +}
> +
> +static int ov05c10_parse_endpoint(struct device *dev,
> +				  struct fwnode_handle *fwnode)
> +{
> +	struct v4l2_fwnode_endpoint bus_cfg = {
> +		.bus_type = V4L2_MBUS_CSI2_DPHY
> +	};
> +	struct fwnode_handle *ep;
> +	unsigned long bitmap;
> +	int ret;
> +
> +	ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
> +	if (!ep) {
> +		dev_err(dev, "Failed to get next endpoint\n");
> +		return -ENXIO;
> +	}
> +
> +	ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
> +	fwnode_handle_put(ep);
> +	if (ret)
> +		return ret;
> +
> +	if (bus_cfg.bus.mipi_csi2.num_data_lanes != supported_mode.lanes) {
> +		dev_err(dev,
> +			"number of CSI2 data lanes %d is not supported\n",
> +			bus_cfg.bus.mipi_csi2.num_data_lanes);
> +		ret = -EINVAL;
> +		goto err_endpoint_free;
> +	}
> +
> +	ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
> +				       bus_cfg.nr_of_link_frequencies,
> +				       ov05c10_link_frequencies,
> +				       ARRAY_SIZE(ov05c10_link_frequencies),
> +				       &bitmap);
> +	if (ret)
> +		dev_err(dev, "v4l2_link_freq_to_bitmap fail with %d\n", ret);
> +err_endpoint_free:
> +	v4l2_fwnode_endpoint_free(&bus_cfg);
> +
> +	return ret;
> +}
> +
> +static int ov05c10_probe(struct i2c_client *client)
> +{
> +	struct ov05c10 *ov05c10;
> +	u32 clkfreq;
> +	int ret;
> +
> +	ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), GFP_KERNEL);
> +	if (!ov05c10)
> +		return -ENOMEM;
> +
> +	struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> +
> +	ret = fwnode_property_read_u32(fwnode, "clock-frequency", &clkfreq);

Maybe it's better to separate this part fwnode and GPIO code into a 
standalone function?

> +	if (ret)
> +		return  dev_err_probe(&client->dev, -EINVAL,
> +				      "fail to get clock freq\n");
> +	if (clkfreq != OV05C10_REF_CLK)
> +		return dev_err_probe(&client->dev, -EINVAL,
> +				     "fail invalid clock freq %u, %lu expected\n",
> +				     clkfreq, OV05C10_REF_CLK);
> +
> +	ret = ov05c10_parse_endpoint(&client->dev, fwnode);
> +	if (ret)
> +		return dev_err_probe(&client->dev, -EINVAL,
> +				     "fail to parse endpoint\n");
> +
> +	ov05c10->enable_gpio = devm_gpiod_get(&client->dev, "enable",
> +					      GPIOD_OUT_LOW);
> +	if (IS_ERR(ov05c10->enable_gpio))
> +		return dev_err_probe(&client->dev,
> +				     PTR_ERR(ov05c10->enable_gpio),
> +				     "fail to get enable gpio\n");
> +
> +	v4l2_i2c_subdev_init(&ov05c10->sd, client, &ov05c10_subdev_ops);
> +
> +	ov05c10->regmap = devm_cci_regmap_init_i2c(client, 8);
> +	if (IS_ERR(ov05c10->regmap))
> +		return dev_err_probe(&client->dev, PTR_ERR(ov05c10->regmap),
> +				     "fail to init cci\n");
> +
> +	ov05c10->cur_page = -1;
> +
> +	ret = ov05c10_init_controls(ov05c10);
> +	if (ret)
> +		return dev_err_probe(&client->dev, ret, "fail to init ctl\n");
> +
> +	ov05c10->sd.internal_ops = &ov05c10_internal_ops;
> +	ov05c10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	ov05c10->sd.entity.ops = &ov05c10_subdev_entity_ops;
> +	ov05c10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> +
> +	ov05c10->pad.flags = MEDIA_PAD_FL_SOURCE;
> +
> +	ret = media_entity_pads_init(&ov05c10->sd.entity, NUM_OF_PADS,
> +				     &ov05c10->pad);
> +	if (ret)
> +		goto err_hdl_free;
> +
> +	ret = v4l2_subdev_init_finalize(&ov05c10->sd);
> +	if (ret < 0)
> +		goto err_media_entity_cleanup;
> +
> +	ret = v4l2_async_register_subdev_sensor(&ov05c10->sd);
> +	if (ret)
> +		goto err_media_entity_cleanup;
> +
> +	pm_runtime_set_active(&client->dev);
> +	pm_runtime_enable(&client->dev);
> +	pm_runtime_idle(&client->dev);
> +	pm_runtime_set_autosuspend_delay(&client->dev, 1000);
> +	pm_runtime_use_autosuspend(&client->dev);
> +	return 0;
> +
> +err_media_entity_cleanup:
> +	media_entity_cleanup(&ov05c10->sd.entity);
> +
> +err_hdl_free:
> +	v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
> +
> +	return ret;
> +}
> +
> +static void ov05c10_remove(struct i2c_client *client)
> +{
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> +
> +	v4l2_async_unregister_subdev(sd);
> +	media_entity_cleanup(&sd->entity);
> +	v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
> +
> +	pm_runtime_disable(&client->dev);
> +	pm_runtime_set_suspended(&client->dev);
> +}
> +
> +static int ov05c10_runtime_resume(struct device *dev)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> +
> +	ov05c10_sensor_power_set(ov05c10, true);
> +	return 0;
> +}
> +
> +static int ov05c10_runtime_suspend(struct device *dev)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> +
> +	ov05c10_sensor_power_set(ov05c10, false);
> +	return 0;
> +}
> +
> +static DEFINE_RUNTIME_DEV_PM_OPS(ov05c10_pm_ops, ov05c10_runtime_suspend,
> +				 ov05c10_runtime_resume, NULL);
> +
> +static const struct i2c_device_id ov05c10_i2c_ids[] = {
> +	{"ov05c10", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, ov05c10_i2c_ids);
> +
> +static struct i2c_driver ov05c10_i2c_driver = {
> +	.driver = {
> +		.name = DRV_NAME,
> +		.pm = pm_ptr(&ov05c10_pm_ops),
> +	},
> +	.id_table = ov05c10_i2c_ids,
> +	.probe = ov05c10_probe,
> +	.remove = ov05c10_remove,
> +};
> +
> +module_i2c_driver(ov05c10_i2c_driver);
> +
> +MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
> +MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@amd.com>");
> +MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
> +MODULE_DESCRIPTION("OmniVision OV05C1010 sensor driver");

OV05C10

> +MODULE_LICENSE("GPL");


Hi Sakari,

Seems there are already several camera sensors using page-based 
registers. Is it a good idea to add page support in CCI interface?


Best Regards,
Hao Yao


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-13  4:55 ` Hao Yao
@ 2025-06-13 11:02   ` Kieran Bingham
  2025-06-13 12:05     ` Bryan O'Donoghue
  2025-06-16 23:46     ` Nirujogi, Pratap
  2025-06-13 22:02   ` Sakari Ailus
  2025-06-17  0:19   ` Nirujogi, Pratap
  2 siblings, 2 replies; 60+ messages in thread
From: Kieran Bingham @ 2025-06-13 11:02 UTC (permalink / raw)
  To: Hao Yao, Pratap Nirujogi, sakari.ailus
  Cc: mchehab, laurent.pinchart, hverkuil, bryan.odonoghue, krzk,
	dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen, linux-media,
	linux-kernel, benjamin.chan, bin.du, grosikop, king.li, dantony,
	vengutta, dongcheng.yan, jason.z.chen, jimmy.su

Quoting Hao Yao (2025-06-13 05:55:46)
> Hi Pratap,
> 
> Thanks for your patch.
> 
> This patch is written for your camera sensor module, which seems very 
> different from those already applied on Dell laptops (some of "Dell Pro" 
> series). Looking into the driver, I think this version will break the 

Have there been existing efforts from Intel to upstream support for that
device?

Or is this simply the case that Intel have lost the race by not working
towards upstream?

I see plenty of references to OV05C10 in the intel/ipu6 repositories on
github - but that doesn't count as upstream here ...

Perhaps could Intel work to support the additional requirements on top
of AMD's driver ?


But having multiple devices use the same sensor driver is a good thing
here.

I think it will highlight that werever possible - the code below should
be factored out to support the different configuration requirements.
Cleaning up the large tables of register addresses and making those
configurable functions for example configuring the link rate
independently would be really beneficial!

That's precisely why we continually push for reducing the large
"undocumented register" tables in sensor drivers...



> devices using ov05c10 sensor.
> 
> I think this patch is better to be validated on existing devices, but 
> please do some fixes before we can do validation. Please check my 
> comments inline.
> 
> 
> On 2025/6/10 03:42, Pratap Nirujogi wrote:
> > Add driver for OmniVision 5.2M OV05C10 sensor. This driver
> > supports only the full size normal 2888x1808@30fps 2-lane
> > sensor profile.
> > 
> > Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> > Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> > Co-developed-by: Bin Du <bin.du@amd.com>
> > Signed-off-by: Bin Du <bin.du@amd.com>
> > Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
> > ---
> > Changes v2 -> v3:
> > 
> > * Update "refclk" property variable as "clock-frequency".
> > * Update sensor GPIO connector id name.
> > * Fix sensor v4l2 compliance issue.
> > * Fix license info.
> > * Address review comments.
> > 
> >   MAINTAINERS                 |    8 +
> >   drivers/media/i2c/Kconfig   |   10 +
> >   drivers/media/i2c/Makefile  |    1 +
> >   drivers/media/i2c/ov05c10.c | 1061 +++++++++++++++++++++++++++++++++++
> >   4 files changed, 1080 insertions(+)
> >   create mode 100644 drivers/media/i2c/ov05c10.c
> > 
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index a92290fffa16..caca25d00bf2 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -18303,6 +18303,14 @@ T:   git git://linuxtv.org/media.git
> >   F:  Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml
> >   F:  drivers/media/i2c/ov02e10.c
> >   
> > +OMNIVISION OV05C10 SENSOR DRIVER
> > +M:   Nirujogi Pratap <pratap.nirujogi@amd.com>
> > +M:   Bin Du <bin.du@amd.com>
> > +L:   linux-media@vger.kernel.org
> > +S:   Maintained
> > +T:   git git://linuxtv.org/media.git
> > +F:   drivers/media/i2c/ov05c10.c
> > +
> >   OMNIVISION OV08D10 SENSOR DRIVER
> >   M:  Jimmy Su <jimmy.su@intel.com>
> >   L:  linux-media@vger.kernel.org
> > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > index e68202954a8f..1662fb29d75c 100644
> > --- a/drivers/media/i2c/Kconfig
> > +++ b/drivers/media/i2c/Kconfig
> > @@ -377,6 +377,16 @@ config VIDEO_OV02C10
> >         To compile this driver as a module, choose M here: the
> >         module will be called ov02c10.
> >   
> > +config VIDEO_OV05C10
> > +     tristate "OmniVision OV05C10 sensor support"
> > +     select V4L2_CCI_I2C
> > +     help
> > +       This is a Video4Linux2 sensor driver for the OmniVision
> > +       OV05C10 camera.
> > +
> > +       To compile this driver as a module, choose M here: the
> > +       module will be called OV05C10.
> > +
> >   config VIDEO_OV08D10
> >           tristate "OmniVision OV08D10 sensor support"
> >           help
> > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > index 5873d29433ee..b4a1d721a7f2 100644
> > --- a/drivers/media/i2c/Makefile
> > +++ b/drivers/media/i2c/Makefile
> > @@ -85,6 +85,7 @@ obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
> >   obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
> >   obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o
> >   obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o
> > +obj-$(CONFIG_VIDEO_OV05C10) += ov05c10.o
> >   obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
> >   obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
> >   obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
> > diff --git a/drivers/media/i2c/ov05c10.c b/drivers/media/i2c/ov05c10.c
> > new file mode 100644
> > index 000000000000..9a1e493c4073
> > --- /dev/null
> > +++ b/drivers/media/i2c/ov05c10.c
> > @@ -0,0 +1,1061 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +// Copyright (C) 2025 Advanced Micro Devices, Inc.
> > +
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/gpio.h>
> > +#include <linux/i2c.h>
> > +#include <linux/module.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/units.h>
> > +#include <media/v4l2-cci.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-fwnode.h>
> > +
> > +#define DRV_NAME                     "ov05c10"
> > +#define OV05C10_REF_CLK                      (24 * HZ_PER_MHZ)
> 
> Seems your module use 24 MHz clock input. The Dell's modules always use 
> 19.2MHz, which means your the PLL settings will not work on Dell's.

That sounds like a feature that Dell and Intel could work towards
supporting ?

For instance, we made the Sony IMX283 support multiple input frequencies:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/media/i2c/imx283.c#n263

Of course it would be even better if we could always dynamically
calculate the PLLs correctly based on the input clocks.

And if we can make the drivers more freely configurable instead of
mode-based - then they would be more adaptable to the different platform
configurations possible in different environments.

> 
> > +
> > +#define MODE_WIDTH  2888
> > +#define MODE_HEIGHT 1808
> > +
> > +#define PAGE_NUM_MASK                        0xff000000
> > +#define PAGE_NUM_SHIFT                       24
> > +#define REG_ADDR_MASK                        0x00ffffff
> > +
> > +#define OV05C10_SYSCTL_PAGE          (0 << PAGE_NUM_SHIFT)
> > +#define OV05C10_CISCTL_PAGE          (1 << PAGE_NUM_SHIFT)
> > +#define OV05C10_ISPCTL_PAGE          (4 << PAGE_NUM_SHIFT)
> > +
> > +/* Chip ID */
> > +#define OV05C10_REG_CHIP_ID          (CCI_REG24(0x00) | OV05C10_SYSCTL_PAGE)
> > +#define OV05C10_CHIP_ID                      0x43055610
> > +
> > +/* Control registers */
> > +#define OV05C10_REG_TRIGGER          (CCI_REG8(0x01) | OV05C10_CISCTL_PAGE)
> > +#define OV05C_REG_TRIGGER_START              BIT(0)
> > +
> > +/* Exposure control */
> > +#define OV05C10_REG_EXPOSURE         (CCI_REG24(0x02) | OV05C10_CISCTL_PAGE)
> > +#define OV05C10_EXPOSURE_MAX_MARGIN  33
> > +#define OV05C10_EXPOSURE_MIN         4
> > +#define OV05C10_EXPOSURE_STEP                1
> > +#define OV05C10_EXPOSURE_DEFAULT     0x40
> > +
> > +/* V_TIMING internal */
> > +#define OV05C10_REG_VTS                      (CCI_REG16(0x05) | OV05C10_CISCTL_PAGE)
> > +#define OV05C10_VTS_30FPS            1860
> > +#define OV05C10_VTS_MAX                      0x7fff
> > +
> > +/* Test Pattern Control */
> > +#define OV05C10_REG_TEST_PATTERN     (CCI_REG8(0x12) | OV05C10_ISPCTL_PAGE)
> > +#define OV05C10_TEST_PATTERN_ENABLE  BIT(0)
> > +#define OV05C10_REG_TEST_PATTERN_CTL (CCI_REG8(0xf3) | OV05C10_ISPCTL_PAGE)
> > +#define OV05C10_REG_TEST_PATTERN_XXX BIT(0)
> > +
> > +/* Digital gain control */
> > +#define OV05C10_REG_DGTL_GAIN_H              (CCI_REG8(0x21) | OV05C10_CISCTL_PAGE)
> > +#define OV05C10_REG_DGTL_GAIN_L              (CCI_REG8(0x22) | OV05C10_CISCTL_PAGE)
> > +
> > +#define OV05C10_DGTL_GAIN_MIN                0x40
> > +#define OV05C10_DGTL_GAIN_MAX                0xff
> > +#define OV05C10_DGTL_GAIN_DEFAULT    0x40
> > +#define OV05C10_DGTL_GAIN_STEP               1
> > +
> > +#define OV05C10_DGTL_GAIN_L_MASK     0xff
> > +#define OV05C10_DGTL_GAIN_H_SHIFT    8
> > +#define OV05C10_DGTL_GAIN_H_MASK     0xff00
> > +
> > +/* Analog gain control */
> > +#define OV05C10_REG_ANALOG_GAIN              (CCI_REG8(0x24) | OV05C10_CISCTL_PAGE)
> > +#define OV05C10_ANA_GAIN_MIN         0x80
> > +#define OV05C10_ANA_GAIN_MAX         0x07c0
> > +#define OV05C10_ANA_GAIN_STEP                1
> > +#define OV05C10_ANA_GAIN_DEFAULT     0x80
> > +
> > +/* H TIMING internal */
> > +#define OV05C10_REG_HTS                      (CCI_REG16(0x37) | OV05C10_CISCTL_PAGE)
> > +#define OV05C10_HTS_30FPS            0x0280
> > +
> > +/* Page selection */
> > +#define OV05C10_REG_PAGE_CTL         CCI_REG8(0xfd)
> > +
> > +#define NUM_OF_PADS 1
> > +
> > +#define OV05C10_GET_PAGE_NUM(reg)    (((reg) & PAGE_NUM_MASK) >>\
> > +                                      PAGE_NUM_SHIFT)
> > +#define OV05C10_GET_REG_ADDR(reg)    ((reg) & REG_ADDR_MASK)
> > +
> > +enum {
> > +     OV05C10_LINK_FREQ_900MHZ_INDEX,
> > +};
> > +
> > +struct ov05c10_reg_list {
> > +     u32 num_of_regs;
> > +     const struct cci_reg_sequence *regs;
> > +};
> > +
> > +/* Mode : resolution and related config&values */
> > +struct ov05c10_mode {
> > +     /* Frame width */
> > +     u32 width;
> > +     /* Frame height */
> > +     u32 height;
> > +     /* number of lanes */
> > +     u32 lanes;
> > +
> > +     /* V-timing */
> > +     u32 vts_def;
> > +     u32 vts_min;
> > +
> > +     /* HTS */
> > +     u32 hts;
> > +
> > +     /* Index of Link frequency config to be used */
> > +     u32 link_freq_index;
> > +
> > +     /* Default register values */
> > +     struct ov05c10_reg_list reg_list;
> > +};
> > +
> > +static const s64 ov05c10_link_frequencies[] = {
> > +     925 * HZ_PER_MHZ,
> > +};
> 
> Is it 900 MHz, or 925 MHz?
> 
> > +
> > +/* 2888x1808 30fps, 1800mbps, 2lane, 24mhz */
> 
> Currently Dell's devices with ov05c10 use a CV chip to passthrough MIPI 
> CSI signals, but it supports max 750 MHz link frequency. That's why this 
> version:
> https://github.com/intel/ipu6-drivers/blob/master/drivers/media/i2c/ov05c10.c
> uses 480 MHz link frequency and a different resolution setting 
> (2800x1576). At least the setting in out-of-tree Github driver should be 
> merged into this version.

If Pratap doesn't have that device, can you work towards adding the
support and testing?



> 
> > +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
> > +     { CCI_REG8(0xfd),  0x00 },
> > +     { CCI_REG8(0x20),  0x00 },
> > +     { CCI_REG8(0xfd),  0x00 },
> > +     { CCI_REG8(0x20),  0x0b },
> > +     { CCI_REG8(0xc1),  0x09 },
> > +     { CCI_REG8(0x21),  0x06 },
> > +     { CCI_REG8(0x14),  0x78 },
> > +     { CCI_REG8(0xe7),  0x03 },
> > +     { CCI_REG8(0xe7),  0x00 },
> > +     { CCI_REG8(0x21),  0x00 },
> > +     { CCI_REG8(0xfd),  0x01 },
> > +     { CCI_REG8(0x03),  0x00 },
> > +     { CCI_REG8(0x04),  0x06 },
> > +     { CCI_REG8(0x05),  0x07 },
> > +     { CCI_REG8(0x06),  0x44 },
> > +     { CCI_REG8(0x07),  0x08 },
> > +     { CCI_REG8(0x1b),  0x01 },
> > +     { CCI_REG8(0x24),  0xff },
> > +     { CCI_REG8(0x32),  0x03 },
> > +     { CCI_REG8(0x42),  0x5d },
> > +     { CCI_REG8(0x43),  0x08 },
> > +     { CCI_REG8(0x44),  0x81 },
> > +     { CCI_REG8(0x46),  0x5f },
> > +     { CCI_REG8(0x48),  0x18 },
> > +     { CCI_REG8(0x49),  0x04 },
> > +     { CCI_REG8(0x5c),  0x18 },
> > +     { CCI_REG8(0x5e),  0x13 },
> > +     { CCI_REG8(0x70),  0x15 },
> > +     { CCI_REG8(0x77),  0x35 },
> > +     { CCI_REG8(0x79),  0x00 },
> > +     { CCI_REG8(0x7b),  0x08 },
> > +     { CCI_REG8(0x7d),  0x08 },
> > +     { CCI_REG8(0x7e),  0x08 },
> > +     { CCI_REG8(0x7f),  0x08 },
> > +     { CCI_REG8(0x90),  0x37 },
> > +     { CCI_REG8(0x91),  0x05 },
> > +     { CCI_REG8(0x92),  0x18 },
> > +     { CCI_REG8(0x93),  0x27 },
> > +     { CCI_REG8(0x94),  0x05 },
> > +     { CCI_REG8(0x95),  0x38 },
> > +     { CCI_REG8(0x9b),  0x00 },
> > +     { CCI_REG8(0x9c),  0x06 },
> > +     { CCI_REG8(0x9d),  0x28 },
> > +     { CCI_REG8(0x9e),  0x06 },
> > +     { CCI_REG8(0xb2),  0x0f },
> > +     { CCI_REG8(0xb3),  0x29 },
> > +     { CCI_REG8(0xbf),  0x3c },
> > +     { CCI_REG8(0xc2),  0x04 },
> > +     { CCI_REG8(0xc4),  0x00 },
> > +     { CCI_REG8(0xca),  0x20 },
> > +     { CCI_REG8(0xcb),  0x20 },
> > +     { CCI_REG8(0xcc),  0x28 },
> > +     { CCI_REG8(0xcd),  0x28 },
> > +     { CCI_REG8(0xce),  0x20 },
> > +     { CCI_REG8(0xcf),  0x20 },
> > +     { CCI_REG8(0xd0),  0x2a },
> > +     { CCI_REG8(0xd1),  0x2a },
> > +     { CCI_REG8(0xfd),  0x0f },
> > +     { CCI_REG8(0x00),  0x00 },
> > +     { CCI_REG8(0x01),  0xa0 },
> > +     { CCI_REG8(0x02),  0x48 },
> > +     { CCI_REG8(0x07),  0x8f },
> > +     { CCI_REG8(0x08),  0x70 },
> > +     { CCI_REG8(0x09),  0x01 },
> > +     { CCI_REG8(0x0b),  0x40 },
> > +     { CCI_REG8(0x0d),  0x07 },
> > +     { CCI_REG8(0x11),  0x33 },
> > +     { CCI_REG8(0x12),  0x77 },
> > +     { CCI_REG8(0x13),  0x66 },
> > +     { CCI_REG8(0x14),  0x65 },
> > +     { CCI_REG8(0x15),  0x37 },
> > +     { CCI_REG8(0x16),  0xbf },
> > +     { CCI_REG8(0x17),  0xff },
> > +     { CCI_REG8(0x18),  0xff },
> > +     { CCI_REG8(0x19),  0x12 },
> > +     { CCI_REG8(0x1a),  0x10 },
> > +     { CCI_REG8(0x1c),  0x77 },
> > +     { CCI_REG8(0x1d),  0x77 },
> > +     { CCI_REG8(0x20),  0x0f },
> > +     { CCI_REG8(0x21),  0x0f },
> > +     { CCI_REG8(0x22),  0x0f },
> > +     { CCI_REG8(0x23),  0x0f },
> > +     { CCI_REG8(0x2b),  0x20 },
> > +     { CCI_REG8(0x2c),  0x20 },
> > +     { CCI_REG8(0x2d),  0x04 },
> > +     { CCI_REG8(0xfd),  0x03 },
> > +     { CCI_REG8(0x9d),  0x0f },
> > +     { CCI_REG8(0x9f),  0x40 },
> > +     { CCI_REG8(0xfd),  0x00 },
> > +     { CCI_REG8(0x20),  0x1b },
> > +     { CCI_REG8(0xfd),  0x04 },
> > +     { CCI_REG8(0x19),  0x60 },
> > +     { CCI_REG8(0xfd),  0x02 },
> > +     { CCI_REG8(0x75),  0x05 },
> > +     { CCI_REG8(0x7f),  0x06 },
> > +     { CCI_REG8(0x9a),  0x03 },
> > +     { CCI_REG8(0xa2),  0x07 },
> > +     { CCI_REG8(0xa3),  0x10 },
> > +     { CCI_REG8(0xa5),  0x02 },
> > +     { CCI_REG8(0xa6),  0x0b },
> > +     { CCI_REG8(0xa7),  0x48 },
> > +     { CCI_REG8(0xfd),  0x07 },
> > +     { CCI_REG8(0x42),  0x00 },
> > +     { CCI_REG8(0x43),  0x80 },
> > +     { CCI_REG8(0x44),  0x00 },
> > +     { CCI_REG8(0x45),  0x80 },
> > +     { CCI_REG8(0x46),  0x00 },
> > +     { CCI_REG8(0x47),  0x80 },
> > +     { CCI_REG8(0x48),  0x00 },
> > +     { CCI_REG8(0x49),  0x80 },
> > +     { CCI_REG8(0x00),  0xf7 },
> > +     { CCI_REG8(0xfd),  0x00 },
> > +     { CCI_REG8(0xe7),  0x03 },
> > +     { CCI_REG8(0xe7),  0x00 },
> > +     { CCI_REG8(0xfd),  0x00 },
> > +     { CCI_REG8(0x93),  0x18 },
> > +     { CCI_REG8(0x94),  0xff },
> > +     { CCI_REG8(0x95),  0xbd },
> > +     { CCI_REG8(0x96),  0x1a },
> > +     { CCI_REG8(0x98),  0x04 },
> > +     { CCI_REG8(0x99),  0x08 },
> > +     { CCI_REG8(0x9b),  0x10 },
> > +     { CCI_REG8(0x9c),  0x3f },
> > +     { CCI_REG8(0xa1),  0x05 },
> > +     { CCI_REG8(0xa4),  0x2f },
> > +     { CCI_REG8(0xc0),  0x0c },
> > +     { CCI_REG8(0xc1),  0x08 },
> > +     { CCI_REG8(0xc2),  0x00 },
> > +     { CCI_REG8(0xb6),  0x20 },
> > +     { CCI_REG8(0xbb),  0x80 },
> > +     { CCI_REG8(0xfd),  0x00 },
> > +     { CCI_REG8(0xa0),  0x01 },
> > +     { CCI_REG8(0xfd),  0x01 },
> > +};
> > +
> > +static const struct cci_reg_sequence mode_OV05C10_stream_on_regs[] = {

Why is the word 'mode_ in here ?

> > +     { CCI_REG8(0xfd), 0x01 },
> > +     { CCI_REG8(0x33), 0x03 },
> > +     { CCI_REG8(0x01), 0x02 },
> > +     { CCI_REG8(0xfd), 0x00 },
> > +     { CCI_REG8(0x20), 0x1f },
> > +     { CCI_REG8(0xfd), 0x01 },
> > +};
> > +
> > +static const struct cci_reg_sequence mode_OV05C10_stream_off_regs[] = {

And why are these two register tables now OV05C10 rather than ov05c10
(capital case, while everything else is lower case)


> > +     { CCI_REG8(0xfd), 0x00 },
> > +     { CCI_REG8(0x20), 0x5b },
> > +     { CCI_REG8(0xfd), 0x01 },
> > +     { CCI_REG8(0x33), 0x02 },
> > +     { CCI_REG8(0x01), 0x02 },

Are there any registers in any of the tables we can name to make the
driver more maintainable? Or extract to a directly coded function to
make it more explicit perhaps?

In both those tables 0xfd gets written in a curious manner - is that
another of the page selection type accesses ?

In fact, I can see that it is the page control register indeed - so
perhaps it would make more sense to code this through the page
accessors!



> > +};
> > +
> > +static const char * const ov05c10_test_pattern_menu[] = {
> > +     "Disabled",
> > +     "Vertical Color Bar Type 1",
> > +     "Vertical Color Bar Type 2",
> > +     "Vertical Color Bar Type 3",
> > +     "Vertical Color Bar Type 4"
> > +};
> > +
> > +/* Configurations for supported link frequencies */
> > +#define OV05C10_LINK_FREQ_900MHZ     (900 * HZ_PER_MHZ)
> > +
> > +/* Number of lanes supported */
> > +#define OV05C10_DATA_LANES           2
> > +
> > +/* Bits per sample of sensor output */
> > +#define OV05C10_BITS_PER_SAMPLE              10
> > +
> > +/*
> > + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
> > + * data rate => double data rate; number of lanes => 2; bits per pixel => 10
> > + */
> > +static u64 link_freq_to_pixel_rate(u64 f, u32 lane_nr)
> > +{
> > +     f *= 2 * lane_nr;
> > +     do_div(f, OV05C10_BITS_PER_SAMPLE);
> > +
> > +     return f;
> > +}
> > +
> > +/* Menu items for LINK_FREQ V4L2 control */
> > +static const s64 ov05c10_link_freq_menu_items[] = {
> > +     OV05C10_LINK_FREQ_900MHZ,
> > +};
> > +
> > +/* Mode configs, currently, only support 1 mode */
> > +static const struct ov05c10_mode supported_mode = {
> > +     .width = MODE_WIDTH,
> > +     .height = MODE_HEIGHT,
> > +     .vts_def = OV05C10_VTS_30FPS,
> > +     .vts_min = OV05C10_VTS_30FPS,
> > +     .hts = 640,
> > +     .lanes = 2,
> > +     .reg_list = {
> > +             .num_of_regs = ARRAY_SIZE(ov05c10_2888x1808_regs),
> > +             .regs = ov05c10_2888x1808_regs,
> > +     },
> > +     .link_freq_index = OV05C10_LINK_FREQ_900MHZ_INDEX,
> > +};
> > +
> > +struct ov05c10 {
> > +     struct v4l2_subdev sd;
> > +     struct media_pad pad;
> > +
> > +     /* V4L2 control handler */
> > +     struct v4l2_ctrl_handler ctrl_handler;
> > +
> > +     /* V4L2 Controls */
> > +     struct v4l2_ctrl *link_freq;
> > +     struct v4l2_ctrl *pixel_rate;
> > +     struct v4l2_ctrl *vblank;
> > +     struct v4l2_ctrl *hblank;
> > +     struct v4l2_ctrl *exposure;
> > +
> > +     struct regmap *regmap;
> > +
> > +     /* gpio descriptor */
> > +     struct gpio_desc *enable_gpio;
> > +
> > +     /* Current page for sensor register control */
> > +     int cur_page;
> > +};
> > +
> > +#define to_ov05c10(_sd)      container_of(_sd, struct ov05c10, sd)
> > +
> > +static int ov05c10_init_state(struct v4l2_subdev *sd,
> > +                           struct v4l2_subdev_state *sd_state)
> > +{
> > +     struct v4l2_mbus_framefmt *frame_fmt;
> > +     struct v4l2_subdev_format fmt = {
> > +             .which = V4L2_SUBDEV_FORMAT_TRY,
> > +             .format = {
> > +                     .width = MODE_WIDTH,
> > +                     .height = MODE_HEIGHT,
> > +                     .code = MEDIA_BUS_FMT_SGRBG10_1X10,
> > +                     .field = V4L2_FIELD_NONE,
> > +             }
> > +     };
> > +
> > +     frame_fmt = v4l2_subdev_state_get_format(sd_state, 0);
> > +     *frame_fmt = fmt.format;
> > +     return 0;
> > +}
> > +
> > +static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page, int *err)
> 
> Seems nobody cares the return value of ov05c10_switch_page() or 
> ov05c10_reg_write(), etc.. It should be better to use void return, or 
> use return value instead of int *err.

This style probably comes from the way that the CCI helpers were
written, to allow consectuive writes to avoid having to duplicate error
checks when writing lots of registers.

Depending on how the generic page switching could be implemented, there
may still be benefit to tracking the *err and I would still keep the
return ret type - as there are times where it can still make sense to
write:

 	ret = ov05c10_switch_page(..)
etc...


> 
> > +{
> > +     int ret = 0;
> > +
> > +     if (err && *err)
> > +             return *err;
> > +
> > +     if (page != ov05c10->cur_page) {
> > +             cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, &ret);
> > +             if (!ret)
> > +                     ov05c10->cur_page = page;
> > +     }
> > +
> > +     if (err)
> > +             *err = ret;
> > +
> > +     return ret;
> > +}
> > +
> > +/* refer to the implementation of cci_read */
> > +static int ov05c10_reg_read(struct ov05c10 *ov05c10, u32 reg,
> > +                         u64 *val, int *err)
> > +{
> > +     u32 page;
> > +     u32 addr;
> > +     int ret = 0;
> > +
> > +     if (err && *err)
> > +             return *err;
> > +
> > +     page = OV05C10_GET_PAGE_NUM(reg);
> > +     addr = OV05C10_GET_REG_ADDR(reg);
> > +     ov05c10_switch_page(ov05c10, page, &ret);
> > +     cci_read(ov05c10->regmap, addr, val, &ret);
> > +     if (err)
> > +             *err = ret;
> > +
> > +     return ret;
> > +}
> > +
> > +/* refer to the implementation of cci_write */
> > +static int ov05c10_reg_write(struct ov05c10 *ov05c10, u32 reg,
> > +                          u64 val, int *err)
> > +{
> > +     u32 page;
> > +     u32 addr;
> > +     int ret = 0;
> > +
> > +     if (err && *err)
> > +             return *err;
> > +
> > +     page = OV05C10_GET_PAGE_NUM(reg);
> > +     addr = OV05C10_GET_REG_ADDR(reg);
> > +     ov05c10_switch_page(ov05c10, page, &ret);
> > +     cci_write(ov05c10->regmap, addr, val, &ret);
> > +     if (err)
> > +             *err = ret;
> > +
> > +     return ret;
> > +}

One of the main goals of CCI helpers was to avoid all of the custom
device accessors being duplicated in each driver, so I think extending
the CCI helpers to support page based accesses in some common way would
be beneficial.


> > +
> > +static int ov05c10_update_vblank(struct ov05c10 *ov05c10, u32 vblank)
> > +{
> > +     const struct ov05c10_mode *mode = &supported_mode;
> > +     u64 val;
> > +     int ret = 0;
> > +
> > +     val = mode->height + vblank;
> > +     ov05c10_reg_write(ov05c10, OV05C10_REG_VTS, val, &ret);
> > +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > +                       OV05C_REG_TRIGGER_START, &ret);
> > +
> > +     return ret;
> > +}
> 
> I remembered that the ov05c10 VTS control (P1:0x05~0x06) is a bit weird. 
> This register seems take the increment of VTS value, so direct write of 
> VTS value will not set it properly. Does this version make AE working on 
> your platform?
> 
> > +
> > +static int ov05c10_update_exposure(struct ov05c10 *ov05c10, u32 exposure)
> > +{
> > +     int ret = 0;
> > +
> > +     ov05c10_reg_write(ov05c10, OV05C10_REG_EXPOSURE, exposure, &ret);
> > +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > +                       OV05C_REG_TRIGGER_START, &ret);
> > +
> > +     return ret;
> > +}
> > +
> > +static int ov05c10_update_analog_gain(struct ov05c10 *ov05c10, u32 a_gain)
> > +{
> > +     int ret = 0;
> > +
> > +     ov05c10_reg_write(ov05c10, OV05C10_REG_ANALOG_GAIN, a_gain, &ret);
> > +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > +                       OV05C_REG_TRIGGER_START, &ret);
> > +
> > +     return ret;
> > +}
> > +
> > +static int ov05c10_update_digital_gain(struct ov05c10 *ov05c10, u32 d_gain)
> > +{
> > +     u64 val;
> > +     int ret = 0;
> > +
> > +     val = d_gain & OV05C10_DGTL_GAIN_L_MASK;
> > +     ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_L, val, &ret);
> > +
> > +     val = (d_gain & OV05C10_DGTL_GAIN_H_MASK) >> OV05C10_DGTL_GAIN_H_SHIFT;
> > +     ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_H, val, &ret);
> > +
> > +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > +                       OV05C_REG_TRIGGER_START, &ret);
> > +
> > +     return ret;
> > +}
> > +
> > +static int ov05c10_enable_test_pattern(struct ov05c10 *ov05c10, u32 pattern)
> > +{
> > +     u64 val;
> > +     int ret = 0;
> > +
> > +     if (pattern) {
> > +             ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
> > +                              &val, &ret);
> > +             ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
> > +                               val | OV05C10_REG_TEST_PATTERN_XXX, &ret);
> > +             ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
> > +             val |= OV05C10_TEST_PATTERN_ENABLE;
> > +     } else {
> > +             ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
> > +             val &= ~OV05C10_TEST_PATTERN_ENABLE;
> > +     }
> > +
> > +     ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN, val, &ret);
> > +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > +                       OV05C_REG_TRIGGER_START, &ret);
> > +
> > +     return ret;
> > +}
> > +
> > +static int ov05c10_set_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > +     struct ov05c10 *ov05c10 = container_of(ctrl->handler,
> > +                                            struct ov05c10, ctrl_handler);
> > +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> > +     const struct ov05c10_mode *mode = &supported_mode;
> > +     s64 max;
> > +     int ret = 0;
> > +
> > +     /* Propagate change of current control to all related controls */
> > +     if (ctrl->id == V4L2_CID_VBLANK) {
> > +             s64 cur_exp = ov05c10->exposure->cur.val;
> > +
> > +             /* Update max exposure while meeting expected vblanking */
> > +             max = mode->height + ctrl->val - OV05C10_EXPOSURE_MAX_MARGIN;
> > +             cur_exp = clamp(cur_exp, ov05c10->exposure->minimum, max);
> > +             ret = __v4l2_ctrl_modify_range(ov05c10->exposure,
> > +                                            ov05c10->exposure->minimum,
> > +                                            max, ov05c10->exposure->step,
> > +                                            cur_exp);
> > +             if (!ret)
> > +                     return ret;
> > +     }
> > +
> > +     /*
> > +      * Applying V4L2 control value only happens
> > +      * when power is up for streaming
> > +      */
> > +     if (!pm_runtime_get_if_in_use(&client->dev))
> > +             return 0;
> > +
> > +     switch (ctrl->id) {
> > +     case V4L2_CID_ANALOGUE_GAIN:
> > +             ret = ov05c10_update_analog_gain(ov05c10, ctrl->val);
> > +             break;
> > +     case V4L2_CID_DIGITAL_GAIN:
> > +             ret = ov05c10_update_digital_gain(ov05c10, ctrl->val);
> > +             break;
> > +     case V4L2_CID_EXPOSURE:
> > +             ret = ov05c10_update_exposure(ov05c10, ctrl->val);
> > +             break;
> > +     case V4L2_CID_VBLANK:
> > +             ret = ov05c10_update_vblank(ov05c10, ctrl->val);
> > +             break;
> > +     case V4L2_CID_TEST_PATTERN:
> > +             ret = ov05c10_enable_test_pattern(ov05c10, ctrl->val);
> > +             break;
> > +     default:
> > +             ret = -ENOTTY;
> > +             dev_err(&client->dev,
> > +                     "ctrl(id:0x%x,val:0x%x) is not handled\n",
> > +                     ctrl->id, ctrl->val);
> > +             break;
> > +     }
> > +
> > +     pm_runtime_put(&client->dev);
> > +
> > +     return ret;
> > +}
> > +
> > +static const struct v4l2_ctrl_ops ov05c10_ctrl_ops = {
> > +     .s_ctrl = ov05c10_set_ctrl,
> > +};
> > +
> > +static int ov05c10_enum_mbus_code(struct v4l2_subdev *sd,
> > +                               struct v4l2_subdev_state *sd_state,
> > +                               struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > +     /* Only one bayer order(GRBG) is supported */
> > +     if (code->index > 0)
> > +             return -EINVAL;
> > +
> > +     code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
> > +
> > +     return 0;
> > +}
> > +
> > +static int ov05c10_enum_frame_size(struct v4l2_subdev *sd,
> > +                                struct v4l2_subdev_state *sd_state,
> > +                                struct v4l2_subdev_frame_size_enum *fse)
> > +{
> > +     /* ov05c10 driver currently only supports 1 mode*/
> > +     if (fse->index != 0)
> > +             return -EINVAL;
> > +
> > +     if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
> > +             return -EINVAL;
> > +
> > +     fse->min_width = supported_mode.width;
> > +     fse->max_width = fse->min_width;
> > +     fse->min_height = supported_mode.height;
> > +     fse->max_height = fse->min_height;
> > +
> > +     return 0;
> > +}
> > +
> > +static void ov05c10_update_pad_format(const struct ov05c10_mode *mode,
> > +                                   struct v4l2_subdev_format *fmt)
> > +{
> > +     fmt->format.width = mode->width;
> > +     fmt->format.height = mode->height;
> > +     fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
> > +     fmt->format.field = V4L2_FIELD_NONE;
> > +}
> > +
> > +static int ov05c10_set_pad_format(struct v4l2_subdev *sd,
> > +                               struct v4l2_subdev_state *sd_state,
> > +                               struct v4l2_subdev_format *fmt)
> > +{
> > +     struct v4l2_mbus_framefmt *framefmt;
> > +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > +     const struct ov05c10_mode *mode;
> > +     s32 vblank_def;
> > +     s32 vblank_min;
> > +     s64 pixel_rate;
> > +     s64 link_freq;
> > +     s64 h_blank;
> > +
> > +     /* Only one raw bayer(GRBG) order is supported */
> > +     if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10)
> > +             fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
> > +
> > +     mode = &supported_mode;
> > +     ov05c10_update_pad_format(mode, fmt);
> > +     if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> > +             framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
> > +             *framefmt = fmt->format;
> > +     } else {
> > +             __v4l2_ctrl_s_ctrl(ov05c10->link_freq, mode->link_freq_index);
> > +             link_freq = ov05c10_link_freq_menu_items[mode->link_freq_index];
> > +             pixel_rate = link_freq_to_pixel_rate(link_freq,
> > +                                                  mode->lanes);
> > +             __v4l2_ctrl_s_ctrl_int64(ov05c10->pixel_rate, pixel_rate);
> > +
> > +             /* Update limits and set FPS to default */
> > +             vblank_def = mode->vts_def - mode->height;
> > +             vblank_min = mode->vts_min - mode->height;
> > +             __v4l2_ctrl_modify_range(ov05c10->vblank, vblank_min,
> > +                                      OV05C10_VTS_MAX - mode->height,
> > +                                      1, vblank_def);
> > +             __v4l2_ctrl_s_ctrl(ov05c10->vblank, vblank_def);
> > +             h_blank = mode->hts;
> > +             __v4l2_ctrl_modify_range(ov05c10->hblank, h_blank,
> > +                                      h_blank, 1, h_blank);
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +static int ov05c10_start_streaming(struct ov05c10 *ov05c10)
> > +{
> > +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> > +     const struct ov05c10_mode *mode = &supported_mode;
> > +     const struct ov05c10_reg_list *reg_list;
> > +     int ret = 0;
> > +
> > +     /* Apply default values of current mode */
> > +     reg_list = &mode->reg_list;
> > +     cci_multi_reg_write(ov05c10->regmap, reg_list->regs,
> > +                         reg_list->num_of_regs, &ret);
> > +     if (ret) {
> > +             dev_err(&client->dev, "fail to set mode, ret: %d\n", ret);
> > +             return ret;
> > +     }
> > +
> > +     /* Apply customized values from user */
> > +     ret =  __v4l2_ctrl_handler_setup(ov05c10->sd.ctrl_handler);
> > +     if (ret) {
> > +             dev_err(&client->dev, "failed to setup v4l2 handler %d\n", ret);
> > +             return ret;
> > +     }
> > +
> > +     cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_on_regs,
> > +                         ARRAY_SIZE(mode_OV05C10_stream_on_regs), &ret);
> > +     if (ret)
> > +             dev_err(&client->dev, "fail to start the streaming\n");
> > +
> > +     return ret;
> > +}
> > +
> > +static int ov05c10_stop_streaming(struct ov05c10 *ov05c10)
> > +{
> > +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> > +     int ret = 0;
> > +
> > +     cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_off_regs,
> > +                         ARRAY_SIZE(mode_OV05C10_stream_off_regs), &ret);
> > +     if (ret)
> > +             dev_err(&client->dev, "fail to stop the streaming\n");
> > +
> > +     return ret;
> > +}
> > +
> > +static void ov05c10_sensor_power_set(struct ov05c10 *ov05c10, bool on)
> > +{
> > +     if (on) {
> > +             gpiod_set_value(ov05c10->enable_gpio, 0);
> > +             usleep_range(10, 20);
> > +
> > +             gpiod_set_value(ov05c10->enable_gpio, 1);
> > +             usleep_range(1000, 2000);
> 
> According to the datasheet, ov05c10 needs at least 8 ms to work after 
> its XSHUTDN pin pulled to high. 1 ms maybe too quick, did you tested it? 
> Or the enable_gpio is actually not the XSHUTDN pin?
> 
> On Intel platforms, if the sensor driver controls the module power, 
> ususally it requires GPIO "reset", regulator "avdd" and clk "img_clk" 
> assigned by kernel driver intel_skl_int3472_discrete. I'm not sure 
> whether any devices on market using this power control solution, but if 
> any, missing those resources will stop them from powering-up cameras.
> 
> > +     } else {
> > +             gpiod_set_value(ov05c10->enable_gpio, 0);
> > +             usleep_range(10, 20);
> > +     }
> > +}
> > +
> > +static int ov05c10_enable_streams(struct v4l2_subdev *sd,
> > +                               struct v4l2_subdev_state *state, u32 pad,
> > +                               u64 streams_mask)
> > +{
> > +     struct i2c_client *client = v4l2_get_subdevdata(sd);
> > +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > +     int ret = 0;
> > +
> > +     ret = pm_runtime_resume_and_get(&client->dev);
> > +     if (ret < 0)
> > +             return ret;
> > +
> > +     ov05c10->cur_page = -1;
> > +
> > +     ret = ov05c10_start_streaming(ov05c10);
> > +     if (ret)
> > +             goto err_rpm_put;
> > +
> > +     return 0;
> > +
> > +err_rpm_put:
> > +     pm_runtime_put(&client->dev);
> > +     return ret;
> > +}
> > +
> > +static int ov05c10_disable_streams(struct v4l2_subdev *sd,
> > +                                struct v4l2_subdev_state *state, u32 pad,
> > +                                u64 streams_mask)
> > +{
> > +     struct i2c_client *client = v4l2_get_subdevdata(sd);
> > +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > +
> > +     ov05c10_stop_streaming(ov05c10);
> > +     pm_runtime_put(&client->dev);
> > +
> > +     return 0;
> > +}
> > +
> > +static const struct v4l2_subdev_video_ops ov05c10_video_ops = {
> > +     .s_stream = v4l2_subdev_s_stream_helper,
> > +};
> > +
> > +static const struct v4l2_subdev_pad_ops ov05c10_pad_ops = {
> > +     .enum_mbus_code = ov05c10_enum_mbus_code,
> > +     .get_fmt = v4l2_subdev_get_fmt,
> > +     .set_fmt = ov05c10_set_pad_format,
> > +     .enum_frame_size = ov05c10_enum_frame_size,
> > +     .enable_streams = ov05c10_enable_streams,
> > +     .disable_streams = ov05c10_disable_streams,
> > +};
> > +
> > +static const struct v4l2_subdev_ops ov05c10_subdev_ops = {
> > +     .video = &ov05c10_video_ops,
> > +     .pad = &ov05c10_pad_ops,
> > +};
> > +
> > +static const struct media_entity_operations ov05c10_subdev_entity_ops = {
> > +     .link_validate = v4l2_subdev_link_validate,
> > +};
> > +
> > +static const struct v4l2_subdev_internal_ops ov05c10_internal_ops = {
> > +     .init_state = ov05c10_init_state,
> > +};
> > +
> > +static int ov05c10_init_controls(struct ov05c10 *ov05c10)
> > +{
> > +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> > +     const struct ov05c10_mode *mode = &supported_mode;
> > +     struct v4l2_fwnode_device_properties props;
> > +     struct v4l2_ctrl_handler *ctrl_hdlr;
> > +     s64 pixel_rate_max;
> > +     s64 exposure_max;
> > +     s64 vblank_def;
> > +     s64 vblank_min;
> > +     u32 max_items;
> > +     s64 hblank;
> > +     int ret;
> > +
> > +     ret = v4l2_ctrl_handler_init(&ov05c10->ctrl_handler, 10);
> > +     if (ret)
> > +             return ret;
> > +
> > +     ctrl_hdlr = &ov05c10->ctrl_handler;
> > +
> > +     max_items = ARRAY_SIZE(ov05c10_link_freq_menu_items) - 1;
> > +     ov05c10->link_freq =
> > +             v4l2_ctrl_new_int_menu(ctrl_hdlr,
> > +                                    NULL,
> > +                                    V4L2_CID_LINK_FREQ,
> > +                                    max_items,
> > +                                    0,
> > +                                    ov05c10_link_freq_menu_items);
> > +     if (ov05c10->link_freq)
> > +             ov05c10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > +
> > +     pixel_rate_max =
> > +             link_freq_to_pixel_rate(ov05c10_link_freq_menu_items[0],
> > +                                     supported_mode.lanes);
> > +     ov05c10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
> > +                                             V4L2_CID_PIXEL_RATE,
> > +                                             0, pixel_rate_max,
> > +                                             1, pixel_rate_max);
> > +
> > +     vblank_def = mode->vts_def - mode->height;
> > +     vblank_min = mode->vts_min - mode->height;
> > +     ov05c10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
> > +                                         V4L2_CID_VBLANK,
> > +                                         vblank_min,
> > +                                         OV05C10_VTS_MAX - mode->height,
> > +                                         1, vblank_def);
> > +
> > +     hblank = (mode->hts > mode->width) ? (mode->hts - mode->width) : 0;
> 
> Here your hts uses 640 but width is 2888, which means hblank is set to 0 
> here. This is wrong, please fix your configuration.
> 
> > +     ov05c10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
> > +                                         V4L2_CID_HBLANK,
> > +                                         hblank, hblank, 1, hblank);
> > +     if (ov05c10->hblank)
> > +             ov05c10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > +
> > +     exposure_max = mode->vts_def - OV05C10_EXPOSURE_MAX_MARGIN;
> > +     ov05c10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
> > +                                           V4L2_CID_EXPOSURE,
> > +                                           OV05C10_EXPOSURE_MIN,
> > +                                           exposure_max,
> > +                                           OV05C10_EXPOSURE_STEP,
> > +                                           exposure_max);
> > +
> > +     v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
> > +                       OV05C10_ANA_GAIN_MIN, OV05C10_ANA_GAIN_MAX,
> > +                       OV05C10_ANA_GAIN_STEP, OV05C10_ANA_GAIN_DEFAULT);
> > +
> > +     v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
> > +                       OV05C10_DGTL_GAIN_MIN, OV05C10_DGTL_GAIN_MAX,
> > +                       OV05C10_DGTL_GAIN_STEP, OV05C10_DGTL_GAIN_DEFAULT);
> > +
> > +     v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov05c10_ctrl_ops,
> > +                                  V4L2_CID_TEST_PATTERN,
> > +                                  ARRAY_SIZE(ov05c10_test_pattern_menu) - 1,
> > +                                  0, 0, ov05c10_test_pattern_menu);
> > +
> > +     if (ctrl_hdlr->error) {
> > +             ret = ctrl_hdlr->error;
> > +             dev_err(&client->dev, "V4L2 control init failed (%d)\n", ret);
> > +             goto err_hdl_free;
> > +     }
> > +
> > +     ret = v4l2_fwnode_device_parse(&client->dev, &props);
> > +     if (ret)
> > +             goto err_hdl_free;
> > +
> > +     ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov05c10_ctrl_ops,
> > +                                           &props);
> > +     if (ret)
> > +             goto err_hdl_free;
> > +
> > +     ov05c10->sd.ctrl_handler = ctrl_hdlr;
> > +
> > +     return 0;
> > +
> > +err_hdl_free:
> > +     v4l2_ctrl_handler_free(ctrl_hdlr);
> > +
> > +     return ret;
> > +}
> > +
> > +static int ov05c10_parse_endpoint(struct device *dev,
> > +                               struct fwnode_handle *fwnode)
> > +{
> > +     struct v4l2_fwnode_endpoint bus_cfg = {
> > +             .bus_type = V4L2_MBUS_CSI2_DPHY
> > +     };
> > +     struct fwnode_handle *ep;
> > +     unsigned long bitmap;
> > +     int ret;
> > +
> > +     ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
> > +     if (!ep) {
> > +             dev_err(dev, "Failed to get next endpoint\n");
> > +             return -ENXIO;
> > +     }
> > +
> > +     ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
> > +     fwnode_handle_put(ep);
> > +     if (ret)
> > +             return ret;
> > +
> > +     if (bus_cfg.bus.mipi_csi2.num_data_lanes != supported_mode.lanes) {
> > +             dev_err(dev,
> > +                     "number of CSI2 data lanes %d is not supported\n",
> > +                     bus_cfg.bus.mipi_csi2.num_data_lanes);
> > +             ret = -EINVAL;
> > +             goto err_endpoint_free;
> > +     }
> > +
> > +     ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
> > +                                    bus_cfg.nr_of_link_frequencies,
> > +                                    ov05c10_link_frequencies,
> > +                                    ARRAY_SIZE(ov05c10_link_frequencies),
> > +                                    &bitmap);
> > +     if (ret)
> > +             dev_err(dev, "v4l2_link_freq_to_bitmap fail with %d\n", ret);
> > +err_endpoint_free:
> > +     v4l2_fwnode_endpoint_free(&bus_cfg);
> > +
> > +     return ret;
> > +}
> > +
> > +static int ov05c10_probe(struct i2c_client *client)
> > +{
> > +     struct ov05c10 *ov05c10;
> > +     u32 clkfreq;
> > +     int ret;
> > +
> > +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), GFP_KERNEL);
> > +     if (!ov05c10)
> > +             return -ENOMEM;
> > +
> > +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> > +
> > +     ret = fwnode_property_read_u32(fwnode, "clock-frequency", &clkfreq);
> 
> Maybe it's better to separate this part fwnode and GPIO code into a 
> standalone function?
> 
> > +     if (ret)
> > +             return  dev_err_probe(&client->dev, -EINVAL,
> > +                                   "fail to get clock freq\n");
> > +     if (clkfreq != OV05C10_REF_CLK)
> > +             return dev_err_probe(&client->dev, -EINVAL,
> > +                                  "fail invalid clock freq %u, %lu expected\n",
> > +                                  clkfreq, OV05C10_REF_CLK);
> > +
> > +     ret = ov05c10_parse_endpoint(&client->dev, fwnode);
> > +     if (ret)
> > +             return dev_err_probe(&client->dev, -EINVAL,
> > +                                  "fail to parse endpoint\n");
> > +
> > +     ov05c10->enable_gpio = devm_gpiod_get(&client->dev, "enable",
> > +                                           GPIOD_OUT_LOW);
> > +     if (IS_ERR(ov05c10->enable_gpio))
> > +             return dev_err_probe(&client->dev,
> > +                                  PTR_ERR(ov05c10->enable_gpio),
> > +                                  "fail to get enable gpio\n");
> > +
> > +     v4l2_i2c_subdev_init(&ov05c10->sd, client, &ov05c10_subdev_ops);
> > +
> > +     ov05c10->regmap = devm_cci_regmap_init_i2c(client, 8);
> > +     if (IS_ERR(ov05c10->regmap))
> > +             return dev_err_probe(&client->dev, PTR_ERR(ov05c10->regmap),
> > +                                  "fail to init cci\n");
> > +
> > +     ov05c10->cur_page = -1;
> > +
> > +     ret = ov05c10_init_controls(ov05c10);
> > +     if (ret)
> > +             return dev_err_probe(&client->dev, ret, "fail to init ctl\n");
> > +
> > +     ov05c10->sd.internal_ops = &ov05c10_internal_ops;
> > +     ov05c10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > +     ov05c10->sd.entity.ops = &ov05c10_subdev_entity_ops;
> > +     ov05c10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > +
> > +     ov05c10->pad.flags = MEDIA_PAD_FL_SOURCE;
> > +
> > +     ret = media_entity_pads_init(&ov05c10->sd.entity, NUM_OF_PADS,
> > +                                  &ov05c10->pad);
> > +     if (ret)
> > +             goto err_hdl_free;
> > +
> > +     ret = v4l2_subdev_init_finalize(&ov05c10->sd);
> > +     if (ret < 0)
> > +             goto err_media_entity_cleanup;
> > +
> > +     ret = v4l2_async_register_subdev_sensor(&ov05c10->sd);
> > +     if (ret)
> > +             goto err_media_entity_cleanup;
> > +
> > +     pm_runtime_set_active(&client->dev);
> > +     pm_runtime_enable(&client->dev);
> > +     pm_runtime_idle(&client->dev);
> > +     pm_runtime_set_autosuspend_delay(&client->dev, 1000);
> > +     pm_runtime_use_autosuspend(&client->dev);
> > +     return 0;
> > +
> > +err_media_entity_cleanup:
> > +     media_entity_cleanup(&ov05c10->sd.entity);
> > +
> > +err_hdl_free:
> > +     v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
> > +
> > +     return ret;
> > +}
> > +
> > +static void ov05c10_remove(struct i2c_client *client)
> > +{
> > +     struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > +
> > +     v4l2_async_unregister_subdev(sd);
> > +     media_entity_cleanup(&sd->entity);
> > +     v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
> > +
> > +     pm_runtime_disable(&client->dev);
> > +     pm_runtime_set_suspended(&client->dev);
> > +}
> > +
> > +static int ov05c10_runtime_resume(struct device *dev)
> > +{
> > +     struct v4l2_subdev *sd = dev_get_drvdata(dev);
> > +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > +
> > +     ov05c10_sensor_power_set(ov05c10, true);
> > +     return 0;
> > +}
> > +
> > +static int ov05c10_runtime_suspend(struct device *dev)
> > +{
> > +     struct v4l2_subdev *sd = dev_get_drvdata(dev);
> > +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > +
> > +     ov05c10_sensor_power_set(ov05c10, false);
> > +     return 0;
> > +}
> > +
> > +static DEFINE_RUNTIME_DEV_PM_OPS(ov05c10_pm_ops, ov05c10_runtime_suspend,
> > +                              ov05c10_runtime_resume, NULL);
> > +
> > +static const struct i2c_device_id ov05c10_i2c_ids[] = {
> > +     {"ov05c10", 0 },
> > +     { }
> > +};
> > +MODULE_DEVICE_TABLE(i2c, ov05c10_i2c_ids);
> > +
> > +static struct i2c_driver ov05c10_i2c_driver = {
> > +     .driver = {
> > +             .name = DRV_NAME,
> > +             .pm = pm_ptr(&ov05c10_pm_ops),
> > +     },
> > +     .id_table = ov05c10_i2c_ids,
> > +     .probe = ov05c10_probe,
> > +     .remove = ov05c10_remove,
> > +};
> > +
> > +module_i2c_driver(ov05c10_i2c_driver);
> > +
> > +MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
> > +MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@amd.com>");
> > +MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
> > +MODULE_DESCRIPTION("OmniVision OV05C1010 sensor driver");
> 
> OV05C10
> 
> > +MODULE_LICENSE("GPL");
> 
> 
> Hi Sakari,
> 
> Seems there are already several camera sensors using page-based 
> registers. Is it a good idea to add page support in CCI interface?

I think that's a good idea!

Some how maintaining the generality to keep it easy to access registers
would be nice. Perhaps the page could be embedded in the CCI_REG()
macros too to support changing the active page when a write accesses a
different one?

--
Kieran


> 
> 
> Best Regards,
> Hao Yao
>

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-13 11:02   ` Kieran Bingham
@ 2025-06-13 12:05     ` Bryan O'Donoghue
  2025-06-16 23:15       ` Nirujogi, Pratap
  2025-06-16 23:46     ` Nirujogi, Pratap
  1 sibling, 1 reply; 60+ messages in thread
From: Bryan O'Donoghue @ 2025-06-13 12:05 UTC (permalink / raw)
  To: Kieran Bingham, Hao Yao, Pratap Nirujogi, sakari.ailus
  Cc: mchehab, laurent.pinchart, hverkuil, krzk, dave.stevenson,
	hdegoede, jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	dongcheng.yan, jason.z.chen, jimmy.su

On 13/06/2025 12:02, Kieran Bingham wrote:
> Quoting Hao Yao (2025-06-13 05:55:46)
>> Hi Pratap,
>>
>> Thanks for your patch.
>>
>> This patch is written for your camera sensor module, which seems very
>> different from those already applied on Dell laptops (some of "Dell Pro"
>> series). Looking into the driver, I think this version will break the
> Have there been existing efforts from Intel to upstream support for that
> device?

FWIW +1

Qualcomm devices - Acer Swift 14 AI, HP OmniBook x14 both use this sensor.

I'd expect though that aside from OF bindings, regulators and clocks 
that any upstream configuration with the right number of lanes would 
"just work", including this one from AMD.

That has been the experience picking up OV02E10 and OV02C10 from the 
IPU6 repository where its ACPI binding and repurposing to OF/Qcom.

So how incompatible could OV05C10 be between different x86/ACPI systems 
? Less than the gap between x86/ACPI and Arm/OF you'd imagine.

Getting any OV05C10 driver upstream would be great, we can work from 
there to bridge whatever gap needs be for !AMD.

---
bod

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-13  4:55 ` Hao Yao
  2025-06-13 11:02   ` Kieran Bingham
@ 2025-06-13 22:02   ` Sakari Ailus
  2025-06-14 22:52     ` Laurent Pinchart
  2025-06-16 23:12     ` Nirujogi, Pratap
  2025-06-17  0:19   ` Nirujogi, Pratap
  2 siblings, 2 replies; 60+ messages in thread
From: Sakari Ailus @ 2025-06-13 22:02 UTC (permalink / raw)
  To: Hao Yao
  Cc: Pratap Nirujogi, mchehab, laurent.pinchart, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su

Hi Hao Yao,

On Fri, Jun 13, 2025 at 12:55:46PM +0800, Hao Yao wrote:
> Hi Pratap,
> 
> Thanks for your patch.
> 
> This patch is written for your camera sensor module, which seems very
> different from those already applied on Dell laptops (some of "Dell Pro"
> series). Looking into the driver, I think this version will break the
> devices using ov05c10 sensor.

There never was such a driver in upstream so nothing breaks. However, in
order to support these, could you check what would it take to support them
using this driver and post patches, please?

> 
> I think this patch is better to be validated on existing devices, but please
> do some fixes before we can do validation. Please check my comments inline.
> 
> 
> On 2025/6/10 03:42, Pratap Nirujogi wrote:
> > Add driver for OmniVision 5.2M OV05C10 sensor. This driver
> > supports only the full size normal 2888x1808@30fps 2-lane
> > sensor profile.
> > 
> > Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> > Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> > Co-developed-by: Bin Du <bin.du@amd.com>
> > Signed-off-by: Bin Du <bin.du@amd.com>
> > Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
> > ---
> > Changes v2 -> v3:
> > 
> > * Update "refclk" property variable as "clock-frequency".
> > * Update sensor GPIO connector id name.
> > * Fix sensor v4l2 compliance issue.
> > * Fix license info.
> > * Address review comments.
> > 
> >   MAINTAINERS                 |    8 +
> >   drivers/media/i2c/Kconfig   |   10 +
> >   drivers/media/i2c/Makefile  |    1 +
> >   drivers/media/i2c/ov05c10.c | 1061 +++++++++++++++++++++++++++++++++++
> >   4 files changed, 1080 insertions(+)
> >   create mode 100644 drivers/media/i2c/ov05c10.c
> > 
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index a92290fffa16..caca25d00bf2 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -18303,6 +18303,14 @@ T:	git git://linuxtv.org/media.git
> >   F:	Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml
> >   F:	drivers/media/i2c/ov02e10.c
> > +OMNIVISION OV05C10 SENSOR DRIVER
> > +M:	Nirujogi Pratap <pratap.nirujogi@amd.com>
> > +M:	Bin Du <bin.du@amd.com>
> > +L:	linux-media@vger.kernel.org
> > +S:	Maintained
> > +T:	git git://linuxtv.org/media.git
> > +F:	drivers/media/i2c/ov05c10.c
> > +
> >   OMNIVISION OV08D10 SENSOR DRIVER
> >   M:	Jimmy Su <jimmy.su@intel.com>
> >   L:	linux-media@vger.kernel.org
> > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > index e68202954a8f..1662fb29d75c 100644
> > --- a/drivers/media/i2c/Kconfig
> > +++ b/drivers/media/i2c/Kconfig
> > @@ -377,6 +377,16 @@ config VIDEO_OV02C10
> >   	  To compile this driver as a module, choose M here: the
> >   	  module will be called ov02c10.
> > +config VIDEO_OV05C10
> > +	tristate "OmniVision OV05C10 sensor support"
> > +	select V4L2_CCI_I2C
> > +	help
> > +	  This is a Video4Linux2 sensor driver for the OmniVision
> > +	  OV05C10 camera.
> > +
> > +	  To compile this driver as a module, choose M here: the
> > +	  module will be called OV05C10.
> > +
> >   config VIDEO_OV08D10
> >           tristate "OmniVision OV08D10 sensor support"
> >           help
> > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > index 5873d29433ee..b4a1d721a7f2 100644
> > --- a/drivers/media/i2c/Makefile
> > +++ b/drivers/media/i2c/Makefile
> > @@ -85,6 +85,7 @@ obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
> >   obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
> >   obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o
> >   obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o
> > +obj-$(CONFIG_VIDEO_OV05C10) += ov05c10.o
> >   obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
> >   obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
> >   obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
> > diff --git a/drivers/media/i2c/ov05c10.c b/drivers/media/i2c/ov05c10.c
> > new file mode 100644
> > index 000000000000..9a1e493c4073
> > --- /dev/null
> > +++ b/drivers/media/i2c/ov05c10.c
> > @@ -0,0 +1,1061 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +// Copyright (C) 2025 Advanced Micro Devices, Inc.
> > +
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/gpio.h>
> > +#include <linux/i2c.h>
> > +#include <linux/module.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/units.h>
> > +#include <media/v4l2-cci.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-fwnode.h>
> > +
> > +#define DRV_NAME			"ov05c10"
> > +#define OV05C10_REF_CLK			(24 * HZ_PER_MHZ)
> 
> Seems your module use 24 MHz clock input. The Dell's modules always use
> 19.2MHz, which means your the PLL settings will not work on Dell's.

This is ok as further work. Please send a patch. :-)

> 
> > +
> > +#define MODE_WIDTH  2888
> > +#define MODE_HEIGHT 1808
> > +
> > +#define PAGE_NUM_MASK			0xff000000
> > +#define PAGE_NUM_SHIFT			24
> > +#define REG_ADDR_MASK			0x00ffffff
> > +
> > +#define OV05C10_SYSCTL_PAGE		(0 << PAGE_NUM_SHIFT)
> > +#define OV05C10_CISCTL_PAGE		(1 << PAGE_NUM_SHIFT)
> > +#define OV05C10_ISPCTL_PAGE		(4 << PAGE_NUM_SHIFT)
> > +
> > +/* Chip ID */
> > +#define OV05C10_REG_CHIP_ID		(CCI_REG24(0x00) | OV05C10_SYSCTL_PAGE)
> > +#define OV05C10_CHIP_ID			0x43055610
> > +
> > +/* Control registers */
> > +#define OV05C10_REG_TRIGGER		(CCI_REG8(0x01) | OV05C10_CISCTL_PAGE)
> > +#define OV05C_REG_TRIGGER_START		BIT(0)
> > +
> > +/* Exposure control */
> > +#define OV05C10_REG_EXPOSURE		(CCI_REG24(0x02) | OV05C10_CISCTL_PAGE)
> > +#define OV05C10_EXPOSURE_MAX_MARGIN	33
> > +#define OV05C10_EXPOSURE_MIN		4
> > +#define OV05C10_EXPOSURE_STEP		1
> > +#define OV05C10_EXPOSURE_DEFAULT	0x40
> > +
> > +/* V_TIMING internal */
> > +#define OV05C10_REG_VTS			(CCI_REG16(0x05) | OV05C10_CISCTL_PAGE)
> > +#define OV05C10_VTS_30FPS		1860
> > +#define OV05C10_VTS_MAX			0x7fff
> > +
> > +/* Test Pattern Control */
> > +#define OV05C10_REG_TEST_PATTERN	(CCI_REG8(0x12) | OV05C10_ISPCTL_PAGE)
> > +#define OV05C10_TEST_PATTERN_ENABLE	BIT(0)
> > +#define OV05C10_REG_TEST_PATTERN_CTL	(CCI_REG8(0xf3) | OV05C10_ISPCTL_PAGE)
> > +#define OV05C10_REG_TEST_PATTERN_XXX	BIT(0)
> > +
> > +/* Digital gain control */
> > +#define OV05C10_REG_DGTL_GAIN_H		(CCI_REG8(0x21) | OV05C10_CISCTL_PAGE)
> > +#define OV05C10_REG_DGTL_GAIN_L		(CCI_REG8(0x22) | OV05C10_CISCTL_PAGE)
> > +
> > +#define OV05C10_DGTL_GAIN_MIN		0x40
> > +#define OV05C10_DGTL_GAIN_MAX		0xff
> > +#define OV05C10_DGTL_GAIN_DEFAULT	0x40
> > +#define OV05C10_DGTL_GAIN_STEP		1
> > +
> > +#define OV05C10_DGTL_GAIN_L_MASK	0xff
> > +#define OV05C10_DGTL_GAIN_H_SHIFT	8
> > +#define OV05C10_DGTL_GAIN_H_MASK	0xff00
> > +
> > +/* Analog gain control */
> > +#define OV05C10_REG_ANALOG_GAIN		(CCI_REG8(0x24) | OV05C10_CISCTL_PAGE)
> > +#define OV05C10_ANA_GAIN_MIN		0x80
> > +#define OV05C10_ANA_GAIN_MAX		0x07c0
> > +#define OV05C10_ANA_GAIN_STEP		1
> > +#define OV05C10_ANA_GAIN_DEFAULT	0x80
> > +
> > +/* H TIMING internal */
> > +#define OV05C10_REG_HTS			(CCI_REG16(0x37) | OV05C10_CISCTL_PAGE)
> > +#define OV05C10_HTS_30FPS		0x0280
> > +
> > +/* Page selection */
> > +#define OV05C10_REG_PAGE_CTL		CCI_REG8(0xfd)
> > +
> > +#define NUM_OF_PADS 1
> > +
> > +#define OV05C10_GET_PAGE_NUM(reg)	(((reg) & PAGE_NUM_MASK) >>\
> > +					 PAGE_NUM_SHIFT)
> > +#define OV05C10_GET_REG_ADDR(reg)	((reg) & REG_ADDR_MASK)
> > +
> > +enum {
> > +	OV05C10_LINK_FREQ_900MHZ_INDEX,
> > +};
> > +
> > +struct ov05c10_reg_list {
> > +	u32 num_of_regs;
> > +	const struct cci_reg_sequence *regs;
> > +};
> > +
> > +/* Mode : resolution and related config&values */
> > +struct ov05c10_mode {
> > +	/* Frame width */
> > +	u32 width;
> > +	/* Frame height */
> > +	u32 height;
> > +	/* number of lanes */
> > +	u32 lanes;
> > +
> > +	/* V-timing */
> > +	u32 vts_def;
> > +	u32 vts_min;
> > +
> > +	/* HTS */
> > +	u32 hts;
> > +
> > +	/* Index of Link frequency config to be used */
> > +	u32 link_freq_index;
> > +
> > +	/* Default register values */
> > +	struct ov05c10_reg_list reg_list;
> > +};
> > +
> > +static const s64 ov05c10_link_frequencies[] = {
> > +	925 * HZ_PER_MHZ,
> > +};
> 
> Is it 900 MHz, or 925 MHz?
> 
> > +
> > +/* 2888x1808 30fps, 1800mbps, 2lane, 24mhz */
> 
> Currently Dell's devices with ov05c10 use a CV chip to passthrough MIPI CSI
> signals, but it supports max 750 MHz link frequency. That's why this
> version:
> https://github.com/intel/ipu6-drivers/blob/master/drivers/media/i2c/ov05c10.c
> uses 480 MHz link frequency and a different resolution setting (2800x1576).
> At least the setting in out-of-tree Github driver should be merged into this
> version.

Ditto.

> 
> > +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
> > +	{ CCI_REG8(0xfd),  0x00 },
> > +	{ CCI_REG8(0x20),  0x00 },
> > +	{ CCI_REG8(0xfd),  0x00 },
> > +	{ CCI_REG8(0x20),  0x0b },
> > +	{ CCI_REG8(0xc1),  0x09 },
> > +	{ CCI_REG8(0x21),  0x06 },
> > +	{ CCI_REG8(0x14),  0x78 },
> > +	{ CCI_REG8(0xe7),  0x03 },
> > +	{ CCI_REG8(0xe7),  0x00 },
> > +	{ CCI_REG8(0x21),  0x00 },
> > +	{ CCI_REG8(0xfd),  0x01 },
> > +	{ CCI_REG8(0x03),  0x00 },
> > +	{ CCI_REG8(0x04),  0x06 },
> > +	{ CCI_REG8(0x05),  0x07 },
> > +	{ CCI_REG8(0x06),  0x44 },
> > +	{ CCI_REG8(0x07),  0x08 },
> > +	{ CCI_REG8(0x1b),  0x01 },
> > +	{ CCI_REG8(0x24),  0xff },
> > +	{ CCI_REG8(0x32),  0x03 },
> > +	{ CCI_REG8(0x42),  0x5d },
> > +	{ CCI_REG8(0x43),  0x08 },
> > +	{ CCI_REG8(0x44),  0x81 },
> > +	{ CCI_REG8(0x46),  0x5f },
> > +	{ CCI_REG8(0x48),  0x18 },
> > +	{ CCI_REG8(0x49),  0x04 },
> > +	{ CCI_REG8(0x5c),  0x18 },
> > +	{ CCI_REG8(0x5e),  0x13 },
> > +	{ CCI_REG8(0x70),  0x15 },
> > +	{ CCI_REG8(0x77),  0x35 },
> > +	{ CCI_REG8(0x79),  0x00 },
> > +	{ CCI_REG8(0x7b),  0x08 },
> > +	{ CCI_REG8(0x7d),  0x08 },
> > +	{ CCI_REG8(0x7e),  0x08 },
> > +	{ CCI_REG8(0x7f),  0x08 },
> > +	{ CCI_REG8(0x90),  0x37 },
> > +	{ CCI_REG8(0x91),  0x05 },
> > +	{ CCI_REG8(0x92),  0x18 },
> > +	{ CCI_REG8(0x93),  0x27 },
> > +	{ CCI_REG8(0x94),  0x05 },
> > +	{ CCI_REG8(0x95),  0x38 },
> > +	{ CCI_REG8(0x9b),  0x00 },
> > +	{ CCI_REG8(0x9c),  0x06 },
> > +	{ CCI_REG8(0x9d),  0x28 },
> > +	{ CCI_REG8(0x9e),  0x06 },
> > +	{ CCI_REG8(0xb2),  0x0f },
> > +	{ CCI_REG8(0xb3),  0x29 },
> > +	{ CCI_REG8(0xbf),  0x3c },
> > +	{ CCI_REG8(0xc2),  0x04 },
> > +	{ CCI_REG8(0xc4),  0x00 },
> > +	{ CCI_REG8(0xca),  0x20 },
> > +	{ CCI_REG8(0xcb),  0x20 },
> > +	{ CCI_REG8(0xcc),  0x28 },
> > +	{ CCI_REG8(0xcd),  0x28 },
> > +	{ CCI_REG8(0xce),  0x20 },
> > +	{ CCI_REG8(0xcf),  0x20 },
> > +	{ CCI_REG8(0xd0),  0x2a },
> > +	{ CCI_REG8(0xd1),  0x2a },
> > +	{ CCI_REG8(0xfd),  0x0f },
> > +	{ CCI_REG8(0x00),  0x00 },
> > +	{ CCI_REG8(0x01),  0xa0 },
> > +	{ CCI_REG8(0x02),  0x48 },
> > +	{ CCI_REG8(0x07),  0x8f },
> > +	{ CCI_REG8(0x08),  0x70 },
> > +	{ CCI_REG8(0x09),  0x01 },
> > +	{ CCI_REG8(0x0b),  0x40 },
> > +	{ CCI_REG8(0x0d),  0x07 },
> > +	{ CCI_REG8(0x11),  0x33 },
> > +	{ CCI_REG8(0x12),  0x77 },
> > +	{ CCI_REG8(0x13),  0x66 },
> > +	{ CCI_REG8(0x14),  0x65 },
> > +	{ CCI_REG8(0x15),  0x37 },
> > +	{ CCI_REG8(0x16),  0xbf },
> > +	{ CCI_REG8(0x17),  0xff },
> > +	{ CCI_REG8(0x18),  0xff },
> > +	{ CCI_REG8(0x19),  0x12 },
> > +	{ CCI_REG8(0x1a),  0x10 },
> > +	{ CCI_REG8(0x1c),  0x77 },
> > +	{ CCI_REG8(0x1d),  0x77 },
> > +	{ CCI_REG8(0x20),  0x0f },
> > +	{ CCI_REG8(0x21),  0x0f },
> > +	{ CCI_REG8(0x22),  0x0f },
> > +	{ CCI_REG8(0x23),  0x0f },
> > +	{ CCI_REG8(0x2b),  0x20 },
> > +	{ CCI_REG8(0x2c),  0x20 },
> > +	{ CCI_REG8(0x2d),  0x04 },
> > +	{ CCI_REG8(0xfd),  0x03 },
> > +	{ CCI_REG8(0x9d),  0x0f },
> > +	{ CCI_REG8(0x9f),  0x40 },
> > +	{ CCI_REG8(0xfd),  0x00 },
> > +	{ CCI_REG8(0x20),  0x1b },
> > +	{ CCI_REG8(0xfd),  0x04 },
> > +	{ CCI_REG8(0x19),  0x60 },
> > +	{ CCI_REG8(0xfd),  0x02 },
> > +	{ CCI_REG8(0x75),  0x05 },
> > +	{ CCI_REG8(0x7f),  0x06 },
> > +	{ CCI_REG8(0x9a),  0x03 },
> > +	{ CCI_REG8(0xa2),  0x07 },
> > +	{ CCI_REG8(0xa3),  0x10 },
> > +	{ CCI_REG8(0xa5),  0x02 },
> > +	{ CCI_REG8(0xa6),  0x0b },
> > +	{ CCI_REG8(0xa7),  0x48 },
> > +	{ CCI_REG8(0xfd),  0x07 },
> > +	{ CCI_REG8(0x42),  0x00 },
> > +	{ CCI_REG8(0x43),  0x80 },
> > +	{ CCI_REG8(0x44),  0x00 },
> > +	{ CCI_REG8(0x45),  0x80 },
> > +	{ CCI_REG8(0x46),  0x00 },
> > +	{ CCI_REG8(0x47),  0x80 },
> > +	{ CCI_REG8(0x48),  0x00 },
> > +	{ CCI_REG8(0x49),  0x80 },
> > +	{ CCI_REG8(0x00),  0xf7 },
> > +	{ CCI_REG8(0xfd),  0x00 },
> > +	{ CCI_REG8(0xe7),  0x03 },
> > +	{ CCI_REG8(0xe7),  0x00 },
> > +	{ CCI_REG8(0xfd),  0x00 },
> > +	{ CCI_REG8(0x93),  0x18 },
> > +	{ CCI_REG8(0x94),  0xff },
> > +	{ CCI_REG8(0x95),  0xbd },
> > +	{ CCI_REG8(0x96),  0x1a },
> > +	{ CCI_REG8(0x98),  0x04 },
> > +	{ CCI_REG8(0x99),  0x08 },
> > +	{ CCI_REG8(0x9b),  0x10 },
> > +	{ CCI_REG8(0x9c),  0x3f },
> > +	{ CCI_REG8(0xa1),  0x05 },
> > +	{ CCI_REG8(0xa4),  0x2f },
> > +	{ CCI_REG8(0xc0),  0x0c },
> > +	{ CCI_REG8(0xc1),  0x08 },
> > +	{ CCI_REG8(0xc2),  0x00 },
> > +	{ CCI_REG8(0xb6),  0x20 },
> > +	{ CCI_REG8(0xbb),  0x80 },
> > +	{ CCI_REG8(0xfd),  0x00 },
> > +	{ CCI_REG8(0xa0),  0x01 },
> > +	{ CCI_REG8(0xfd),  0x01 },
> > +};
> > +
> > +static const struct cci_reg_sequence mode_OV05C10_stream_on_regs[] = {
> > +	{ CCI_REG8(0xfd), 0x01 },
> > +	{ CCI_REG8(0x33), 0x03 },
> > +	{ CCI_REG8(0x01), 0x02 },
> > +	{ CCI_REG8(0xfd), 0x00 },
> > +	{ CCI_REG8(0x20), 0x1f },
> > +	{ CCI_REG8(0xfd), 0x01 },
> > +};
> > +
> > +static const struct cci_reg_sequence mode_OV05C10_stream_off_regs[] = {
> > +	{ CCI_REG8(0xfd), 0x00 },
> > +	{ CCI_REG8(0x20), 0x5b },
> > +	{ CCI_REG8(0xfd), 0x01 },
> > +	{ CCI_REG8(0x33), 0x02 },
> > +	{ CCI_REG8(0x01), 0x02 },
> > +};
> > +
> > +static const char * const ov05c10_test_pattern_menu[] = {
> > +	"Disabled",
> > +	"Vertical Color Bar Type 1",
> > +	"Vertical Color Bar Type 2",
> > +	"Vertical Color Bar Type 3",
> > +	"Vertical Color Bar Type 4"
> > +};
> > +
> > +/* Configurations for supported link frequencies */
> > +#define OV05C10_LINK_FREQ_900MHZ	(900 * HZ_PER_MHZ)
> > +
> > +/* Number of lanes supported */
> > +#define OV05C10_DATA_LANES		2
> > +
> > +/* Bits per sample of sensor output */
> > +#define OV05C10_BITS_PER_SAMPLE		10
> > +
> > +/*
> > + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
> > + * data rate => double data rate; number of lanes => 2; bits per pixel => 10
> > + */
> > +static u64 link_freq_to_pixel_rate(u64 f, u32 lane_nr)
> > +{
> > +	f *= 2 * lane_nr;
> > +	do_div(f, OV05C10_BITS_PER_SAMPLE);
> > +
> > +	return f;
> > +}
> > +
> > +/* Menu items for LINK_FREQ V4L2 control */
> > +static const s64 ov05c10_link_freq_menu_items[] = {
> > +	OV05C10_LINK_FREQ_900MHZ,
> > +};
> > +
> > +/* Mode configs, currently, only support 1 mode */
> > +static const struct ov05c10_mode supported_mode = {
> > +	.width = MODE_WIDTH,
> > +	.height = MODE_HEIGHT,
> > +	.vts_def = OV05C10_VTS_30FPS,
> > +	.vts_min = OV05C10_VTS_30FPS,
> > +	.hts = 640,
> > +	.lanes = 2,
> > +	.reg_list = {
> > +		.num_of_regs = ARRAY_SIZE(ov05c10_2888x1808_regs),
> > +		.regs = ov05c10_2888x1808_regs,
> > +	},
> > +	.link_freq_index = OV05C10_LINK_FREQ_900MHZ_INDEX,
> > +};
> > +
> > +struct ov05c10 {
> > +	struct v4l2_subdev sd;
> > +	struct media_pad pad;
> > +
> > +	/* V4L2 control handler */
> > +	struct v4l2_ctrl_handler ctrl_handler;
> > +
> > +	/* V4L2 Controls */
> > +	struct v4l2_ctrl *link_freq;
> > +	struct v4l2_ctrl *pixel_rate;
> > +	struct v4l2_ctrl *vblank;
> > +	struct v4l2_ctrl *hblank;
> > +	struct v4l2_ctrl *exposure;
> > +
> > +	struct regmap *regmap;
> > +
> > +	/* gpio descriptor */
> > +	struct gpio_desc *enable_gpio;
> > +
> > +	/* Current page for sensor register control */
> > +	int cur_page;
> > +};
> > +
> > +#define to_ov05c10(_sd)	container_of(_sd, struct ov05c10, sd)
> > +
> > +static int ov05c10_init_state(struct v4l2_subdev *sd,
> > +			      struct v4l2_subdev_state *sd_state)
> > +{
> > +	struct v4l2_mbus_framefmt *frame_fmt;
> > +	struct v4l2_subdev_format fmt = {
> > +		.which = V4L2_SUBDEV_FORMAT_TRY,
> > +		.format = {
> > +			.width = MODE_WIDTH,
> > +			.height = MODE_HEIGHT,
> > +			.code = MEDIA_BUS_FMT_SGRBG10_1X10,
> > +			.field = V4L2_FIELD_NONE,
> > +		}
> > +	};
> > +
> > +	frame_fmt = v4l2_subdev_state_get_format(sd_state, 0);
> > +	*frame_fmt = fmt.format;
> > +	return 0;
> > +}
> > +
> > +static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page, int *err)
> 
> Seems nobody cares the return value of ov05c10_switch_page() or
> ov05c10_reg_write(), etc.. It should be better to use void return, or use
> return value instead of int *err.

As this is a function that has two users, I'd use a more common pattern of
returning a value.

> 
> > +{
> > +	int ret = 0;
> > +
> > +	if (err && *err)
> > +		return *err;
> > +
> > +	if (page != ov05c10->cur_page) {
> > +		cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, &ret);
> > +		if (!ret)
> > +			ov05c10->cur_page = page;
> > +	}
> > +
> > +	if (err)
> > +		*err = ret;
> > +
> > +	return ret;
> > +}
> > +
> > +/* refer to the implementation of cci_read */
> > +static int ov05c10_reg_read(struct ov05c10 *ov05c10, u32 reg,
> > +			    u64 *val, int *err)
> > +{
> > +	u32 page;
> > +	u32 addr;
> > +	int ret = 0;
> > +
> > +	if (err && *err)
> > +		return *err;
> > +
> > +	page = OV05C10_GET_PAGE_NUM(reg);
> > +	addr = OV05C10_GET_REG_ADDR(reg);
> > +	ov05c10_switch_page(ov05c10, page, &ret);
> > +	cci_read(ov05c10->regmap, addr, val, &ret);
> > +	if (err)
> > +		*err = ret;
> > +
> > +	return ret;
> > +}
> > +
> > +/* refer to the implementation of cci_write */
> > +static int ov05c10_reg_write(struct ov05c10 *ov05c10, u32 reg,
> > +			     u64 val, int *err)
> > +{
> > +	u32 page;
> > +	u32 addr;
> > +	int ret = 0;
> > +
> > +	if (err && *err)
> > +		return *err;
> > +
> > +	page = OV05C10_GET_PAGE_NUM(reg);
> > +	addr = OV05C10_GET_REG_ADDR(reg);
> > +	ov05c10_switch_page(ov05c10, page, &ret);
> > +	cci_write(ov05c10->regmap, addr, val, &ret);
> > +	if (err)
> > +		*err = ret;
> > +
> > +	return ret;
> > +}
> > +
> > +static int ov05c10_update_vblank(struct ov05c10 *ov05c10, u32 vblank)
> > +{
> > +	const struct ov05c10_mode *mode = &supported_mode;
> > +	u64 val;
> > +	int ret = 0;
> > +
> > +	val = mode->height + vblank;
> > +	ov05c10_reg_write(ov05c10, OV05C10_REG_VTS, val, &ret);
> > +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > +			  OV05C_REG_TRIGGER_START, &ret);
> > +
> > +	return ret;
> > +}
> 
> I remembered that the ov05c10 VTS control (P1:0x05~0x06) is a bit weird.
> This register seems take the increment of VTS value, so direct write of VTS
> value will not set it properly. Does this version make AE working on your
> platform?
> 
> > +
> > +static int ov05c10_update_exposure(struct ov05c10 *ov05c10, u32 exposure)
> > +{
> > +	int ret = 0;
> > +
> > +	ov05c10_reg_write(ov05c10, OV05C10_REG_EXPOSURE, exposure, &ret);
> > +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > +			  OV05C_REG_TRIGGER_START, &ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static int ov05c10_update_analog_gain(struct ov05c10 *ov05c10, u32 a_gain)
> > +{
> > +	int ret = 0;
> > +
> > +	ov05c10_reg_write(ov05c10, OV05C10_REG_ANALOG_GAIN, a_gain, &ret);
> > +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > +			  OV05C_REG_TRIGGER_START, &ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static int ov05c10_update_digital_gain(struct ov05c10 *ov05c10, u32 d_gain)
> > +{
> > +	u64 val;
> > +	int ret = 0;
> > +
> > +	val = d_gain & OV05C10_DGTL_GAIN_L_MASK;
> > +	ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_L, val, &ret);
> > +
> > +	val = (d_gain & OV05C10_DGTL_GAIN_H_MASK) >> OV05C10_DGTL_GAIN_H_SHIFT;
> > +	ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_H, val, &ret);
> > +
> > +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > +			  OV05C_REG_TRIGGER_START, &ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static int ov05c10_enable_test_pattern(struct ov05c10 *ov05c10, u32 pattern)
> > +{
> > +	u64 val;
> > +	int ret = 0;
> > +
> > +	if (pattern) {
> > +		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
> > +				 &val, &ret);
> > +		ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
> > +				  val | OV05C10_REG_TEST_PATTERN_XXX, &ret);
> > +		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
> > +		val |= OV05C10_TEST_PATTERN_ENABLE;
> > +	} else {
> > +		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
> > +		val &= ~OV05C10_TEST_PATTERN_ENABLE;
> > +	}
> > +
> > +	ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN, val, &ret);
> > +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > +			  OV05C_REG_TRIGGER_START, &ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static int ov05c10_set_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > +	struct ov05c10 *ov05c10 = container_of(ctrl->handler,
> > +					       struct ov05c10, ctrl_handler);
> > +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> > +	const struct ov05c10_mode *mode = &supported_mode;
> > +	s64 max;
> > +	int ret = 0;
> > +
> > +	/* Propagate change of current control to all related controls */
> > +	if (ctrl->id == V4L2_CID_VBLANK) {
> > +		s64 cur_exp = ov05c10->exposure->cur.val;
> > +
> > +		/* Update max exposure while meeting expected vblanking */
> > +		max = mode->height + ctrl->val - OV05C10_EXPOSURE_MAX_MARGIN;
> > +		cur_exp = clamp(cur_exp, ov05c10->exposure->minimum, max);
> > +		ret = __v4l2_ctrl_modify_range(ov05c10->exposure,
> > +					       ov05c10->exposure->minimum,
> > +					       max, ov05c10->exposure->step,
> > +					       cur_exp);
> > +		if (!ret)
> > +			return ret;
> > +	}
> > +
> > +	/*
> > +	 * Applying V4L2 control value only happens
> > +	 * when power is up for streaming
> > +	 */
> > +	if (!pm_runtime_get_if_in_use(&client->dev))
> > +		return 0;
> > +
> > +	switch (ctrl->id) {
> > +	case V4L2_CID_ANALOGUE_GAIN:
> > +		ret = ov05c10_update_analog_gain(ov05c10, ctrl->val);
> > +		break;
> > +	case V4L2_CID_DIGITAL_GAIN:
> > +		ret = ov05c10_update_digital_gain(ov05c10, ctrl->val);
> > +		break;
> > +	case V4L2_CID_EXPOSURE:
> > +		ret = ov05c10_update_exposure(ov05c10, ctrl->val);
> > +		break;
> > +	case V4L2_CID_VBLANK:
> > +		ret = ov05c10_update_vblank(ov05c10, ctrl->val);
> > +		break;
> > +	case V4L2_CID_TEST_PATTERN:
> > +		ret = ov05c10_enable_test_pattern(ov05c10, ctrl->val);
> > +		break;
> > +	default:
> > +		ret = -ENOTTY;
> > +		dev_err(&client->dev,
> > +			"ctrl(id:0x%x,val:0x%x) is not handled\n",
> > +			ctrl->id, ctrl->val);
> > +		break;
> > +	}
> > +
> > +	pm_runtime_put(&client->dev);
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct v4l2_ctrl_ops ov05c10_ctrl_ops = {
> > +	.s_ctrl = ov05c10_set_ctrl,
> > +};
> > +
> > +static int ov05c10_enum_mbus_code(struct v4l2_subdev *sd,
> > +				  struct v4l2_subdev_state *sd_state,
> > +				  struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > +	/* Only one bayer order(GRBG) is supported */
> > +	if (code->index > 0)
> > +		return -EINVAL;
> > +
> > +	code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
> > +
> > +	return 0;
> > +}
> > +
> > +static int ov05c10_enum_frame_size(struct v4l2_subdev *sd,
> > +				   struct v4l2_subdev_state *sd_state,
> > +				   struct v4l2_subdev_frame_size_enum *fse)
> > +{
> > +	/* ov05c10 driver currently only supports 1 mode*/
> > +	if (fse->index != 0)
> > +		return -EINVAL;
> > +
> > +	if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
> > +		return -EINVAL;
> > +
> > +	fse->min_width = supported_mode.width;
> > +	fse->max_width = fse->min_width;
> > +	fse->min_height = supported_mode.height;
> > +	fse->max_height = fse->min_height;
> > +
> > +	return 0;
> > +}
> > +
> > +static void ov05c10_update_pad_format(const struct ov05c10_mode *mode,
> > +				      struct v4l2_subdev_format *fmt)
> > +{
> > +	fmt->format.width = mode->width;
> > +	fmt->format.height = mode->height;
> > +	fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
> > +	fmt->format.field = V4L2_FIELD_NONE;
> > +}
> > +
> > +static int ov05c10_set_pad_format(struct v4l2_subdev *sd,
> > +				  struct v4l2_subdev_state *sd_state,
> > +				  struct v4l2_subdev_format *fmt)
> > +{
> > +	struct v4l2_mbus_framefmt *framefmt;
> > +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > +	const struct ov05c10_mode *mode;
> > +	s32 vblank_def;
> > +	s32 vblank_min;
> > +	s64 pixel_rate;
> > +	s64 link_freq;
> > +	s64 h_blank;
> > +
> > +	/* Only one raw bayer(GRBG) order is supported */
> > +	if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10)
> > +		fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
> > +
> > +	mode = &supported_mode;
> > +	ov05c10_update_pad_format(mode, fmt);
> > +	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> > +		framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
> > +		*framefmt = fmt->format;
> > +	} else {
> > +		__v4l2_ctrl_s_ctrl(ov05c10->link_freq, mode->link_freq_index);
> > +		link_freq = ov05c10_link_freq_menu_items[mode->link_freq_index];
> > +		pixel_rate = link_freq_to_pixel_rate(link_freq,
> > +						     mode->lanes);
> > +		__v4l2_ctrl_s_ctrl_int64(ov05c10->pixel_rate, pixel_rate);
> > +
> > +		/* Update limits and set FPS to default */
> > +		vblank_def = mode->vts_def - mode->height;
> > +		vblank_min = mode->vts_min - mode->height;
> > +		__v4l2_ctrl_modify_range(ov05c10->vblank, vblank_min,
> > +					 OV05C10_VTS_MAX - mode->height,
> > +					 1, vblank_def);
> > +		__v4l2_ctrl_s_ctrl(ov05c10->vblank, vblank_def);
> > +		h_blank = mode->hts;
> > +		__v4l2_ctrl_modify_range(ov05c10->hblank, h_blank,
> > +					 h_blank, 1, h_blank);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int ov05c10_start_streaming(struct ov05c10 *ov05c10)
> > +{
> > +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> > +	const struct ov05c10_mode *mode = &supported_mode;
> > +	const struct ov05c10_reg_list *reg_list;
> > +	int ret = 0;
> > +
> > +	/* Apply default values of current mode */
> > +	reg_list = &mode->reg_list;
> > +	cci_multi_reg_write(ov05c10->regmap, reg_list->regs,
> > +			    reg_list->num_of_regs, &ret);
> > +	if (ret) {
> > +		dev_err(&client->dev, "fail to set mode, ret: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	/* Apply customized values from user */
> > +	ret =  __v4l2_ctrl_handler_setup(ov05c10->sd.ctrl_handler);
> > +	if (ret) {
> > +		dev_err(&client->dev, "failed to setup v4l2 handler %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_on_regs,
> > +			    ARRAY_SIZE(mode_OV05C10_stream_on_regs), &ret);
> > +	if (ret)
> > +		dev_err(&client->dev, "fail to start the streaming\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static int ov05c10_stop_streaming(struct ov05c10 *ov05c10)
> > +{
> > +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> > +	int ret = 0;
> > +
> > +	cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_off_regs,
> > +			    ARRAY_SIZE(mode_OV05C10_stream_off_regs), &ret);
> > +	if (ret)
> > +		dev_err(&client->dev, "fail to stop the streaming\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static void ov05c10_sensor_power_set(struct ov05c10 *ov05c10, bool on)
> > +{
> > +	if (on) {
> > +		gpiod_set_value(ov05c10->enable_gpio, 0);
> > +		usleep_range(10, 20);
> > +
> > +		gpiod_set_value(ov05c10->enable_gpio, 1);
> > +		usleep_range(1000, 2000);
> 
> According to the datasheet, ov05c10 needs at least 8 ms to work after its
> XSHUTDN pin pulled to high. 1 ms maybe too quick, did you tested it? Or the
> enable_gpio is actually not the XSHUTDN pin?
> 
> On Intel platforms, if the sensor driver controls the module power, ususally
> it requires GPIO "reset", regulator "avdd" and clk "img_clk" assigned by
> kernel driver intel_skl_int3472_discrete. I'm not sure whether any devices
> on market using this power control solution, but if any, missing those
> resources will stop them from powering-up cameras.

Please post a patch.

> 
> > +	} else {
> > +		gpiod_set_value(ov05c10->enable_gpio, 0);
> > +		usleep_range(10, 20);
> > +	}
> > +}
> > +
> > +static int ov05c10_enable_streams(struct v4l2_subdev *sd,
> > +				  struct v4l2_subdev_state *state, u32 pad,
> > +				  u64 streams_mask)
> > +{
> > +	struct i2c_client *client = v4l2_get_subdevdata(sd);
> > +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > +	int ret = 0;
> > +
> > +	ret = pm_runtime_resume_and_get(&client->dev);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	ov05c10->cur_page = -1;
> > +
> > +	ret = ov05c10_start_streaming(ov05c10);
> > +	if (ret)
> > +		goto err_rpm_put;
> > +
> > +	return 0;
> > +
> > +err_rpm_put:
> > +	pm_runtime_put(&client->dev);
> > +	return ret;
> > +}
> > +
> > +static int ov05c10_disable_streams(struct v4l2_subdev *sd,
> > +				   struct v4l2_subdev_state *state, u32 pad,
> > +				   u64 streams_mask)
> > +{
> > +	struct i2c_client *client = v4l2_get_subdevdata(sd);
> > +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > +
> > +	ov05c10_stop_streaming(ov05c10);
> > +	pm_runtime_put(&client->dev);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct v4l2_subdev_video_ops ov05c10_video_ops = {
> > +	.s_stream = v4l2_subdev_s_stream_helper,
> > +};
> > +
> > +static const struct v4l2_subdev_pad_ops ov05c10_pad_ops = {
> > +	.enum_mbus_code = ov05c10_enum_mbus_code,
> > +	.get_fmt = v4l2_subdev_get_fmt,
> > +	.set_fmt = ov05c10_set_pad_format,
> > +	.enum_frame_size = ov05c10_enum_frame_size,
> > +	.enable_streams = ov05c10_enable_streams,
> > +	.disable_streams = ov05c10_disable_streams,
> > +};
> > +
> > +static const struct v4l2_subdev_ops ov05c10_subdev_ops = {
> > +	.video = &ov05c10_video_ops,
> > +	.pad = &ov05c10_pad_ops,
> > +};
> > +
> > +static const struct media_entity_operations ov05c10_subdev_entity_ops = {
> > +	.link_validate = v4l2_subdev_link_validate,
> > +};
> > +
> > +static const struct v4l2_subdev_internal_ops ov05c10_internal_ops = {
> > +	.init_state = ov05c10_init_state,
> > +};
> > +
> > +static int ov05c10_init_controls(struct ov05c10 *ov05c10)
> > +{
> > +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> > +	const struct ov05c10_mode *mode = &supported_mode;
> > +	struct v4l2_fwnode_device_properties props;
> > +	struct v4l2_ctrl_handler *ctrl_hdlr;
> > +	s64 pixel_rate_max;
> > +	s64 exposure_max;
> > +	s64 vblank_def;
> > +	s64 vblank_min;
> > +	u32 max_items;
> > +	s64 hblank;
> > +	int ret;
> > +
> > +	ret = v4l2_ctrl_handler_init(&ov05c10->ctrl_handler, 10);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ctrl_hdlr = &ov05c10->ctrl_handler;
> > +
> > +	max_items = ARRAY_SIZE(ov05c10_link_freq_menu_items) - 1;
> > +	ov05c10->link_freq =
> > +		v4l2_ctrl_new_int_menu(ctrl_hdlr,
> > +				       NULL,
> > +				       V4L2_CID_LINK_FREQ,
> > +				       max_items,
> > +				       0,
> > +				       ov05c10_link_freq_menu_items);
> > +	if (ov05c10->link_freq)
> > +		ov05c10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > +
> > +	pixel_rate_max =
> > +		link_freq_to_pixel_rate(ov05c10_link_freq_menu_items[0],
> > +					supported_mode.lanes);
> > +	ov05c10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
> > +						V4L2_CID_PIXEL_RATE,
> > +						0, pixel_rate_max,
> > +						1, pixel_rate_max);
> > +
> > +	vblank_def = mode->vts_def - mode->height;
> > +	vblank_min = mode->vts_min - mode->height;
> > +	ov05c10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
> > +					    V4L2_CID_VBLANK,
> > +					    vblank_min,
> > +					    OV05C10_VTS_MAX - mode->height,
> > +					    1, vblank_def);
> > +
> > +	hblank = (mode->hts > mode->width) ? (mode->hts - mode->width) : 0;
> 
> Here your hts uses 640 but width is 2888, which means hblank is set to 0
> here. This is wrong, please fix your configuration.
> 
> > +	ov05c10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
> > +					    V4L2_CID_HBLANK,
> > +					    hblank, hblank, 1, hblank);
> > +	if (ov05c10->hblank)
> > +		ov05c10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > +
> > +	exposure_max = mode->vts_def - OV05C10_EXPOSURE_MAX_MARGIN;
> > +	ov05c10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
> > +					      V4L2_CID_EXPOSURE,
> > +					      OV05C10_EXPOSURE_MIN,
> > +					      exposure_max,
> > +					      OV05C10_EXPOSURE_STEP,
> > +					      exposure_max);
> > +
> > +	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
> > +			  OV05C10_ANA_GAIN_MIN, OV05C10_ANA_GAIN_MAX,
> > +			  OV05C10_ANA_GAIN_STEP, OV05C10_ANA_GAIN_DEFAULT);
> > +
> > +	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
> > +			  OV05C10_DGTL_GAIN_MIN, OV05C10_DGTL_GAIN_MAX,
> > +			  OV05C10_DGTL_GAIN_STEP, OV05C10_DGTL_GAIN_DEFAULT);
> > +
> > +	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov05c10_ctrl_ops,
> > +				     V4L2_CID_TEST_PATTERN,
> > +				     ARRAY_SIZE(ov05c10_test_pattern_menu) - 1,
> > +				     0, 0, ov05c10_test_pattern_menu);
> > +
> > +	if (ctrl_hdlr->error) {
> > +		ret = ctrl_hdlr->error;
> > +		dev_err(&client->dev, "V4L2 control init failed (%d)\n", ret);
> > +		goto err_hdl_free;
> > +	}
> > +
> > +	ret = v4l2_fwnode_device_parse(&client->dev, &props);
> > +	if (ret)
> > +		goto err_hdl_free;
> > +
> > +	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov05c10_ctrl_ops,
> > +					      &props);
> > +	if (ret)
> > +		goto err_hdl_free;
> > +
> > +	ov05c10->sd.ctrl_handler = ctrl_hdlr;
> > +
> > +	return 0;
> > +
> > +err_hdl_free:
> > +	v4l2_ctrl_handler_free(ctrl_hdlr);
> > +
> > +	return ret;
> > +}
> > +
> > +static int ov05c10_parse_endpoint(struct device *dev,
> > +				  struct fwnode_handle *fwnode)
> > +{
> > +	struct v4l2_fwnode_endpoint bus_cfg = {
> > +		.bus_type = V4L2_MBUS_CSI2_DPHY
> > +	};
> > +	struct fwnode_handle *ep;
> > +	unsigned long bitmap;
> > +	int ret;
> > +
> > +	ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
> > +	if (!ep) {
> > +		dev_err(dev, "Failed to get next endpoint\n");
> > +		return -ENXIO;
> > +	}
> > +
> > +	ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
> > +	fwnode_handle_put(ep);
> > +	if (ret)
> > +		return ret;
> > +
> > +	if (bus_cfg.bus.mipi_csi2.num_data_lanes != supported_mode.lanes) {
> > +		dev_err(dev,
> > +			"number of CSI2 data lanes %d is not supported\n",
> > +			bus_cfg.bus.mipi_csi2.num_data_lanes);
> > +		ret = -EINVAL;
> > +		goto err_endpoint_free;
> > +	}
> > +
> > +	ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
> > +				       bus_cfg.nr_of_link_frequencies,
> > +				       ov05c10_link_frequencies,
> > +				       ARRAY_SIZE(ov05c10_link_frequencies),
> > +				       &bitmap);
> > +	if (ret)
> > +		dev_err(dev, "v4l2_link_freq_to_bitmap fail with %d\n", ret);
> > +err_endpoint_free:
> > +	v4l2_fwnode_endpoint_free(&bus_cfg);
> > +
> > +	return ret;
> > +}
> > +
> > +static int ov05c10_probe(struct i2c_client *client)
> > +{
> > +	struct ov05c10 *ov05c10;
> > +	u32 clkfreq;
> > +	int ret;
> > +
> > +	ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), GFP_KERNEL);
> > +	if (!ov05c10)
> > +		return -ENOMEM;
> > +
> > +	struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> > +
> > +	ret = fwnode_property_read_u32(fwnode, "clock-frequency", &clkfreq);
> 
> Maybe it's better to separate this part fwnode and GPIO code into a
> standalone function?

I don't mind, the probe() function isn't very long anyway.

> 
> > +	if (ret)
> > +		return  dev_err_probe(&client->dev, -EINVAL,
> > +				      "fail to get clock freq\n");
> > +	if (clkfreq != OV05C10_REF_CLK)
> > +		return dev_err_probe(&client->dev, -EINVAL,
> > +				     "fail invalid clock freq %u, %lu expected\n",
> > +				     clkfreq, OV05C10_REF_CLK);
> > +
> > +	ret = ov05c10_parse_endpoint(&client->dev, fwnode);
> > +	if (ret)
> > +		return dev_err_probe(&client->dev, -EINVAL,
> > +				     "fail to parse endpoint\n");
> > +
> > +	ov05c10->enable_gpio = devm_gpiod_get(&client->dev, "enable",
> > +					      GPIOD_OUT_LOW);
> > +	if (IS_ERR(ov05c10->enable_gpio))
> > +		return dev_err_probe(&client->dev,
> > +				     PTR_ERR(ov05c10->enable_gpio),
> > +				     "fail to get enable gpio\n");
> > +
> > +	v4l2_i2c_subdev_init(&ov05c10->sd, client, &ov05c10_subdev_ops);
> > +
> > +	ov05c10->regmap = devm_cci_regmap_init_i2c(client, 8);
> > +	if (IS_ERR(ov05c10->regmap))
> > +		return dev_err_probe(&client->dev, PTR_ERR(ov05c10->regmap),
> > +				     "fail to init cci\n");
> > +
> > +	ov05c10->cur_page = -1;
> > +
> > +	ret = ov05c10_init_controls(ov05c10);
> > +	if (ret)
> > +		return dev_err_probe(&client->dev, ret, "fail to init ctl\n");
> > +
> > +	ov05c10->sd.internal_ops = &ov05c10_internal_ops;
> > +	ov05c10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > +	ov05c10->sd.entity.ops = &ov05c10_subdev_entity_ops;
> > +	ov05c10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > +
> > +	ov05c10->pad.flags = MEDIA_PAD_FL_SOURCE;
> > +
> > +	ret = media_entity_pads_init(&ov05c10->sd.entity, NUM_OF_PADS,
> > +				     &ov05c10->pad);
> > +	if (ret)
> > +		goto err_hdl_free;
> > +
> > +	ret = v4l2_subdev_init_finalize(&ov05c10->sd);
> > +	if (ret < 0)
> > +		goto err_media_entity_cleanup;
> > +
> > +	ret = v4l2_async_register_subdev_sensor(&ov05c10->sd);
> > +	if (ret)
> > +		goto err_media_entity_cleanup;
> > +
> > +	pm_runtime_set_active(&client->dev);
> > +	pm_runtime_enable(&client->dev);
> > +	pm_runtime_idle(&client->dev);
> > +	pm_runtime_set_autosuspend_delay(&client->dev, 1000);
> > +	pm_runtime_use_autosuspend(&client->dev);
> > +	return 0;
> > +
> > +err_media_entity_cleanup:
> > +	media_entity_cleanup(&ov05c10->sd.entity);
> > +
> > +err_hdl_free:
> > +	v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
> > +
> > +	return ret;
> > +}
> > +
> > +static void ov05c10_remove(struct i2c_client *client)
> > +{
> > +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > +
> > +	v4l2_async_unregister_subdev(sd);
> > +	media_entity_cleanup(&sd->entity);
> > +	v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
> > +
> > +	pm_runtime_disable(&client->dev);
> > +	pm_runtime_set_suspended(&client->dev);
> > +}
> > +
> > +static int ov05c10_runtime_resume(struct device *dev)
> > +{
> > +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> > +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > +
> > +	ov05c10_sensor_power_set(ov05c10, true);
> > +	return 0;
> > +}
> > +
> > +static int ov05c10_runtime_suspend(struct device *dev)
> > +{
> > +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> > +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > +
> > +	ov05c10_sensor_power_set(ov05c10, false);
> > +	return 0;
> > +}
> > +
> > +static DEFINE_RUNTIME_DEV_PM_OPS(ov05c10_pm_ops, ov05c10_runtime_suspend,
> > +				 ov05c10_runtime_resume, NULL);
> > +
> > +static const struct i2c_device_id ov05c10_i2c_ids[] = {
> > +	{"ov05c10", 0 },
> > +	{ }
> > +};
> > +MODULE_DEVICE_TABLE(i2c, ov05c10_i2c_ids);
> > +
> > +static struct i2c_driver ov05c10_i2c_driver = {
> > +	.driver = {
> > +		.name = DRV_NAME,
> > +		.pm = pm_ptr(&ov05c10_pm_ops),
> > +	},
> > +	.id_table = ov05c10_i2c_ids,
> > +	.probe = ov05c10_probe,
> > +	.remove = ov05c10_remove,
> > +};
> > +
> > +module_i2c_driver(ov05c10_i2c_driver);
> > +
> > +MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
> > +MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@amd.com>");
> > +MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
> > +MODULE_DESCRIPTION("OmniVision OV05C1010 sensor driver");
> 
> OV05C10
> 
> > +MODULE_LICENSE("GPL");
> 
> 
> Hi Sakari,
> 
> Seems there are already several camera sensors using page-based registers.
> Is it a good idea to add page support in CCI interface?

Sounds like a good idea as such but I'm not sure how common this really is,
I think I've seen a few Omnivision sensors doing this. If implemented, I
think it would be nice if the page could be encoded in the register address
which V4L2 CCI would store and switch page if needed only. This would
require serialising accesses, too. There's some room in CCI register raw
value space so this could be done without even changing that, say, with
8-bit page and 8-bit register address.

-- 
Kind regards,

Sakari Ailus

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-13 22:02   ` Sakari Ailus
@ 2025-06-14 22:52     ` Laurent Pinchart
  2025-06-16 22:33       ` Nirujogi, Pratap
  2025-06-23 11:27       ` Sakari Ailus
  2025-06-16 23:12     ` Nirujogi, Pratap
  1 sibling, 2 replies; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-14 22:52 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Hao Yao, Pratap Nirujogi, mchehab, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, dongcheng.yan, jason.z.chen, jimmy.su

On Fri, Jun 13, 2025 at 10:02:49PM +0000, Sakari Ailus wrote:
> On Fri, Jun 13, 2025 at 12:55:46PM +0800, Hao Yao wrote:
> > Hi Pratap,
> > 
> > Thanks for your patch.
> > 
> > This patch is written for your camera sensor module, which seems very
> > different from those already applied on Dell laptops (some of "Dell Pro"
> > series). Looking into the driver, I think this version will break the
> > devices using ov05c10 sensor.
> 
> There never was such a driver in upstream so nothing breaks. However, in
> order to support these, could you check what would it take to support them
> using this driver and post patches, please?

I'll repeat Kieran's comment here, there's a "first come, first serve"
policiy in the kernel in general. That's one more reason to handle
upstreaming early, if you want your driver to be merged :-)

> > I think this patch is better to be validated on existing devices, but please
> > do some fixes before we can do validation. Please check my comments inline.
> > 
> > On 2025/6/10 03:42, Pratap Nirujogi wrote:
> > > Add driver for OmniVision 5.2M OV05C10 sensor. This driver
> > > supports only the full size normal 2888x1808@30fps 2-lane
> > > sensor profile.
> > > 
> > > Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> > > Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> > > Co-developed-by: Bin Du <bin.du@amd.com>
> > > Signed-off-by: Bin Du <bin.du@amd.com>
> > > Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
> > > ---
> > > Changes v2 -> v3:
> > > 
> > > * Update "refclk" property variable as "clock-frequency".
> > > * Update sensor GPIO connector id name.
> > > * Fix sensor v4l2 compliance issue.
> > > * Fix license info.
> > > * Address review comments.
> > > 
> > >   MAINTAINERS                 |    8 +
> > >   drivers/media/i2c/Kconfig   |   10 +
> > >   drivers/media/i2c/Makefile  |    1 +
> > >   drivers/media/i2c/ov05c10.c | 1061 +++++++++++++++++++++++++++++++++++
> > >   4 files changed, 1080 insertions(+)
> > >   create mode 100644 drivers/media/i2c/ov05c10.c
> > > 
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index a92290fffa16..caca25d00bf2 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -18303,6 +18303,14 @@ T:	git git://linuxtv.org/media.git
> > >   F:	Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml
> > >   F:	drivers/media/i2c/ov02e10.c
> > > +OMNIVISION OV05C10 SENSOR DRIVER
> > > +M:	Nirujogi Pratap <pratap.nirujogi@amd.com>
> > > +M:	Bin Du <bin.du@amd.com>
> > > +L:	linux-media@vger.kernel.org
> > > +S:	Maintained
> > > +T:	git git://linuxtv.org/media.git
> > > +F:	drivers/media/i2c/ov05c10.c
> > > +
> > >   OMNIVISION OV08D10 SENSOR DRIVER
> > >   M:	Jimmy Su <jimmy.su@intel.com>
> > >   L:	linux-media@vger.kernel.org
> > > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > > index e68202954a8f..1662fb29d75c 100644
> > > --- a/drivers/media/i2c/Kconfig
> > > +++ b/drivers/media/i2c/Kconfig
> > > @@ -377,6 +377,16 @@ config VIDEO_OV02C10
> > >   	  To compile this driver as a module, choose M here: the
> > >   	  module will be called ov02c10.
> > > +config VIDEO_OV05C10
> > > +	tristate "OmniVision OV05C10 sensor support"
> > > +	select V4L2_CCI_I2C
> > > +	help
> > > +	  This is a Video4Linux2 sensor driver for the OmniVision
> > > +	  OV05C10 camera.
> > > +
> > > +	  To compile this driver as a module, choose M here: the
> > > +	  module will be called OV05C10.
> > > +
> > >   config VIDEO_OV08D10
> > >           tristate "OmniVision OV08D10 sensor support"
> > >           help
> > > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > > index 5873d29433ee..b4a1d721a7f2 100644
> > > --- a/drivers/media/i2c/Makefile
> > > +++ b/drivers/media/i2c/Makefile
> > > @@ -85,6 +85,7 @@ obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
> > >   obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
> > >   obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o
> > >   obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o
> > > +obj-$(CONFIG_VIDEO_OV05C10) += ov05c10.o
> > >   obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
> > >   obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
> > >   obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
> > > diff --git a/drivers/media/i2c/ov05c10.c b/drivers/media/i2c/ov05c10.c
> > > new file mode 100644
> > > index 000000000000..9a1e493c4073
> > > --- /dev/null
> > > +++ b/drivers/media/i2c/ov05c10.c
> > > @@ -0,0 +1,1061 @@
> > > +// SPDX-License-Identifier: GPL-2.0+
> > > +// Copyright (C) 2025 Advanced Micro Devices, Inc.
> > > +
> > > +#include <linux/clk.h>
> > > +#include <linux/delay.h>
> > > +#include <linux/gpio.h>
> > > +#include <linux/i2c.h>
> > > +#include <linux/module.h>
> > > +#include <linux/pm_runtime.h>
> > > +#include <linux/units.h>
> > > +#include <media/v4l2-cci.h>
> > > +#include <media/v4l2-ctrls.h>
> > > +#include <media/v4l2-device.h>
> > > +#include <media/v4l2-fwnode.h>
> > > +
> > > +#define DRV_NAME			"ov05c10"
> > > +#define OV05C10_REF_CLK			(24 * HZ_PER_MHZ)
> > 
> > Seems your module use 24 MHz clock input. The Dell's modules always use
> > 19.2MHz, which means your the PLL settings will not work on Dell's.
> 
> This is ok as further work. Please send a patch. :-)

The patch should calculate PLL configuration dynamically, perhaps using
the ccs-pll calculator if applicable.

> > > +
> > > +#define MODE_WIDTH  2888
> > > +#define MODE_HEIGHT 1808
> > > +
> > > +#define PAGE_NUM_MASK			0xff000000
> > > +#define PAGE_NUM_SHIFT			24
> > > +#define REG_ADDR_MASK			0x00ffffff
> > > +
> > > +#define OV05C10_SYSCTL_PAGE		(0 << PAGE_NUM_SHIFT)
> > > +#define OV05C10_CISCTL_PAGE		(1 << PAGE_NUM_SHIFT)
> > > +#define OV05C10_ISPCTL_PAGE		(4 << PAGE_NUM_SHIFT)
> > > +
> > > +/* Chip ID */
> > > +#define OV05C10_REG_CHIP_ID		(CCI_REG24(0x00) | OV05C10_SYSCTL_PAGE)
> > > +#define OV05C10_CHIP_ID			0x43055610
> > > +
> > > +/* Control registers */
> > > +#define OV05C10_REG_TRIGGER		(CCI_REG8(0x01) | OV05C10_CISCTL_PAGE)
> > > +#define OV05C_REG_TRIGGER_START		BIT(0)
> > > +
> > > +/* Exposure control */
> > > +#define OV05C10_REG_EXPOSURE		(CCI_REG24(0x02) | OV05C10_CISCTL_PAGE)
> > > +#define OV05C10_EXPOSURE_MAX_MARGIN	33
> > > +#define OV05C10_EXPOSURE_MIN		4
> > > +#define OV05C10_EXPOSURE_STEP		1
> > > +#define OV05C10_EXPOSURE_DEFAULT	0x40
> > > +
> > > +/* V_TIMING internal */
> > > +#define OV05C10_REG_VTS			(CCI_REG16(0x05) | OV05C10_CISCTL_PAGE)
> > > +#define OV05C10_VTS_30FPS		1860
> > > +#define OV05C10_VTS_MAX			0x7fff
> > > +
> > > +/* Test Pattern Control */
> > > +#define OV05C10_REG_TEST_PATTERN	(CCI_REG8(0x12) | OV05C10_ISPCTL_PAGE)
> > > +#define OV05C10_TEST_PATTERN_ENABLE	BIT(0)
> > > +#define OV05C10_REG_TEST_PATTERN_CTL	(CCI_REG8(0xf3) | OV05C10_ISPCTL_PAGE)
> > > +#define OV05C10_REG_TEST_PATTERN_XXX	BIT(0)
> > > +
> > > +/* Digital gain control */
> > > +#define OV05C10_REG_DGTL_GAIN_H		(CCI_REG8(0x21) | OV05C10_CISCTL_PAGE)
> > > +#define OV05C10_REG_DGTL_GAIN_L		(CCI_REG8(0x22) | OV05C10_CISCTL_PAGE)
> > > +
> > > +#define OV05C10_DGTL_GAIN_MIN		0x40
> > > +#define OV05C10_DGTL_GAIN_MAX		0xff
> > > +#define OV05C10_DGTL_GAIN_DEFAULT	0x40
> > > +#define OV05C10_DGTL_GAIN_STEP		1
> > > +
> > > +#define OV05C10_DGTL_GAIN_L_MASK	0xff
> > > +#define OV05C10_DGTL_GAIN_H_SHIFT	8
> > > +#define OV05C10_DGTL_GAIN_H_MASK	0xff00
> > > +
> > > +/* Analog gain control */
> > > +#define OV05C10_REG_ANALOG_GAIN		(CCI_REG8(0x24) | OV05C10_CISCTL_PAGE)
> > > +#define OV05C10_ANA_GAIN_MIN		0x80
> > > +#define OV05C10_ANA_GAIN_MAX		0x07c0
> > > +#define OV05C10_ANA_GAIN_STEP		1
> > > +#define OV05C10_ANA_GAIN_DEFAULT	0x80
> > > +
> > > +/* H TIMING internal */
> > > +#define OV05C10_REG_HTS			(CCI_REG16(0x37) | OV05C10_CISCTL_PAGE)
> > > +#define OV05C10_HTS_30FPS		0x0280
> > > +
> > > +/* Page selection */
> > > +#define OV05C10_REG_PAGE_CTL		CCI_REG8(0xfd)
> > > +
> > > +#define NUM_OF_PADS 1
> > > +
> > > +#define OV05C10_GET_PAGE_NUM(reg)	(((reg) & PAGE_NUM_MASK) >>\
> > > +					 PAGE_NUM_SHIFT)
> > > +#define OV05C10_GET_REG_ADDR(reg)	((reg) & REG_ADDR_MASK)
> > > +
> > > +enum {
> > > +	OV05C10_LINK_FREQ_900MHZ_INDEX,
> > > +};
> > > +
> > > +struct ov05c10_reg_list {
> > > +	u32 num_of_regs;
> > > +	const struct cci_reg_sequence *regs;
> > > +};
> > > +
> > > +/* Mode : resolution and related config&values */
> > > +struct ov05c10_mode {
> > > +	/* Frame width */
> > > +	u32 width;
> > > +	/* Frame height */
> > > +	u32 height;
> > > +	/* number of lanes */
> > > +	u32 lanes;
> > > +
> > > +	/* V-timing */
> > > +	u32 vts_def;
> > > +	u32 vts_min;
> > > +
> > > +	/* HTS */
> > > +	u32 hts;
> > > +
> > > +	/* Index of Link frequency config to be used */
> > > +	u32 link_freq_index;
> > > +
> > > +	/* Default register values */
> > > +	struct ov05c10_reg_list reg_list;
> > > +};
> > > +
> > > +static const s64 ov05c10_link_frequencies[] = {
> > > +	925 * HZ_PER_MHZ,
> > > +};
> > 
> > Is it 900 MHz, or 925 MHz?
> > 
> > > +
> > > +/* 2888x1808 30fps, 1800mbps, 2lane, 24mhz */
> > 
> > Currently Dell's devices with ov05c10 use a CV chip to passthrough MIPI CSI
> > signals, but it supports max 750 MHz link frequency. That's why this
> > version:
> > https://github.com/intel/ipu6-drivers/blob/master/drivers/media/i2c/ov05c10.c
> > uses 480 MHz link frequency and a different resolution setting (2800x1576).
> > At least the setting in out-of-tree Github driver should be merged into this
> > version.
> 
> Ditto.
> 
> > > +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
> > > +	{ CCI_REG8(0xfd),  0x00 },
> > > +	{ CCI_REG8(0x20),  0x00 },
> > > +	{ CCI_REG8(0xfd),  0x00 },
> > > +	{ CCI_REG8(0x20),  0x0b },
> > > +	{ CCI_REG8(0xc1),  0x09 },
> > > +	{ CCI_REG8(0x21),  0x06 },
> > > +	{ CCI_REG8(0x14),  0x78 },
> > > +	{ CCI_REG8(0xe7),  0x03 },
> > > +	{ CCI_REG8(0xe7),  0x00 },
> > > +	{ CCI_REG8(0x21),  0x00 },
> > > +	{ CCI_REG8(0xfd),  0x01 },
> > > +	{ CCI_REG8(0x03),  0x00 },
> > > +	{ CCI_REG8(0x04),  0x06 },
> > > +	{ CCI_REG8(0x05),  0x07 },
> > > +	{ CCI_REG8(0x06),  0x44 },
> > > +	{ CCI_REG8(0x07),  0x08 },
> > > +	{ CCI_REG8(0x1b),  0x01 },
> > > +	{ CCI_REG8(0x24),  0xff },
> > > +	{ CCI_REG8(0x32),  0x03 },
> > > +	{ CCI_REG8(0x42),  0x5d },
> > > +	{ CCI_REG8(0x43),  0x08 },
> > > +	{ CCI_REG8(0x44),  0x81 },
> > > +	{ CCI_REG8(0x46),  0x5f },
> > > +	{ CCI_REG8(0x48),  0x18 },
> > > +	{ CCI_REG8(0x49),  0x04 },
> > > +	{ CCI_REG8(0x5c),  0x18 },
> > > +	{ CCI_REG8(0x5e),  0x13 },
> > > +	{ CCI_REG8(0x70),  0x15 },
> > > +	{ CCI_REG8(0x77),  0x35 },
> > > +	{ CCI_REG8(0x79),  0x00 },
> > > +	{ CCI_REG8(0x7b),  0x08 },
> > > +	{ CCI_REG8(0x7d),  0x08 },
> > > +	{ CCI_REG8(0x7e),  0x08 },
> > > +	{ CCI_REG8(0x7f),  0x08 },
> > > +	{ CCI_REG8(0x90),  0x37 },
> > > +	{ CCI_REG8(0x91),  0x05 },
> > > +	{ CCI_REG8(0x92),  0x18 },
> > > +	{ CCI_REG8(0x93),  0x27 },
> > > +	{ CCI_REG8(0x94),  0x05 },
> > > +	{ CCI_REG8(0x95),  0x38 },
> > > +	{ CCI_REG8(0x9b),  0x00 },
> > > +	{ CCI_REG8(0x9c),  0x06 },
> > > +	{ CCI_REG8(0x9d),  0x28 },
> > > +	{ CCI_REG8(0x9e),  0x06 },
> > > +	{ CCI_REG8(0xb2),  0x0f },
> > > +	{ CCI_REG8(0xb3),  0x29 },
> > > +	{ CCI_REG8(0xbf),  0x3c },
> > > +	{ CCI_REG8(0xc2),  0x04 },
> > > +	{ CCI_REG8(0xc4),  0x00 },
> > > +	{ CCI_REG8(0xca),  0x20 },
> > > +	{ CCI_REG8(0xcb),  0x20 },
> > > +	{ CCI_REG8(0xcc),  0x28 },
> > > +	{ CCI_REG8(0xcd),  0x28 },
> > > +	{ CCI_REG8(0xce),  0x20 },
> > > +	{ CCI_REG8(0xcf),  0x20 },
> > > +	{ CCI_REG8(0xd0),  0x2a },
> > > +	{ CCI_REG8(0xd1),  0x2a },
> > > +	{ CCI_REG8(0xfd),  0x0f },
> > > +	{ CCI_REG8(0x00),  0x00 },
> > > +	{ CCI_REG8(0x01),  0xa0 },
> > > +	{ CCI_REG8(0x02),  0x48 },
> > > +	{ CCI_REG8(0x07),  0x8f },
> > > +	{ CCI_REG8(0x08),  0x70 },
> > > +	{ CCI_REG8(0x09),  0x01 },
> > > +	{ CCI_REG8(0x0b),  0x40 },
> > > +	{ CCI_REG8(0x0d),  0x07 },
> > > +	{ CCI_REG8(0x11),  0x33 },
> > > +	{ CCI_REG8(0x12),  0x77 },
> > > +	{ CCI_REG8(0x13),  0x66 },
> > > +	{ CCI_REG8(0x14),  0x65 },
> > > +	{ CCI_REG8(0x15),  0x37 },
> > > +	{ CCI_REG8(0x16),  0xbf },
> > > +	{ CCI_REG8(0x17),  0xff },
> > > +	{ CCI_REG8(0x18),  0xff },
> > > +	{ CCI_REG8(0x19),  0x12 },
> > > +	{ CCI_REG8(0x1a),  0x10 },
> > > +	{ CCI_REG8(0x1c),  0x77 },
> > > +	{ CCI_REG8(0x1d),  0x77 },
> > > +	{ CCI_REG8(0x20),  0x0f },
> > > +	{ CCI_REG8(0x21),  0x0f },
> > > +	{ CCI_REG8(0x22),  0x0f },
> > > +	{ CCI_REG8(0x23),  0x0f },
> > > +	{ CCI_REG8(0x2b),  0x20 },
> > > +	{ CCI_REG8(0x2c),  0x20 },
> > > +	{ CCI_REG8(0x2d),  0x04 },
> > > +	{ CCI_REG8(0xfd),  0x03 },
> > > +	{ CCI_REG8(0x9d),  0x0f },
> > > +	{ CCI_REG8(0x9f),  0x40 },
> > > +	{ CCI_REG8(0xfd),  0x00 },
> > > +	{ CCI_REG8(0x20),  0x1b },
> > > +	{ CCI_REG8(0xfd),  0x04 },
> > > +	{ CCI_REG8(0x19),  0x60 },
> > > +	{ CCI_REG8(0xfd),  0x02 },
> > > +	{ CCI_REG8(0x75),  0x05 },
> > > +	{ CCI_REG8(0x7f),  0x06 },
> > > +	{ CCI_REG8(0x9a),  0x03 },
> > > +	{ CCI_REG8(0xa2),  0x07 },
> > > +	{ CCI_REG8(0xa3),  0x10 },
> > > +	{ CCI_REG8(0xa5),  0x02 },
> > > +	{ CCI_REG8(0xa6),  0x0b },
> > > +	{ CCI_REG8(0xa7),  0x48 },
> > > +	{ CCI_REG8(0xfd),  0x07 },
> > > +	{ CCI_REG8(0x42),  0x00 },
> > > +	{ CCI_REG8(0x43),  0x80 },
> > > +	{ CCI_REG8(0x44),  0x00 },
> > > +	{ CCI_REG8(0x45),  0x80 },
> > > +	{ CCI_REG8(0x46),  0x00 },
> > > +	{ CCI_REG8(0x47),  0x80 },
> > > +	{ CCI_REG8(0x48),  0x00 },
> > > +	{ CCI_REG8(0x49),  0x80 },
> > > +	{ CCI_REG8(0x00),  0xf7 },
> > > +	{ CCI_REG8(0xfd),  0x00 },
> > > +	{ CCI_REG8(0xe7),  0x03 },
> > > +	{ CCI_REG8(0xe7),  0x00 },
> > > +	{ CCI_REG8(0xfd),  0x00 },
> > > +	{ CCI_REG8(0x93),  0x18 },
> > > +	{ CCI_REG8(0x94),  0xff },
> > > +	{ CCI_REG8(0x95),  0xbd },
> > > +	{ CCI_REG8(0x96),  0x1a },
> > > +	{ CCI_REG8(0x98),  0x04 },
> > > +	{ CCI_REG8(0x99),  0x08 },
> > > +	{ CCI_REG8(0x9b),  0x10 },
> > > +	{ CCI_REG8(0x9c),  0x3f },
> > > +	{ CCI_REG8(0xa1),  0x05 },
> > > +	{ CCI_REG8(0xa4),  0x2f },
> > > +	{ CCI_REG8(0xc0),  0x0c },
> > > +	{ CCI_REG8(0xc1),  0x08 },
> > > +	{ CCI_REG8(0xc2),  0x00 },
> > > +	{ CCI_REG8(0xb6),  0x20 },
> > > +	{ CCI_REG8(0xbb),  0x80 },
> > > +	{ CCI_REG8(0xfd),  0x00 },
> > > +	{ CCI_REG8(0xa0),  0x01 },
> > > +	{ CCI_REG8(0xfd),  0x01 },

Please replace these with names macros where possible. I'm sure quite a
few of the registers configured here are documented in the datasheet.
The registers that configure the mode (analog crop, digital crop,
binning, skipping, ...) should be computed dynamically from the subdev
pad format and selection rectangles, not hardcoded.

> > > +};
> > > +
> > > +static const struct cci_reg_sequence mode_OV05C10_stream_on_regs[] = {
> > > +	{ CCI_REG8(0xfd), 0x01 },
> > > +	{ CCI_REG8(0x33), 0x03 },
> > > +	{ CCI_REG8(0x01), 0x02 },
> > > +	{ CCI_REG8(0xfd), 0x00 },
> > > +	{ CCI_REG8(0x20), 0x1f },
> > > +	{ CCI_REG8(0xfd), 0x01 },
> > > +};
> > > +
> > > +static const struct cci_reg_sequence mode_OV05C10_stream_off_regs[] = {
> > > +	{ CCI_REG8(0xfd), 0x00 },
> > > +	{ CCI_REG8(0x20), 0x5b },
> > > +	{ CCI_REG8(0xfd), 0x01 },
> > > +	{ CCI_REG8(0x33), 0x02 },
> > > +	{ CCI_REG8(0x01), 0x02 },
> > > +};
> > > +
> > > +static const char * const ov05c10_test_pattern_menu[] = {
> > > +	"Disabled",
> > > +	"Vertical Color Bar Type 1",
> > > +	"Vertical Color Bar Type 2",
> > > +	"Vertical Color Bar Type 3",
> > > +	"Vertical Color Bar Type 4"
> > > +};
> > > +
> > > +/* Configurations for supported link frequencies */
> > > +#define OV05C10_LINK_FREQ_900MHZ	(900 * HZ_PER_MHZ)
> > > +
> > > +/* Number of lanes supported */
> > > +#define OV05C10_DATA_LANES		2
> > > +
> > > +/* Bits per sample of sensor output */
> > > +#define OV05C10_BITS_PER_SAMPLE		10
> > > +
> > > +/*
> > > + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
> > > + * data rate => double data rate; number of lanes => 2; bits per pixel => 10
> > > + */
> > > +static u64 link_freq_to_pixel_rate(u64 f, u32 lane_nr)
> > > +{
> > > +	f *= 2 * lane_nr;
> > > +	do_div(f, OV05C10_BITS_PER_SAMPLE);
> > > +
> > > +	return f;
> > > +}
> > > +
> > > +/* Menu items for LINK_FREQ V4L2 control */
> > > +static const s64 ov05c10_link_freq_menu_items[] = {
> > > +	OV05C10_LINK_FREQ_900MHZ,
> > > +};
> > > +
> > > +/* Mode configs, currently, only support 1 mode */
> > > +static const struct ov05c10_mode supported_mode = {
> > > +	.width = MODE_WIDTH,
> > > +	.height = MODE_HEIGHT,
> > > +	.vts_def = OV05C10_VTS_30FPS,
> > > +	.vts_min = OV05C10_VTS_30FPS,
> > > +	.hts = 640,
> > > +	.lanes = 2,
> > > +	.reg_list = {
> > > +		.num_of_regs = ARRAY_SIZE(ov05c10_2888x1808_regs),
> > > +		.regs = ov05c10_2888x1808_regs,
> > > +	},
> > > +	.link_freq_index = OV05C10_LINK_FREQ_900MHZ_INDEX,
> > > +};
> > > +
> > > +struct ov05c10 {
> > > +	struct v4l2_subdev sd;
> > > +	struct media_pad pad;
> > > +
> > > +	/* V4L2 control handler */
> > > +	struct v4l2_ctrl_handler ctrl_handler;
> > > +
> > > +	/* V4L2 Controls */
> > > +	struct v4l2_ctrl *link_freq;
> > > +	struct v4l2_ctrl *pixel_rate;
> > > +	struct v4l2_ctrl *vblank;
> > > +	struct v4l2_ctrl *hblank;
> > > +	struct v4l2_ctrl *exposure;
> > > +
> > > +	struct regmap *regmap;
> > > +
> > > +	/* gpio descriptor */
> > > +	struct gpio_desc *enable_gpio;
> > > +
> > > +	/* Current page for sensor register control */
> > > +	int cur_page;
> > > +};
> > > +
> > > +#define to_ov05c10(_sd)	container_of(_sd, struct ov05c10, sd)
> > > +
> > > +static int ov05c10_init_state(struct v4l2_subdev *sd,
> > > +			      struct v4l2_subdev_state *sd_state)
> > > +{
> > > +	struct v4l2_mbus_framefmt *frame_fmt;
> > > +	struct v4l2_subdev_format fmt = {
> > > +		.which = V4L2_SUBDEV_FORMAT_TRY,
> > > +		.format = {
> > > +			.width = MODE_WIDTH,
> > > +			.height = MODE_HEIGHT,
> > > +			.code = MEDIA_BUS_FMT_SGRBG10_1X10,
> > > +			.field = V4L2_FIELD_NONE,
> > > +		}
> > > +	};
> > > +
> > > +	frame_fmt = v4l2_subdev_state_get_format(sd_state, 0);
> > > +	*frame_fmt = fmt.format;
> > > +	return 0;
> > > +}
> > > +
> > > +static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page, int *err)
> > 
> > Seems nobody cares the return value of ov05c10_switch_page() or
> > ov05c10_reg_write(), etc.. It should be better to use void return, or use
> > return value instead of int *err.
> 
> As this is a function that has two users, I'd use a more common pattern of
> returning a value.
> 
> > > +{
> > > +	int ret = 0;
> > > +
> > > +	if (err && *err)
> > > +		return *err;
> > > +
> > > +	if (page != ov05c10->cur_page) {
> > > +		cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, &ret);
> > > +		if (!ret)
> > > +			ov05c10->cur_page = page;
> > > +	}
> > > +
> > > +	if (err)
> > > +		*err = ret;
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +/* refer to the implementation of cci_read */
> > > +static int ov05c10_reg_read(struct ov05c10 *ov05c10, u32 reg,
> > > +			    u64 *val, int *err)
> > > +{
> > > +	u32 page;
> > > +	u32 addr;
> > > +	int ret = 0;
> > > +
> > > +	if (err && *err)
> > > +		return *err;
> > > +
> > > +	page = OV05C10_GET_PAGE_NUM(reg);
> > > +	addr = OV05C10_GET_REG_ADDR(reg);
> > > +	ov05c10_switch_page(ov05c10, page, &ret);
> > > +	cci_read(ov05c10->regmap, addr, val, &ret);
> > > +	if (err)
> > > +		*err = ret;
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +/* refer to the implementation of cci_write */
> > > +static int ov05c10_reg_write(struct ov05c10 *ov05c10, u32 reg,
> > > +			     u64 val, int *err)
> > > +{
> > > +	u32 page;
> > > +	u32 addr;
> > > +	int ret = 0;
> > > +
> > > +	if (err && *err)
> > > +		return *err;
> > > +
> > > +	page = OV05C10_GET_PAGE_NUM(reg);
> > > +	addr = OV05C10_GET_REG_ADDR(reg);
> > > +	ov05c10_switch_page(ov05c10, page, &ret);
> > > +	cci_write(ov05c10->regmap, addr, val, &ret);
> > > +	if (err)
> > > +		*err = ret;
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int ov05c10_update_vblank(struct ov05c10 *ov05c10, u32 vblank)
> > > +{
> > > +	const struct ov05c10_mode *mode = &supported_mode;
> > > +	u64 val;
> > > +	int ret = 0;
> > > +
> > > +	val = mode->height + vblank;
> > > +	ov05c10_reg_write(ov05c10, OV05C10_REG_VTS, val, &ret);
> > > +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > > +			  OV05C_REG_TRIGGER_START, &ret);
> > > +
> > > +	return ret;
> > > +}
> > 
> > I remembered that the ov05c10 VTS control (P1:0x05~0x06) is a bit weird.
> > This register seems take the increment of VTS value, so direct write of VTS
> > value will not set it properly. Does this version make AE working on your
> > platform?
> > 
> > > +
> > > +static int ov05c10_update_exposure(struct ov05c10 *ov05c10, u32 exposure)
> > > +{
> > > +	int ret = 0;
> > > +
> > > +	ov05c10_reg_write(ov05c10, OV05C10_REG_EXPOSURE, exposure, &ret);
> > > +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > > +			  OV05C_REG_TRIGGER_START, &ret);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int ov05c10_update_analog_gain(struct ov05c10 *ov05c10, u32 a_gain)
> > > +{
> > > +	int ret = 0;
> > > +
> > > +	ov05c10_reg_write(ov05c10, OV05C10_REG_ANALOG_GAIN, a_gain, &ret);
> > > +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > > +			  OV05C_REG_TRIGGER_START, &ret);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int ov05c10_update_digital_gain(struct ov05c10 *ov05c10, u32 d_gain)
> > > +{
> > > +	u64 val;
> > > +	int ret = 0;
> > > +
> > > +	val = d_gain & OV05C10_DGTL_GAIN_L_MASK;
> > > +	ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_L, val, &ret);
> > > +
> > > +	val = (d_gain & OV05C10_DGTL_GAIN_H_MASK) >> OV05C10_DGTL_GAIN_H_SHIFT;
> > > +	ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_H, val, &ret);
> > > +
> > > +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > > +			  OV05C_REG_TRIGGER_START, &ret);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int ov05c10_enable_test_pattern(struct ov05c10 *ov05c10, u32 pattern)
> > > +{
> > > +	u64 val;
> > > +	int ret = 0;
> > > +
> > > +	if (pattern) {
> > > +		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
> > > +				 &val, &ret);
> > > +		ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
> > > +				  val | OV05C10_REG_TEST_PATTERN_XXX, &ret);
> > > +		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
> > > +		val |= OV05C10_TEST_PATTERN_ENABLE;
> > > +	} else {
> > > +		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
> > > +		val &= ~OV05C10_TEST_PATTERN_ENABLE;
> > > +	}
> > > +
> > > +	ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN, val, &ret);
> > > +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> > > +			  OV05C_REG_TRIGGER_START, &ret);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int ov05c10_set_ctrl(struct v4l2_ctrl *ctrl)
> > > +{
> > > +	struct ov05c10 *ov05c10 = container_of(ctrl->handler,
> > > +					       struct ov05c10, ctrl_handler);
> > > +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> > > +	const struct ov05c10_mode *mode = &supported_mode;
> > > +	s64 max;
> > > +	int ret = 0;
> > > +
> > > +	/* Propagate change of current control to all related controls */
> > > +	if (ctrl->id == V4L2_CID_VBLANK) {
> > > +		s64 cur_exp = ov05c10->exposure->cur.val;
> > > +
> > > +		/* Update max exposure while meeting expected vblanking */
> > > +		max = mode->height + ctrl->val - OV05C10_EXPOSURE_MAX_MARGIN;
> > > +		cur_exp = clamp(cur_exp, ov05c10->exposure->minimum, max);
> > > +		ret = __v4l2_ctrl_modify_range(ov05c10->exposure,
> > > +					       ov05c10->exposure->minimum,
> > > +					       max, ov05c10->exposure->step,
> > > +					       cur_exp);
> > > +		if (!ret)
> > > +			return ret;
> > > +	}
> > > +
> > > +	/*
> > > +	 * Applying V4L2 control value only happens
> > > +	 * when power is up for streaming
> > > +	 */
> > > +	if (!pm_runtime_get_if_in_use(&client->dev))
> > > +		return 0;
> > > +
> > > +	switch (ctrl->id) {
> > > +	case V4L2_CID_ANALOGUE_GAIN:
> > > +		ret = ov05c10_update_analog_gain(ov05c10, ctrl->val);
> > > +		break;
> > > +	case V4L2_CID_DIGITAL_GAIN:
> > > +		ret = ov05c10_update_digital_gain(ov05c10, ctrl->val);
> > > +		break;
> > > +	case V4L2_CID_EXPOSURE:
> > > +		ret = ov05c10_update_exposure(ov05c10, ctrl->val);
> > > +		break;
> > > +	case V4L2_CID_VBLANK:
> > > +		ret = ov05c10_update_vblank(ov05c10, ctrl->val);
> > > +		break;
> > > +	case V4L2_CID_TEST_PATTERN:
> > > +		ret = ov05c10_enable_test_pattern(ov05c10, ctrl->val);
> > > +		break;
> > > +	default:
> > > +		ret = -ENOTTY;
> > > +		dev_err(&client->dev,
> > > +			"ctrl(id:0x%x,val:0x%x) is not handled\n",
> > > +			ctrl->id, ctrl->val);
> > > +		break;
> > > +	}
> > > +
> > > +	pm_runtime_put(&client->dev);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static const struct v4l2_ctrl_ops ov05c10_ctrl_ops = {
> > > +	.s_ctrl = ov05c10_set_ctrl,
> > > +};
> > > +
> > > +static int ov05c10_enum_mbus_code(struct v4l2_subdev *sd,
> > > +				  struct v4l2_subdev_state *sd_state,
> > > +				  struct v4l2_subdev_mbus_code_enum *code)
> > > +{
> > > +	/* Only one bayer order(GRBG) is supported */
> > > +	if (code->index > 0)
> > > +		return -EINVAL;
> > > +
> > > +	code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int ov05c10_enum_frame_size(struct v4l2_subdev *sd,
> > > +				   struct v4l2_subdev_state *sd_state,
> > > +				   struct v4l2_subdev_frame_size_enum *fse)
> > > +{
> > > +	/* ov05c10 driver currently only supports 1 mode*/
> > > +	if (fse->index != 0)
> > > +		return -EINVAL;
> > > +
> > > +	if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
> > > +		return -EINVAL;
> > > +
> > > +	fse->min_width = supported_mode.width;
> > > +	fse->max_width = fse->min_width;
> > > +	fse->min_height = supported_mode.height;
> > > +	fse->max_height = fse->min_height;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static void ov05c10_update_pad_format(const struct ov05c10_mode *mode,
> > > +				      struct v4l2_subdev_format *fmt)
> > > +{
> > > +	fmt->format.width = mode->width;
> > > +	fmt->format.height = mode->height;
> > > +	fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
> > > +	fmt->format.field = V4L2_FIELD_NONE;
> > > +}
> > > +
> > > +static int ov05c10_set_pad_format(struct v4l2_subdev *sd,
> > > +				  struct v4l2_subdev_state *sd_state,
> > > +				  struct v4l2_subdev_format *fmt)
> > > +{
> > > +	struct v4l2_mbus_framefmt *framefmt;
> > > +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > > +	const struct ov05c10_mode *mode;
> > > +	s32 vblank_def;
> > > +	s32 vblank_min;
> > > +	s64 pixel_rate;
> > > +	s64 link_freq;
> > > +	s64 h_blank;
> > > +
> > > +	/* Only one raw bayer(GRBG) order is supported */
> > > +	if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10)
> > > +		fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
> > > +
> > > +	mode = &supported_mode;
> > > +	ov05c10_update_pad_format(mode, fmt);
> > > +	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> > > +		framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
> > > +		*framefmt = fmt->format;
> > > +	} else {
> > > +		__v4l2_ctrl_s_ctrl(ov05c10->link_freq, mode->link_freq_index);
> > > +		link_freq = ov05c10_link_freq_menu_items[mode->link_freq_index];
> > > +		pixel_rate = link_freq_to_pixel_rate(link_freq,
> > > +						     mode->lanes);
> > > +		__v4l2_ctrl_s_ctrl_int64(ov05c10->pixel_rate, pixel_rate);
> > > +
> > > +		/* Update limits and set FPS to default */
> > > +		vblank_def = mode->vts_def - mode->height;
> > > +		vblank_min = mode->vts_min - mode->height;
> > > +		__v4l2_ctrl_modify_range(ov05c10->vblank, vblank_min,
> > > +					 OV05C10_VTS_MAX - mode->height,
> > > +					 1, vblank_def);
> > > +		__v4l2_ctrl_s_ctrl(ov05c10->vblank, vblank_def);
> > > +		h_blank = mode->hts;
> > > +		__v4l2_ctrl_modify_range(ov05c10->hblank, h_blank,
> > > +					 h_blank, 1, h_blank);
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int ov05c10_start_streaming(struct ov05c10 *ov05c10)
> > > +{
> > > +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> > > +	const struct ov05c10_mode *mode = &supported_mode;
> > > +	const struct ov05c10_reg_list *reg_list;
> > > +	int ret = 0;
> > > +
> > > +	/* Apply default values of current mode */
> > > +	reg_list = &mode->reg_list;
> > > +	cci_multi_reg_write(ov05c10->regmap, reg_list->regs,
> > > +			    reg_list->num_of_regs, &ret);
> > > +	if (ret) {
> > > +		dev_err(&client->dev, "fail to set mode, ret: %d\n", ret);
> > > +		return ret;
> > > +	}
> > > +
> > > +	/* Apply customized values from user */
> > > +	ret =  __v4l2_ctrl_handler_setup(ov05c10->sd.ctrl_handler);
> > > +	if (ret) {
> > > +		dev_err(&client->dev, "failed to setup v4l2 handler %d\n", ret);
> > > +		return ret;
> > > +	}
> > > +
> > > +	cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_on_regs,
> > > +			    ARRAY_SIZE(mode_OV05C10_stream_on_regs), &ret);
> > > +	if (ret)
> > > +		dev_err(&client->dev, "fail to start the streaming\n");
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int ov05c10_stop_streaming(struct ov05c10 *ov05c10)
> > > +{
> > > +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> > > +	int ret = 0;
> > > +
> > > +	cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_off_regs,
> > > +			    ARRAY_SIZE(mode_OV05C10_stream_off_regs), &ret);
> > > +	if (ret)
> > > +		dev_err(&client->dev, "fail to stop the streaming\n");
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static void ov05c10_sensor_power_set(struct ov05c10 *ov05c10, bool on)
> > > +{
> > > +	if (on) {
> > > +		gpiod_set_value(ov05c10->enable_gpio, 0);
> > > +		usleep_range(10, 20);
> > > +
> > > +		gpiod_set_value(ov05c10->enable_gpio, 1);
> > > +		usleep_range(1000, 2000);
> > 
> > According to the datasheet, ov05c10 needs at least 8 ms to work after its
> > XSHUTDN pin pulled to high. 1 ms maybe too quick, did you tested it? Or the
> > enable_gpio is actually not the XSHUTDN pin?
> > 
> > On Intel platforms, if the sensor driver controls the module power, ususally
> > it requires GPIO "reset", regulator "avdd" and clk "img_clk" assigned by
> > kernel driver intel_skl_int3472_discrete. I'm not sure whether any devices
> > on market using this power control solution, but if any, missing those
> > resources will stop them from powering-up cameras.
> 
> Please post a patch.
> 
> > > +	} else {
> > > +		gpiod_set_value(ov05c10->enable_gpio, 0);
> > > +		usleep_range(10, 20);
> > > +	}
> > > +}
> > > +
> > > +static int ov05c10_enable_streams(struct v4l2_subdev *sd,
> > > +				  struct v4l2_subdev_state *state, u32 pad,
> > > +				  u64 streams_mask)
> > > +{
> > > +	struct i2c_client *client = v4l2_get_subdevdata(sd);
> > > +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > > +	int ret = 0;
> > > +
> > > +	ret = pm_runtime_resume_and_get(&client->dev);
> > > +	if (ret < 0)
> > > +		return ret;
> > > +
> > > +	ov05c10->cur_page = -1;
> > > +
> > > +	ret = ov05c10_start_streaming(ov05c10);
> > > +	if (ret)
> > > +		goto err_rpm_put;
> > > +
> > > +	return 0;
> > > +
> > > +err_rpm_put:
> > > +	pm_runtime_put(&client->dev);
> > > +	return ret;
> > > +}
> > > +
> > > +static int ov05c10_disable_streams(struct v4l2_subdev *sd,
> > > +				   struct v4l2_subdev_state *state, u32 pad,
> > > +				   u64 streams_mask)
> > > +{
> > > +	struct i2c_client *client = v4l2_get_subdevdata(sd);
> > > +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > > +
> > > +	ov05c10_stop_streaming(ov05c10);
> > > +	pm_runtime_put(&client->dev);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static const struct v4l2_subdev_video_ops ov05c10_video_ops = {
> > > +	.s_stream = v4l2_subdev_s_stream_helper,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_pad_ops ov05c10_pad_ops = {
> > > +	.enum_mbus_code = ov05c10_enum_mbus_code,
> > > +	.get_fmt = v4l2_subdev_get_fmt,
> > > +	.set_fmt = ov05c10_set_pad_format,
> > > +	.enum_frame_size = ov05c10_enum_frame_size,
> > > +	.enable_streams = ov05c10_enable_streams,
> > > +	.disable_streams = ov05c10_disable_streams,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_ops ov05c10_subdev_ops = {
> > > +	.video = &ov05c10_video_ops,
> > > +	.pad = &ov05c10_pad_ops,
> > > +};
> > > +
> > > +static const struct media_entity_operations ov05c10_subdev_entity_ops = {
> > > +	.link_validate = v4l2_subdev_link_validate,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_internal_ops ov05c10_internal_ops = {
> > > +	.init_state = ov05c10_init_state,
> > > +};
> > > +
> > > +static int ov05c10_init_controls(struct ov05c10 *ov05c10)
> > > +{
> > > +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> > > +	const struct ov05c10_mode *mode = &supported_mode;
> > > +	struct v4l2_fwnode_device_properties props;
> > > +	struct v4l2_ctrl_handler *ctrl_hdlr;
> > > +	s64 pixel_rate_max;
> > > +	s64 exposure_max;
> > > +	s64 vblank_def;
> > > +	s64 vblank_min;
> > > +	u32 max_items;
> > > +	s64 hblank;
> > > +	int ret;
> > > +
> > > +	ret = v4l2_ctrl_handler_init(&ov05c10->ctrl_handler, 10);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	ctrl_hdlr = &ov05c10->ctrl_handler;
> > > +
> > > +	max_items = ARRAY_SIZE(ov05c10_link_freq_menu_items) - 1;
> > > +	ov05c10->link_freq =
> > > +		v4l2_ctrl_new_int_menu(ctrl_hdlr,
> > > +				       NULL,
> > > +				       V4L2_CID_LINK_FREQ,
> > > +				       max_items,
> > > +				       0,
> > > +				       ov05c10_link_freq_menu_items);
> > > +	if (ov05c10->link_freq)
> > > +		ov05c10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > > +
> > > +	pixel_rate_max =
> > > +		link_freq_to_pixel_rate(ov05c10_link_freq_menu_items[0],
> > > +					supported_mode.lanes);
> > > +	ov05c10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
> > > +						V4L2_CID_PIXEL_RATE,
> > > +						0, pixel_rate_max,
> > > +						1, pixel_rate_max);
> > > +
> > > +	vblank_def = mode->vts_def - mode->height;
> > > +	vblank_min = mode->vts_min - mode->height;
> > > +	ov05c10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
> > > +					    V4L2_CID_VBLANK,
> > > +					    vblank_min,
> > > +					    OV05C10_VTS_MAX - mode->height,
> > > +					    1, vblank_def);
> > > +
> > > +	hblank = (mode->hts > mode->width) ? (mode->hts - mode->width) : 0;
> > 
> > Here your hts uses 640 but width is 2888, which means hblank is set to 0
> > here. This is wrong, please fix your configuration.
> > 
> > > +	ov05c10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
> > > +					    V4L2_CID_HBLANK,
> > > +					    hblank, hblank, 1, hblank);
> > > +	if (ov05c10->hblank)
> > > +		ov05c10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > > +
> > > +	exposure_max = mode->vts_def - OV05C10_EXPOSURE_MAX_MARGIN;
> > > +	ov05c10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
> > > +					      V4L2_CID_EXPOSURE,
> > > +					      OV05C10_EXPOSURE_MIN,
> > > +					      exposure_max,
> > > +					      OV05C10_EXPOSURE_STEP,
> > > +					      exposure_max);
> > > +
> > > +	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
> > > +			  OV05C10_ANA_GAIN_MIN, OV05C10_ANA_GAIN_MAX,
> > > +			  OV05C10_ANA_GAIN_STEP, OV05C10_ANA_GAIN_DEFAULT);
> > > +
> > > +	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
> > > +			  OV05C10_DGTL_GAIN_MIN, OV05C10_DGTL_GAIN_MAX,
> > > +			  OV05C10_DGTL_GAIN_STEP, OV05C10_DGTL_GAIN_DEFAULT);
> > > +
> > > +	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov05c10_ctrl_ops,
> > > +				     V4L2_CID_TEST_PATTERN,
> > > +				     ARRAY_SIZE(ov05c10_test_pattern_menu) - 1,
> > > +				     0, 0, ov05c10_test_pattern_menu);
> > > +
> > > +	if (ctrl_hdlr->error) {
> > > +		ret = ctrl_hdlr->error;
> > > +		dev_err(&client->dev, "V4L2 control init failed (%d)\n", ret);
> > > +		goto err_hdl_free;
> > > +	}
> > > +
> > > +	ret = v4l2_fwnode_device_parse(&client->dev, &props);
> > > +	if (ret)
> > > +		goto err_hdl_free;
> > > +
> > > +	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov05c10_ctrl_ops,
> > > +					      &props);
> > > +	if (ret)
> > > +		goto err_hdl_free;
> > > +
> > > +	ov05c10->sd.ctrl_handler = ctrl_hdlr;
> > > +
> > > +	return 0;
> > > +
> > > +err_hdl_free:
> > > +	v4l2_ctrl_handler_free(ctrl_hdlr);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int ov05c10_parse_endpoint(struct device *dev,
> > > +				  struct fwnode_handle *fwnode)
> > > +{
> > > +	struct v4l2_fwnode_endpoint bus_cfg = {
> > > +		.bus_type = V4L2_MBUS_CSI2_DPHY
> > > +	};
> > > +	struct fwnode_handle *ep;
> > > +	unsigned long bitmap;
> > > +	int ret;
> > > +
> > > +	ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
> > > +	if (!ep) {
> > > +		dev_err(dev, "Failed to get next endpoint\n");
> > > +		return -ENXIO;
> > > +	}
> > > +
> > > +	ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
> > > +	fwnode_handle_put(ep);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	if (bus_cfg.bus.mipi_csi2.num_data_lanes != supported_mode.lanes) {
> > > +		dev_err(dev,
> > > +			"number of CSI2 data lanes %d is not supported\n",
> > > +			bus_cfg.bus.mipi_csi2.num_data_lanes);
> > > +		ret = -EINVAL;
> > > +		goto err_endpoint_free;
> > > +	}
> > > +
> > > +	ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
> > > +				       bus_cfg.nr_of_link_frequencies,
> > > +				       ov05c10_link_frequencies,
> > > +				       ARRAY_SIZE(ov05c10_link_frequencies),
> > > +				       &bitmap);
> > > +	if (ret)
> > > +		dev_err(dev, "v4l2_link_freq_to_bitmap fail with %d\n", ret);
> > > +err_endpoint_free:
> > > +	v4l2_fwnode_endpoint_free(&bus_cfg);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int ov05c10_probe(struct i2c_client *client)
> > > +{
> > > +	struct ov05c10 *ov05c10;
> > > +	u32 clkfreq;
> > > +	int ret;
> > > +
> > > +	ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), GFP_KERNEL);
> > > +	if (!ov05c10)
> > > +		return -ENOMEM;
> > > +
> > > +	struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> > > +
> > > +	ret = fwnode_property_read_u32(fwnode, "clock-frequency", &clkfreq);
> > 
> > Maybe it's better to separate this part fwnode and GPIO code into a
> > standalone function?
> 
> I don't mind, the probe() function isn't very long anyway.
> 
> > > +	if (ret)
> > > +		return  dev_err_probe(&client->dev, -EINVAL,
> > > +				      "fail to get clock freq\n");
> > > +	if (clkfreq != OV05C10_REF_CLK)
> > > +		return dev_err_probe(&client->dev, -EINVAL,
> > > +				     "fail invalid clock freq %u, %lu expected\n",
> > > +				     clkfreq, OV05C10_REF_CLK);
> > > +
> > > +	ret = ov05c10_parse_endpoint(&client->dev, fwnode);
> > > +	if (ret)
> > > +		return dev_err_probe(&client->dev, -EINVAL,
> > > +				     "fail to parse endpoint\n");
> > > +
> > > +	ov05c10->enable_gpio = devm_gpiod_get(&client->dev, "enable",
> > > +					      GPIOD_OUT_LOW);
> > > +	if (IS_ERR(ov05c10->enable_gpio))
> > > +		return dev_err_probe(&client->dev,
> > > +				     PTR_ERR(ov05c10->enable_gpio),
> > > +				     "fail to get enable gpio\n");
> > > +
> > > +	v4l2_i2c_subdev_init(&ov05c10->sd, client, &ov05c10_subdev_ops);
> > > +
> > > +	ov05c10->regmap = devm_cci_regmap_init_i2c(client, 8);
> > > +	if (IS_ERR(ov05c10->regmap))
> > > +		return dev_err_probe(&client->dev, PTR_ERR(ov05c10->regmap),
> > > +				     "fail to init cci\n");
> > > +
> > > +	ov05c10->cur_page = -1;
> > > +
> > > +	ret = ov05c10_init_controls(ov05c10);
> > > +	if (ret)
> > > +		return dev_err_probe(&client->dev, ret, "fail to init ctl\n");
> > > +
> > > +	ov05c10->sd.internal_ops = &ov05c10_internal_ops;
> > > +	ov05c10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > > +	ov05c10->sd.entity.ops = &ov05c10_subdev_entity_ops;
> > > +	ov05c10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > > +
> > > +	ov05c10->pad.flags = MEDIA_PAD_FL_SOURCE;
> > > +
> > > +	ret = media_entity_pads_init(&ov05c10->sd.entity, NUM_OF_PADS,
> > > +				     &ov05c10->pad);
> > > +	if (ret)
> > > +		goto err_hdl_free;
> > > +
> > > +	ret = v4l2_subdev_init_finalize(&ov05c10->sd);
> > > +	if (ret < 0)
> > > +		goto err_media_entity_cleanup;
> > > +
> > > +	ret = v4l2_async_register_subdev_sensor(&ov05c10->sd);
> > > +	if (ret)
> > > +		goto err_media_entity_cleanup;
> > > +
> > > +	pm_runtime_set_active(&client->dev);
> > > +	pm_runtime_enable(&client->dev);
> > > +	pm_runtime_idle(&client->dev);
> > > +	pm_runtime_set_autosuspend_delay(&client->dev, 1000);
> > > +	pm_runtime_use_autosuspend(&client->dev);
> > > +	return 0;
> > > +
> > > +err_media_entity_cleanup:
> > > +	media_entity_cleanup(&ov05c10->sd.entity);
> > > +
> > > +err_hdl_free:
> > > +	v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static void ov05c10_remove(struct i2c_client *client)
> > > +{
> > > +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > > +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > > +
> > > +	v4l2_async_unregister_subdev(sd);
> > > +	media_entity_cleanup(&sd->entity);
> > > +	v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
> > > +
> > > +	pm_runtime_disable(&client->dev);
> > > +	pm_runtime_set_suspended(&client->dev);
> > > +}
> > > +
> > > +static int ov05c10_runtime_resume(struct device *dev)
> > > +{
> > > +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> > > +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > > +
> > > +	ov05c10_sensor_power_set(ov05c10, true);
> > > +	return 0;
> > > +}
> > > +
> > > +static int ov05c10_runtime_suspend(struct device *dev)
> > > +{
> > > +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> > > +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> > > +
> > > +	ov05c10_sensor_power_set(ov05c10, false);
> > > +	return 0;
> > > +}
> > > +
> > > +static DEFINE_RUNTIME_DEV_PM_OPS(ov05c10_pm_ops, ov05c10_runtime_suspend,
> > > +				 ov05c10_runtime_resume, NULL);
> > > +
> > > +static const struct i2c_device_id ov05c10_i2c_ids[] = {
> > > +	{"ov05c10", 0 },
> > > +	{ }
> > > +};
> > > +MODULE_DEVICE_TABLE(i2c, ov05c10_i2c_ids);
> > > +
> > > +static struct i2c_driver ov05c10_i2c_driver = {
> > > +	.driver = {
> > > +		.name = DRV_NAME,
> > > +		.pm = pm_ptr(&ov05c10_pm_ops),
> > > +	},
> > > +	.id_table = ov05c10_i2c_ids,
> > > +	.probe = ov05c10_probe,
> > > +	.remove = ov05c10_remove,
> > > +};
> > > +
> > > +module_i2c_driver(ov05c10_i2c_driver);
> > > +
> > > +MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
> > > +MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@amd.com>");
> > > +MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
> > > +MODULE_DESCRIPTION("OmniVision OV05C1010 sensor driver");
> > 
> > OV05C10
> > 
> > > +MODULE_LICENSE("GPL");
> > 
> > Hi Sakari,
> > 
> > Seems there are already several camera sensors using page-based registers.
> > Is it a good idea to add page support in CCI interface?
> 
> Sounds like a good idea as such but I'm not sure how common this really is,
> I think I've seen a few Omnivision sensors doing this. If implemented, I
> think it would be nice if the page could be encoded in the register address
> which V4L2 CCI would store and switch page if needed only. This would
> require serialising accesses, too. There's some room in CCI register raw
> value space so this could be done without even changing that, say, with
> 8-bit page and 8-bit register address.

Ack. I've worked on a driver for the AP1302 external ISP, which also
uses pages registers. The full address space spans 32 bits though, but
fortunately the driver doesn't need to access anything above 0x00ffffff.

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-09 19:42 [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver Pratap Nirujogi
  2025-06-13  4:55 ` Hao Yao
@ 2025-06-15  0:09 ` Laurent Pinchart
  2025-06-16 22:49   ` Nirujogi, Pratap
  1 sibling, 1 reply; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-15  0:09 UTC (permalink / raw)
  To: Pratap Nirujogi
  Cc: mchehab, sakari.ailus, hverkuil, bryan.odonoghue, krzk,
	dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen, linux-media,
	linux-kernel, benjamin.chan, bin.du, grosikop, king.li, dantony,
	vengutta

Hi Pratap,

Thank you for the patch.

On Mon, Jun 09, 2025 at 03:42:22PM -0400, Pratap Nirujogi wrote:
> Add driver for OmniVision 5.2M OV05C10 sensor. This driver
> supports only the full size normal 2888x1808@30fps 2-lane
> sensor profile.
> 
> Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> Co-developed-by: Bin Du <bin.du@amd.com>
> Signed-off-by: Bin Du <bin.du@amd.com>
> Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
> ---
> Changes v2 -> v3:
> 
> * Update "refclk" property variable as "clock-frequency".
> * Update sensor GPIO connector id name.
> * Fix sensor v4l2 compliance issue.
> * Fix license info.
> * Address review comments.
> 
>  MAINTAINERS                 |    8 +
>  drivers/media/i2c/Kconfig   |   10 +
>  drivers/media/i2c/Makefile  |    1 +
>  drivers/media/i2c/ov05c10.c | 1061 +++++++++++++++++++++++++++++++++++
>  4 files changed, 1080 insertions(+)
>  create mode 100644 drivers/media/i2c/ov05c10.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a92290fffa16..caca25d00bf2 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18303,6 +18303,14 @@ T:	git git://linuxtv.org/media.git
>  F:	Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml
>  F:	drivers/media/i2c/ov02e10.c
>  
> +OMNIVISION OV05C10 SENSOR DRIVER
> +M:	Nirujogi Pratap <pratap.nirujogi@amd.com>
> +M:	Bin Du <bin.du@amd.com>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained
> +T:	git git://linuxtv.org/media.git
> +F:	drivers/media/i2c/ov05c10.c
> +
>  OMNIVISION OV08D10 SENSOR DRIVER
>  M:	Jimmy Su <jimmy.su@intel.com>
>  L:	linux-media@vger.kernel.org
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index e68202954a8f..1662fb29d75c 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -377,6 +377,16 @@ config VIDEO_OV02C10
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called ov02c10.
>  
> +config VIDEO_OV05C10
> +	tristate "OmniVision OV05C10 sensor support"
> +	select V4L2_CCI_I2C
> +	help
> +	  This is a Video4Linux2 sensor driver for the OmniVision
> +	  OV05C10 camera.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called OV05C10.
> +
>  config VIDEO_OV08D10
>          tristate "OmniVision OV08D10 sensor support"
>          help
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 5873d29433ee..b4a1d721a7f2 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -85,6 +85,7 @@ obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
>  obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
>  obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o
>  obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o
> +obj-$(CONFIG_VIDEO_OV05C10) += ov05c10.o
>  obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
>  obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
>  obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
> diff --git a/drivers/media/i2c/ov05c10.c b/drivers/media/i2c/ov05c10.c
> new file mode 100644
> index 000000000000..9a1e493c4073
> --- /dev/null
> +++ b/drivers/media/i2c/ov05c10.c
> @@ -0,0 +1,1061 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +// Copyright (C) 2025 Advanced Micro Devices, Inc.
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/units.h>
> +#include <media/v4l2-cci.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +
> +#define DRV_NAME			"ov05c10"
> +#define OV05C10_REF_CLK			(24 * HZ_PER_MHZ)
> +
> +#define MODE_WIDTH  2888
> +#define MODE_HEIGHT 1808
> +
> +#define PAGE_NUM_MASK			0xff000000
> +#define PAGE_NUM_SHIFT			24
> +#define REG_ADDR_MASK			0x00ffffff
> +
> +#define OV05C10_SYSCTL_PAGE		(0 << PAGE_NUM_SHIFT)
> +#define OV05C10_CISCTL_PAGE		(1 << PAGE_NUM_SHIFT)
> +#define OV05C10_ISPCTL_PAGE		(4 << PAGE_NUM_SHIFT)
> +
> +/* Chip ID */
> +#define OV05C10_REG_CHIP_ID		(CCI_REG24(0x00) | OV05C10_SYSCTL_PAGE)
> +#define OV05C10_CHIP_ID			0x43055610
> +
> +/* Control registers */
> +#define OV05C10_REG_TRIGGER		(CCI_REG8(0x01) | OV05C10_CISCTL_PAGE)
> +#define OV05C_REG_TRIGGER_START		BIT(0)
> +
> +/* Exposure control */
> +#define OV05C10_REG_EXPOSURE		(CCI_REG24(0x02) | OV05C10_CISCTL_PAGE)
> +#define OV05C10_EXPOSURE_MAX_MARGIN	33
> +#define OV05C10_EXPOSURE_MIN		4
> +#define OV05C10_EXPOSURE_STEP		1
> +#define OV05C10_EXPOSURE_DEFAULT	0x40
> +
> +/* V_TIMING internal */
> +#define OV05C10_REG_VTS			(CCI_REG16(0x05) | OV05C10_CISCTL_PAGE)
> +#define OV05C10_VTS_30FPS		1860
> +#define OV05C10_VTS_MAX			0x7fff
> +
> +/* Test Pattern Control */
> +#define OV05C10_REG_TEST_PATTERN	(CCI_REG8(0x12) | OV05C10_ISPCTL_PAGE)
> +#define OV05C10_TEST_PATTERN_ENABLE	BIT(0)
> +#define OV05C10_REG_TEST_PATTERN_CTL	(CCI_REG8(0xf3) | OV05C10_ISPCTL_PAGE)
> +#define OV05C10_REG_TEST_PATTERN_XXX	BIT(0)

What's XXX ?

> +
> +/* Digital gain control */
> +#define OV05C10_REG_DGTL_GAIN_H		(CCI_REG8(0x21) | OV05C10_CISCTL_PAGE)
> +#define OV05C10_REG_DGTL_GAIN_L		(CCI_REG8(0x22) | OV05C10_CISCTL_PAGE)

Can you make this a 16-bit register ?

#define OV05C10_REG_DGTL_GAIN		(CCI_REG16(0x21) | OV05C10_CISCTL_PAGE)

> +
> +#define OV05C10_DGTL_GAIN_MIN		0x40
> +#define OV05C10_DGTL_GAIN_MAX		0xff
> +#define OV05C10_DGTL_GAIN_DEFAULT	0x40
> +#define OV05C10_DGTL_GAIN_STEP		1
> +
> +#define OV05C10_DGTL_GAIN_L_MASK	0xff
> +#define OV05C10_DGTL_GAIN_H_SHIFT	8
> +#define OV05C10_DGTL_GAIN_H_MASK	0xff00
> +
> +/* Analog gain control */
> +#define OV05C10_REG_ANALOG_GAIN		(CCI_REG8(0x24) | OV05C10_CISCTL_PAGE)
> +#define OV05C10_ANA_GAIN_MIN		0x80
> +#define OV05C10_ANA_GAIN_MAX		0x07c0
> +#define OV05C10_ANA_GAIN_STEP		1
> +#define OV05C10_ANA_GAIN_DEFAULT	0x80
> +
> +/* H TIMING internal */
> +#define OV05C10_REG_HTS			(CCI_REG16(0x37) | OV05C10_CISCTL_PAGE)
> +#define OV05C10_HTS_30FPS		0x0280
> +
> +/* Page selection */
> +#define OV05C10_REG_PAGE_CTL		CCI_REG8(0xfd)
> +
> +#define NUM_OF_PADS 1

OV05C10_NUM_OF_PADS

> +
> +#define OV05C10_GET_PAGE_NUM(reg)	(((reg) & PAGE_NUM_MASK) >>\
> +					 PAGE_NUM_SHIFT)
> +#define OV05C10_GET_REG_ADDR(reg)	((reg) & REG_ADDR_MASK)
> +
> +enum {
> +	OV05C10_LINK_FREQ_900MHZ_INDEX,
> +};
> +
> +struct ov05c10_reg_list {
> +	u32 num_of_regs;
> +	const struct cci_reg_sequence *regs;
> +};
> +
> +/* Mode : resolution and related config&values */
> +struct ov05c10_mode {
> +	/* Frame width */
> +	u32 width;
> +	/* Frame height */
> +	u32 height;
> +	/* number of lanes */
> +	u32 lanes;
> +
> +	/* V-timing */
> +	u32 vts_def;
> +	u32 vts_min;
> +
> +	/* HTS */
> +	u32 hts;
> +
> +	/* Index of Link frequency config to be used */
> +	u32 link_freq_index;
> +
> +	/* Default register values */
> +	struct ov05c10_reg_list reg_list;
> +};
> +
> +static const s64 ov05c10_link_frequencies[] = {
> +	925 * HZ_PER_MHZ,
> +};
> +
> +/* 2888x1808 30fps, 1800mbps, 2lane, 24mhz */
> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
> +	{ CCI_REG8(0xfd),  0x00 },
> +	{ CCI_REG8(0x20),  0x00 },
> +	{ CCI_REG8(0xfd),  0x00 },
> +	{ CCI_REG8(0x20),  0x0b },
> +	{ CCI_REG8(0xc1),  0x09 },
> +	{ CCI_REG8(0x21),  0x06 },
> +	{ CCI_REG8(0x14),  0x78 },
> +	{ CCI_REG8(0xe7),  0x03 },
> +	{ CCI_REG8(0xe7),  0x00 },
> +	{ CCI_REG8(0x21),  0x00 },
> +	{ CCI_REG8(0xfd),  0x01 },
> +	{ CCI_REG8(0x03),  0x00 },
> +	{ CCI_REG8(0x04),  0x06 },
> +	{ CCI_REG8(0x05),  0x07 },
> +	{ CCI_REG8(0x06),  0x44 },
> +	{ CCI_REG8(0x07),  0x08 },
> +	{ CCI_REG8(0x1b),  0x01 },
> +	{ CCI_REG8(0x24),  0xff },
> +	{ CCI_REG8(0x32),  0x03 },
> +	{ CCI_REG8(0x42),  0x5d },
> +	{ CCI_REG8(0x43),  0x08 },
> +	{ CCI_REG8(0x44),  0x81 },
> +	{ CCI_REG8(0x46),  0x5f },
> +	{ CCI_REG8(0x48),  0x18 },
> +	{ CCI_REG8(0x49),  0x04 },
> +	{ CCI_REG8(0x5c),  0x18 },
> +	{ CCI_REG8(0x5e),  0x13 },
> +	{ CCI_REG8(0x70),  0x15 },
> +	{ CCI_REG8(0x77),  0x35 },
> +	{ CCI_REG8(0x79),  0x00 },
> +	{ CCI_REG8(0x7b),  0x08 },
> +	{ CCI_REG8(0x7d),  0x08 },
> +	{ CCI_REG8(0x7e),  0x08 },
> +	{ CCI_REG8(0x7f),  0x08 },
> +	{ CCI_REG8(0x90),  0x37 },
> +	{ CCI_REG8(0x91),  0x05 },
> +	{ CCI_REG8(0x92),  0x18 },
> +	{ CCI_REG8(0x93),  0x27 },
> +	{ CCI_REG8(0x94),  0x05 },
> +	{ CCI_REG8(0x95),  0x38 },
> +	{ CCI_REG8(0x9b),  0x00 },
> +	{ CCI_REG8(0x9c),  0x06 },
> +	{ CCI_REG8(0x9d),  0x28 },
> +	{ CCI_REG8(0x9e),  0x06 },
> +	{ CCI_REG8(0xb2),  0x0f },
> +	{ CCI_REG8(0xb3),  0x29 },
> +	{ CCI_REG8(0xbf),  0x3c },
> +	{ CCI_REG8(0xc2),  0x04 },
> +	{ CCI_REG8(0xc4),  0x00 },
> +	{ CCI_REG8(0xca),  0x20 },
> +	{ CCI_REG8(0xcb),  0x20 },
> +	{ CCI_REG8(0xcc),  0x28 },
> +	{ CCI_REG8(0xcd),  0x28 },
> +	{ CCI_REG8(0xce),  0x20 },
> +	{ CCI_REG8(0xcf),  0x20 },
> +	{ CCI_REG8(0xd0),  0x2a },
> +	{ CCI_REG8(0xd1),  0x2a },
> +	{ CCI_REG8(0xfd),  0x0f },
> +	{ CCI_REG8(0x00),  0x00 },
> +	{ CCI_REG8(0x01),  0xa0 },
> +	{ CCI_REG8(0x02),  0x48 },
> +	{ CCI_REG8(0x07),  0x8f },
> +	{ CCI_REG8(0x08),  0x70 },
> +	{ CCI_REG8(0x09),  0x01 },
> +	{ CCI_REG8(0x0b),  0x40 },
> +	{ CCI_REG8(0x0d),  0x07 },
> +	{ CCI_REG8(0x11),  0x33 },
> +	{ CCI_REG8(0x12),  0x77 },
> +	{ CCI_REG8(0x13),  0x66 },
> +	{ CCI_REG8(0x14),  0x65 },
> +	{ CCI_REG8(0x15),  0x37 },
> +	{ CCI_REG8(0x16),  0xbf },
> +	{ CCI_REG8(0x17),  0xff },
> +	{ CCI_REG8(0x18),  0xff },
> +	{ CCI_REG8(0x19),  0x12 },
> +	{ CCI_REG8(0x1a),  0x10 },
> +	{ CCI_REG8(0x1c),  0x77 },
> +	{ CCI_REG8(0x1d),  0x77 },
> +	{ CCI_REG8(0x20),  0x0f },
> +	{ CCI_REG8(0x21),  0x0f },
> +	{ CCI_REG8(0x22),  0x0f },
> +	{ CCI_REG8(0x23),  0x0f },
> +	{ CCI_REG8(0x2b),  0x20 },
> +	{ CCI_REG8(0x2c),  0x20 },
> +	{ CCI_REG8(0x2d),  0x04 },
> +	{ CCI_REG8(0xfd),  0x03 },
> +	{ CCI_REG8(0x9d),  0x0f },
> +	{ CCI_REG8(0x9f),  0x40 },
> +	{ CCI_REG8(0xfd),  0x00 },
> +	{ CCI_REG8(0x20),  0x1b },
> +	{ CCI_REG8(0xfd),  0x04 },
> +	{ CCI_REG8(0x19),  0x60 },
> +	{ CCI_REG8(0xfd),  0x02 },
> +	{ CCI_REG8(0x75),  0x05 },
> +	{ CCI_REG8(0x7f),  0x06 },
> +	{ CCI_REG8(0x9a),  0x03 },
> +	{ CCI_REG8(0xa2),  0x07 },
> +	{ CCI_REG8(0xa3),  0x10 },
> +	{ CCI_REG8(0xa5),  0x02 },
> +	{ CCI_REG8(0xa6),  0x0b },
> +	{ CCI_REG8(0xa7),  0x48 },
> +	{ CCI_REG8(0xfd),  0x07 },
> +	{ CCI_REG8(0x42),  0x00 },
> +	{ CCI_REG8(0x43),  0x80 },
> +	{ CCI_REG8(0x44),  0x00 },
> +	{ CCI_REG8(0x45),  0x80 },
> +	{ CCI_REG8(0x46),  0x00 },
> +	{ CCI_REG8(0x47),  0x80 },
> +	{ CCI_REG8(0x48),  0x00 },
> +	{ CCI_REG8(0x49),  0x80 },
> +	{ CCI_REG8(0x00),  0xf7 },
> +	{ CCI_REG8(0xfd),  0x00 },
> +	{ CCI_REG8(0xe7),  0x03 },
> +	{ CCI_REG8(0xe7),  0x00 },
> +	{ CCI_REG8(0xfd),  0x00 },
> +	{ CCI_REG8(0x93),  0x18 },
> +	{ CCI_REG8(0x94),  0xff },
> +	{ CCI_REG8(0x95),  0xbd },
> +	{ CCI_REG8(0x96),  0x1a },
> +	{ CCI_REG8(0x98),  0x04 },
> +	{ CCI_REG8(0x99),  0x08 },
> +	{ CCI_REG8(0x9b),  0x10 },
> +	{ CCI_REG8(0x9c),  0x3f },
> +	{ CCI_REG8(0xa1),  0x05 },
> +	{ CCI_REG8(0xa4),  0x2f },
> +	{ CCI_REG8(0xc0),  0x0c },
> +	{ CCI_REG8(0xc1),  0x08 },
> +	{ CCI_REG8(0xc2),  0x00 },
> +	{ CCI_REG8(0xb6),  0x20 },
> +	{ CCI_REG8(0xbb),  0x80 },
> +	{ CCI_REG8(0xfd),  0x00 },
> +	{ CCI_REG8(0xa0),  0x01 },
> +	{ CCI_REG8(0xfd),  0x01 },
> +};
> +
> +static const struct cci_reg_sequence mode_OV05C10_stream_on_regs[] = {
> +	{ CCI_REG8(0xfd), 0x01 },
> +	{ CCI_REG8(0x33), 0x03 },
> +	{ CCI_REG8(0x01), 0x02 },
> +	{ CCI_REG8(0xfd), 0x00 },
> +	{ CCI_REG8(0x20), 0x1f },
> +	{ CCI_REG8(0xfd), 0x01 },
> +};
> +
> +static const struct cci_reg_sequence mode_OV05C10_stream_off_regs[] = {
> +	{ CCI_REG8(0xfd), 0x00 },
> +	{ CCI_REG8(0x20), 0x5b },
> +	{ CCI_REG8(0xfd), 0x01 },
> +	{ CCI_REG8(0x33), 0x02 },
> +	{ CCI_REG8(0x01), 0x02 },
> +};

Add named macros for the registers you set when starting or stopping
streaming, as well as macros for the register fields.

> +
> +static const char * const ov05c10_test_pattern_menu[] = {
> +	"Disabled",
> +	"Vertical Color Bar Type 1",
> +	"Vertical Color Bar Type 2",
> +	"Vertical Color Bar Type 3",
> +	"Vertical Color Bar Type 4"
> +};

Move this just above ov05c10_init_controls().

> +
> +/* Configurations for supported link frequencies */
> +#define OV05C10_LINK_FREQ_900MHZ	(900 * HZ_PER_MHZ)
> +
> +/* Number of lanes supported */
> +#define OV05C10_DATA_LANES		2
> +
> +/* Bits per sample of sensor output */
> +#define OV05C10_BITS_PER_SAMPLE		10

Move the macros above, with the other ones.

> +
> +/*
> + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
> + * data rate => double data rate; number of lanes => 2; bits per pixel => 10
> + */
> +static u64 link_freq_to_pixel_rate(u64 f, u32 lane_nr)
> +{
> +	f *= 2 * lane_nr;
> +	do_div(f, OV05C10_BITS_PER_SAMPLE);
> +
> +	return f;
> +}
> +
> +/* Menu items for LINK_FREQ V4L2 control */
> +static const s64 ov05c10_link_freq_menu_items[] = {
> +	OV05C10_LINK_FREQ_900MHZ,
> +};
> +
> +/* Mode configs, currently, only support 1 mode */
> +static const struct ov05c10_mode supported_mode = {
> +	.width = MODE_WIDTH,
> +	.height = MODE_HEIGHT,
> +	.vts_def = OV05C10_VTS_30FPS,
> +	.vts_min = OV05C10_VTS_30FPS,
> +	.hts = 640,
> +	.lanes = 2,
> +	.reg_list = {
> +		.num_of_regs = ARRAY_SIZE(ov05c10_2888x1808_regs),
> +		.regs = ov05c10_2888x1808_regs,
> +	},
> +	.link_freq_index = OV05C10_LINK_FREQ_900MHZ_INDEX,
> +};
> +
> +struct ov05c10 {
> +	struct v4l2_subdev sd;
> +	struct media_pad pad;
> +
> +	/* V4L2 control handler */
> +	struct v4l2_ctrl_handler ctrl_handler;
> +
> +	/* V4L2 Controls */
> +	struct v4l2_ctrl *link_freq;
> +	struct v4l2_ctrl *pixel_rate;
> +	struct v4l2_ctrl *vblank;
> +	struct v4l2_ctrl *hblank;
> +	struct v4l2_ctrl *exposure;
> +
> +	struct regmap *regmap;
> +
> +	/* gpio descriptor */
> +	struct gpio_desc *enable_gpio;
> +
> +	/* Current page for sensor register control */
> +	int cur_page;
> +};
> +
> +#define to_ov05c10(_sd)	container_of(_sd, struct ov05c10, sd)

We try to use inline functions for this, to improve type safety.

static inline struct ov05c10 *to_ov05c10(struct v4l2_subdev *_sd)
{
	return container_of(_sd, struct ov05c10, sd);
}

> +
> +static int ov05c10_init_state(struct v4l2_subdev *sd,
> +			      struct v4l2_subdev_state *sd_state)

Move this function below with the other subdev ops, after
ov05c10_disable_streams().

> +{
> +	struct v4l2_mbus_framefmt *frame_fmt;
> +	struct v4l2_subdev_format fmt = {

static const

> +		.which = V4L2_SUBDEV_FORMAT_TRY,
> +		.format = {
> +			.width = MODE_WIDTH,
> +			.height = MODE_HEIGHT,
> +			.code = MEDIA_BUS_FMT_SGRBG10_1X10,
> +			.field = V4L2_FIELD_NONE,

You also need to set the colorspace fields.

> +		}
> +	};
> +
> +	frame_fmt = v4l2_subdev_state_get_format(sd_state, 0);
> +	*frame_fmt = fmt.format;
> +	return 0;
> +}
> +
> +static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page, int *err)
> +{
> +	int ret = 0;
> +
> +	if (err && *err)
> +		return *err;

This function is never called with *err != 0. I think you can simplify
it by dropping the err argument:

static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page)
{
	int ret;

	if (page == ov05c10->cur_page)
		return 0;

	ret = cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, NULL);
	if (ret)
		return ret;

	ov05c10->cur_page = page;

	return 0;
}

> +
> +	if (page != ov05c10->cur_page) {
> +		cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, &ret);
> +		if (!ret)
> +			ov05c10->cur_page = page;
> +	}
> +
> +	if (err)
> +		*err = ret;
> +
> +	return ret;
> +}
> +
> +/* refer to the implementation of cci_read */
> +static int ov05c10_reg_read(struct ov05c10 *ov05c10, u32 reg,
> +			    u64 *val, int *err)
> +{
> +	u32 page;
> +	u32 addr;
> +	int ret = 0;
> +
> +	if (err && *err)
> +		return *err;
> +
> +	page = OV05C10_GET_PAGE_NUM(reg);
> +	addr = OV05C10_GET_REG_ADDR(reg);
> +	ov05c10_switch_page(ov05c10, page, &ret);
> +	cci_read(ov05c10->regmap, addr, val, &ret);
> +	if (err)
> +		*err = ret;
> +
> +	return ret;
> +}
> +
> +/* refer to the implementation of cci_write */
> +static int ov05c10_reg_write(struct ov05c10 *ov05c10, u32 reg,
> +			     u64 val, int *err)
> +{
> +	u32 page;
> +	u32 addr;
> +	int ret = 0;
> +
> +	if (err && *err)
> +		return *err;
> +
> +	page = OV05C10_GET_PAGE_NUM(reg);
> +	addr = OV05C10_GET_REG_ADDR(reg);
> +	ov05c10_switch_page(ov05c10, page, &ret);
> +	cci_write(ov05c10->regmap, addr, val, &ret);
> +	if (err)
> +		*err = ret;
> +
> +	return ret;
> +}
> +
> +static int ov05c10_update_vblank(struct ov05c10 *ov05c10, u32 vblank)
> +{
> +	const struct ov05c10_mode *mode = &supported_mode;

To prepare for making the sensor freely configurable, let's not access
modes here. You can get the data you need from the format retrieved from
the active state.

> +	u64 val;

u32 is enough. Same below.

> +	int ret = 0;
> +
> +	val = mode->height + vblank;
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_VTS, val, &ret);
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> +			  OV05C_REG_TRIGGER_START, &ret);

Does the OV05C10_REG_TRIGGER register need to be set after any change to
controls ? If so you could move it to the end of the ov05c10_set_ctrl()
function.

> +
> +	return ret;
> +}
> +
> +static int ov05c10_update_exposure(struct ov05c10 *ov05c10, u32 exposure)
> +{
> +	int ret = 0;
> +
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_EXPOSURE, exposure, &ret);
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> +			  OV05C_REG_TRIGGER_START, &ret);
> +
> +	return ret;
> +}
> +
> +static int ov05c10_update_analog_gain(struct ov05c10 *ov05c10, u32 a_gain)
> +{
> +	int ret = 0;
> +
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_ANALOG_GAIN, a_gain, &ret);
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> +			  OV05C_REG_TRIGGER_START, &ret);
> +
> +	return ret;
> +}
> +
> +static int ov05c10_update_digital_gain(struct ov05c10 *ov05c10, u32 d_gain)
> +{
> +	u64 val;
> +	int ret = 0;
> +
> +	val = d_gain & OV05C10_DGTL_GAIN_L_MASK;
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_L, val, &ret);
> +
> +	val = (d_gain & OV05C10_DGTL_GAIN_H_MASK) >> OV05C10_DGTL_GAIN_H_SHIFT;
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_H, val, &ret);
> +
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> +			  OV05C_REG_TRIGGER_START, &ret);
> +
> +	return ret;
> +}
> +
> +static int ov05c10_enable_test_pattern(struct ov05c10 *ov05c10, u32 pattern)
> +{
> +	u64 val;
> +	int ret = 0;
> +
> +	if (pattern) {
> +		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
> +				 &val, &ret);
> +		ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
> +				  val | OV05C10_REG_TEST_PATTERN_XXX, &ret);
> +		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
> +		val |= OV05C10_TEST_PATTERN_ENABLE;
> +	} else {
> +		ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
> +		val &= ~OV05C10_TEST_PATTERN_ENABLE;
> +	}
> +
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN, val, &ret);
> +	ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
> +			  OV05C_REG_TRIGGER_START, &ret);
> +
> +	return ret;
> +}
> +
> +static int ov05c10_set_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct ov05c10 *ov05c10 = container_of(ctrl->handler,
> +					       struct ov05c10, ctrl_handler);
> +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);

You use client solely to access client->dev. Store a struct device *dev
in struct ov05c10, and access it below. Same in the rest of the driver.

> +	const struct ov05c10_mode *mode = &supported_mode;

Here too you can get the data you need from the format retrieved from
the active state.

> +	s64 max;
> +	int ret = 0;
> +
> +	/* Propagate change of current control to all related controls */
> +	if (ctrl->id == V4L2_CID_VBLANK) {
> +		s64 cur_exp = ov05c10->exposure->cur.val;
> +
> +		/* Update max exposure while meeting expected vblanking */
> +		max = mode->height + ctrl->val - OV05C10_EXPOSURE_MAX_MARGIN;
> +		cur_exp = clamp(cur_exp, ov05c10->exposure->minimum, max);
> +		ret = __v4l2_ctrl_modify_range(ov05c10->exposure,
> +					       ov05c10->exposure->minimum,
> +					       max, ov05c10->exposure->step,
> +					       cur_exp);
> +		if (!ret)
> +			return ret;
> +	}
> +
> +	/*
> +	 * Applying V4L2 control value only happens
> +	 * when power is up for streaming
> +	 */
> +	if (!pm_runtime_get_if_in_use(&client->dev))
> +		return 0;
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_ANALOGUE_GAIN:
> +		ret = ov05c10_update_analog_gain(ov05c10, ctrl->val);
> +		break;
> +	case V4L2_CID_DIGITAL_GAIN:
> +		ret = ov05c10_update_digital_gain(ov05c10, ctrl->val);
> +		break;
> +	case V4L2_CID_EXPOSURE:
> +		ret = ov05c10_update_exposure(ov05c10, ctrl->val);
> +		break;
> +	case V4L2_CID_VBLANK:
> +		ret = ov05c10_update_vblank(ov05c10, ctrl->val);
> +		break;
> +	case V4L2_CID_TEST_PATTERN:
> +		ret = ov05c10_enable_test_pattern(ov05c10, ctrl->val);
> +		break;
> +	default:
> +		ret = -ENOTTY;
> +		dev_err(&client->dev,
> +			"ctrl(id:0x%x,val:0x%x) is not handled\n",
> +			ctrl->id, ctrl->val);
> +		break;
> +	}
> +
> +	pm_runtime_put(&client->dev);

Use the autosuspend variant (and unless the pm_runtime_mark_last_busy()
has been folded in - Sakari has submitted patches - you will need to
call it too).

> +
> +	return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops ov05c10_ctrl_ops = {
> +	.s_ctrl = ov05c10_set_ctrl,
> +};
> +
> +static int ov05c10_enum_mbus_code(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *sd_state,
> +				  struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	/* Only one bayer order(GRBG) is supported */
> +	if (code->index > 0)
> +		return -EINVAL;
> +
> +	code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
> +
> +	return 0;
> +}
> +
> +static int ov05c10_enum_frame_size(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *sd_state,
> +				   struct v4l2_subdev_frame_size_enum *fse)
> +{
> +	/* ov05c10 driver currently only supports 1 mode*/
> +	if (fse->index != 0)
> +		return -EINVAL;
> +
> +	if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
> +		return -EINVAL;
> +
> +	fse->min_width = supported_mode.width;
> +	fse->max_width = fse->min_width;
> +	fse->min_height = supported_mode.height;
> +	fse->max_height = fse->min_height;
> +
> +	return 0;
> +}
> +
> +static void ov05c10_update_pad_format(const struct ov05c10_mode *mode,
> +				      struct v4l2_subdev_format *fmt)
> +{
> +	fmt->format.width = mode->width;
> +	fmt->format.height = mode->height;
> +	fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
> +	fmt->format.field = V4L2_FIELD_NONE;

Move this code to the single caller below.

You also need to handle the colorspace fields.

> +}
> +
> +static int ov05c10_set_pad_format(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *sd_state,
> +				  struct v4l2_subdev_format *fmt)
> +{
> +	struct v4l2_mbus_framefmt *framefmt;
> +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> +	const struct ov05c10_mode *mode;
> +	s32 vblank_def;
> +	s32 vblank_min;
> +	s64 pixel_rate;
> +	s64 link_freq;
> +	s64 h_blank;
> +
> +	/* Only one raw bayer(GRBG) order is supported */
> +	if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10)
> +		fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;

Just do

	/* Only one raw bayer(GRBG) order is supported */
	fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;

unconditionally.

> +
> +	mode = &supported_mode;
> +	ov05c10_update_pad_format(mode, fmt);
> +	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> +		framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
> +		*framefmt = fmt->format;

This needs to be done for the active state too.

> +	} else {
> +		__v4l2_ctrl_s_ctrl(ov05c10->link_freq, mode->link_freq_index);
> +		link_freq = ov05c10_link_freq_menu_items[mode->link_freq_index];
> +		pixel_rate = link_freq_to_pixel_rate(link_freq,
> +						     mode->lanes);
> +		__v4l2_ctrl_s_ctrl_int64(ov05c10->pixel_rate, pixel_rate);
> +
> +		/* Update limits and set FPS to default */
> +		vblank_def = mode->vts_def - mode->height;
> +		vblank_min = mode->vts_min - mode->height;
> +		__v4l2_ctrl_modify_range(ov05c10->vblank, vblank_min,
> +					 OV05C10_VTS_MAX - mode->height,
> +					 1, vblank_def);
> +		__v4l2_ctrl_s_ctrl(ov05c10->vblank, vblank_def);
> +		h_blank = mode->hts;
> +		__v4l2_ctrl_modify_range(ov05c10->hblank, h_blank,
> +					 h_blank, 1, h_blank);
> +	}
> +
> +	return 0;
> +}
> +
> +static int ov05c10_start_streaming(struct ov05c10 *ov05c10)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> +	const struct ov05c10_mode *mode = &supported_mode;
> +	const struct ov05c10_reg_list *reg_list;
> +	int ret = 0;
> +
> +	/* Apply default values of current mode */
> +	reg_list = &mode->reg_list;
> +	cci_multi_reg_write(ov05c10->regmap, reg_list->regs,
> +			    reg_list->num_of_regs, &ret);
> +	if (ret) {
> +		dev_err(&client->dev, "fail to set mode, ret: %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* Apply customized values from user */
> +	ret =  __v4l2_ctrl_handler_setup(ov05c10->sd.ctrl_handler);
> +	if (ret) {
> +		dev_err(&client->dev, "failed to setup v4l2 handler %d\n", ret);
> +		return ret;
> +	}
> +
> +	cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_on_regs,
> +			    ARRAY_SIZE(mode_OV05C10_stream_on_regs), &ret);
> +	if (ret)
> +		dev_err(&client->dev, "fail to start the streaming\n");
> +
> +	return ret;
> +}
> +
> +static int ov05c10_stop_streaming(struct ov05c10 *ov05c10)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> +	int ret = 0;
> +
> +	cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_off_regs,
> +			    ARRAY_SIZE(mode_OV05C10_stream_off_regs), &ret);
> +	if (ret)
> +		dev_err(&client->dev, "fail to stop the streaming\n");
> +
> +	return ret;
> +}
> +
> +static void ov05c10_sensor_power_set(struct ov05c10 *ov05c10, bool on)
> +{
> +	if (on) {
> +		gpiod_set_value(ov05c10->enable_gpio, 0);
> +		usleep_range(10, 20);
> +
> +		gpiod_set_value(ov05c10->enable_gpio, 1);
> +		usleep_range(1000, 2000);
> +	} else {
> +		gpiod_set_value(ov05c10->enable_gpio, 0);
> +		usleep_range(10, 20);
> +	}
> +}
> +
> +static int ov05c10_enable_streams(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state, u32 pad,
> +				  u64 streams_mask)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(sd);
> +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> +	int ret = 0;
> +
> +	ret = pm_runtime_resume_and_get(&client->dev);
> +	if (ret < 0)
> +		return ret;
> +
> +	ov05c10->cur_page = -1;

If you think the page number can't be trusted after resuming, then move
this to the resume handler. It can also be dropped from the probe
function.

> +
> +	ret = ov05c10_start_streaming(ov05c10);

As ov05c10_start_streaming() is called here only, I would just move the
code here. Same for ov05c10_stop_streaming() below.

> +	if (ret)
> +		goto err_rpm_put;
> +
> +	return 0;
> +
> +err_rpm_put:
> +	pm_runtime_put(&client->dev);
> +	return ret;
> +}
> +
> +static int ov05c10_disable_streams(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state, u32 pad,
> +				   u64 streams_mask)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(sd);
> +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> +
> +	ov05c10_stop_streaming(ov05c10);
> +	pm_runtime_put(&client->dev);
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_subdev_video_ops ov05c10_video_ops = {
> +	.s_stream = v4l2_subdev_s_stream_helper,
> +};
> +
> +static const struct v4l2_subdev_pad_ops ov05c10_pad_ops = {
> +	.enum_mbus_code = ov05c10_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = ov05c10_set_pad_format,
> +	.enum_frame_size = ov05c10_enum_frame_size,
> +	.enable_streams = ov05c10_enable_streams,
> +	.disable_streams = ov05c10_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops ov05c10_subdev_ops = {
> +	.video = &ov05c10_video_ops,
> +	.pad = &ov05c10_pad_ops,
> +};
> +
> +static const struct media_entity_operations ov05c10_subdev_entity_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +static const struct v4l2_subdev_internal_ops ov05c10_internal_ops = {
> +	.init_state = ov05c10_init_state,
> +};
> +
> +static int ov05c10_init_controls(struct ov05c10 *ov05c10)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> +	const struct ov05c10_mode *mode = &supported_mode;
> +	struct v4l2_fwnode_device_properties props;
> +	struct v4l2_ctrl_handler *ctrl_hdlr;

	struct v4l2_ctrl_handler *ctrl_hdlr = &ov05c10->ctrl_handler;

> +	s64 pixel_rate_max;
> +	s64 exposure_max;
> +	s64 vblank_def;
> +	s64 vblank_min;
> +	u32 max_items;
> +	s64 hblank;
> +	int ret;
> +
> +	ret = v4l2_ctrl_handler_init(&ov05c10->ctrl_handler, 10);

You can use ctrl_hdlr here.

> +	if (ret)
> +		return ret;
> +
> +	ctrl_hdlr = &ov05c10->ctrl_handler;

Drop this line.

> +
> +	max_items = ARRAY_SIZE(ov05c10_link_freq_menu_items) - 1;
> +	ov05c10->link_freq =
> +		v4l2_ctrl_new_int_menu(ctrl_hdlr,
> +				       NULL,
> +				       V4L2_CID_LINK_FREQ,
> +				       max_items,
> +				       0,
> +				       ov05c10_link_freq_menu_items);
> +	if (ov05c10->link_freq)
> +		ov05c10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +
> +	pixel_rate_max =
> +		link_freq_to_pixel_rate(ov05c10_link_freq_menu_items[0],
> +					supported_mode.lanes);
> +	ov05c10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
> +						V4L2_CID_PIXEL_RATE,
> +						0, pixel_rate_max,
> +						1, pixel_rate_max);
> +
> +	vblank_def = mode->vts_def - mode->height;
> +	vblank_min = mode->vts_min - mode->height;
> +	ov05c10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
> +					    V4L2_CID_VBLANK,
> +					    vblank_min,
> +					    OV05C10_VTS_MAX - mode->height,
> +					    1, vblank_def);
> +
> +	hblank = (mode->hts > mode->width) ? (mode->hts - mode->width) : 0;

No need for parentheses.

> +	ov05c10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
> +					    V4L2_CID_HBLANK,
> +					    hblank, hblank, 1, hblank);
> +	if (ov05c10->hblank)
> +		ov05c10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +
> +	exposure_max = mode->vts_def - OV05C10_EXPOSURE_MAX_MARGIN;
> +	ov05c10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
> +					      V4L2_CID_EXPOSURE,
> +					      OV05C10_EXPOSURE_MIN,
> +					      exposure_max,
> +					      OV05C10_EXPOSURE_STEP,
> +					      exposure_max);
> +
> +	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
> +			  OV05C10_ANA_GAIN_MIN, OV05C10_ANA_GAIN_MAX,
> +			  OV05C10_ANA_GAIN_STEP, OV05C10_ANA_GAIN_DEFAULT);
> +
> +	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
> +			  OV05C10_DGTL_GAIN_MIN, OV05C10_DGTL_GAIN_MAX,
> +			  OV05C10_DGTL_GAIN_STEP, OV05C10_DGTL_GAIN_DEFAULT);
> +
> +	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov05c10_ctrl_ops,
> +				     V4L2_CID_TEST_PATTERN,
> +				     ARRAY_SIZE(ov05c10_test_pattern_menu) - 1,
> +				     0, 0, ov05c10_test_pattern_menu);
> +
> +	if (ctrl_hdlr->error) {
> +		ret = ctrl_hdlr->error;
> +		dev_err(&client->dev, "V4L2 control init failed (%d)\n", ret);
> +		goto err_hdl_free;
> +	}
> +
> +	ret = v4l2_fwnode_device_parse(&client->dev, &props);
> +	if (ret)
> +		goto err_hdl_free;

Move this to the top of the function.

> +
> +	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov05c10_ctrl_ops,
> +					      &props);
> +	if (ret)
> +		goto err_hdl_free;

And this before the ctrl_hdlr->error check.

> +
> +	ov05c10->sd.ctrl_handler = ctrl_hdlr;
> +
> +	return 0;
> +
> +err_hdl_free:
> +	v4l2_ctrl_handler_free(ctrl_hdlr);
> +
> +	return ret;
> +}
> +
> +static int ov05c10_parse_endpoint(struct device *dev,
> +				  struct fwnode_handle *fwnode)
> +{
> +	struct v4l2_fwnode_endpoint bus_cfg = {
> +		.bus_type = V4L2_MBUS_CSI2_DPHY
> +	};
> +	struct fwnode_handle *ep;
> +	unsigned long bitmap;
> +	int ret;
> +
> +	ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
> +	if (!ep) {
> +		dev_err(dev, "Failed to get next endpoint\n");
> +		return -ENXIO;
> +	}
> +
> +	ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
> +	fwnode_handle_put(ep);
> +	if (ret)
> +		return ret;
> +
> +	if (bus_cfg.bus.mipi_csi2.num_data_lanes != supported_mode.lanes) {

Ideally the driver should support different number of lanes. That's not
the case yet, but let's decouple the number of lanes from the mode. Drop
the ov05c10_mode.lanes field and use OV05C10_DATA_LANES here.

> +		dev_err(dev,
> +			"number of CSI2 data lanes %d is not supported\n",
> +			bus_cfg.bus.mipi_csi2.num_data_lanes);
> +		ret = -EINVAL;
> +		goto err_endpoint_free;
> +	}
> +
> +	ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
> +				       bus_cfg.nr_of_link_frequencies,
> +				       ov05c10_link_frequencies,
> +				       ARRAY_SIZE(ov05c10_link_frequencies),
> +				       &bitmap);

You're supposed to use that bitmap to select which link frequencies to
expose to userspace.

> +	if (ret)
> +		dev_err(dev, "v4l2_link_freq_to_bitmap fail with %d\n", ret);
> +err_endpoint_free:
> +	v4l2_fwnode_endpoint_free(&bus_cfg);
> +
> +	return ret;
> +}
> +
> +static int ov05c10_probe(struct i2c_client *client)
> +{
> +	struct ov05c10 *ov05c10;
> +	u32 clkfreq;
> +	int ret;
> +
> +	ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), GFP_KERNEL);
> +	if (!ov05c10)
> +		return -ENOMEM;
> +
> +	struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> +
> +	ret = fwnode_property_read_u32(fwnode, "clock-frequency", &clkfreq);
> +	if (ret)
> +		return  dev_err_probe(&client->dev, -EINVAL,
> +				      "fail to get clock freq\n");

Let's try to land
https://lore.kernel.org/linux-media/20250521104115.176950-1-mehdi.djait@linux.intel.com/
and replace the code above with devm_v4l2_sensor_clk_get().

> +	if (clkfreq != OV05C10_REF_CLK)
> +		return dev_err_probe(&client->dev, -EINVAL,
> +				     "fail invalid clock freq %u, %lu expected\n",
> +				     clkfreq, OV05C10_REF_CLK);
> +
> +	ret = ov05c10_parse_endpoint(&client->dev, fwnode);
> +	if (ret)
> +		return dev_err_probe(&client->dev, -EINVAL,
> +				     "fail to parse endpoint\n");
> +
> +	ov05c10->enable_gpio = devm_gpiod_get(&client->dev, "enable",
> +					      GPIOD_OUT_LOW);
> +	if (IS_ERR(ov05c10->enable_gpio))
> +		return dev_err_probe(&client->dev,
> +				     PTR_ERR(ov05c10->enable_gpio),
> +				     "fail to get enable gpio\n");
> +
> +	v4l2_i2c_subdev_init(&ov05c10->sd, client, &ov05c10_subdev_ops);

I would move this line below just before ov05c10_init_controls() to keep
all the subdev initialization grouped.

> +
> +	ov05c10->regmap = devm_cci_regmap_init_i2c(client, 8);
> +	if (IS_ERR(ov05c10->regmap))
> +		return dev_err_probe(&client->dev, PTR_ERR(ov05c10->regmap),
> +				     "fail to init cci\n");
> +
> +	ov05c10->cur_page = -1;
> +
> +	ret = ov05c10_init_controls(ov05c10);
> +	if (ret)
> +		return dev_err_probe(&client->dev, ret, "fail to init ctl\n");
> +
> +	ov05c10->sd.internal_ops = &ov05c10_internal_ops;
> +	ov05c10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	ov05c10->sd.entity.ops = &ov05c10_subdev_entity_ops;
> +	ov05c10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> +
> +	ov05c10->pad.flags = MEDIA_PAD_FL_SOURCE;
> +
> +	ret = media_entity_pads_init(&ov05c10->sd.entity, NUM_OF_PADS,
> +				     &ov05c10->pad);
> +	if (ret)
> +		goto err_hdl_free;
> +
> +	ret = v4l2_subdev_init_finalize(&ov05c10->sd);
> +	if (ret < 0)
> +		goto err_media_entity_cleanup;
> +
> +	ret = v4l2_async_register_subdev_sensor(&ov05c10->sd);
> +	if (ret)
> +		goto err_media_entity_cleanup;
> +
> +	pm_runtime_set_active(&client->dev);
> +	pm_runtime_enable(&client->dev);
> +	pm_runtime_idle(&client->dev);
> +	pm_runtime_set_autosuspend_delay(&client->dev, 1000);
> +	pm_runtime_use_autosuspend(&client->dev);

Initialization of runtime PM should be done before calling
v4l2_async_register_subdev_sensor(), as the device can be used as soon
as it gets registered.

This will also not work on platforms where CONFIG_PM is not enabled. See
the imx290 driver for an example of how to enable (and disable) runtime
PM properly.

> +	return 0;
> +
> +err_media_entity_cleanup:
> +	media_entity_cleanup(&ov05c10->sd.entity);
> +
> +err_hdl_free:
> +	v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
> +
> +	return ret;
> +}
> +
> +static void ov05c10_remove(struct i2c_client *client)
> +{
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> +
> +	v4l2_async_unregister_subdev(sd);
> +	media_entity_cleanup(&sd->entity);
> +	v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
> +
> +	pm_runtime_disable(&client->dev);
> +	pm_runtime_set_suspended(&client->dev);
> +}
> +
> +static int ov05c10_runtime_resume(struct device *dev)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> +
> +	ov05c10_sensor_power_set(ov05c10, true);
> +	return 0;
> +}
> +
> +static int ov05c10_runtime_suspend(struct device *dev)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +	struct ov05c10 *ov05c10 = to_ov05c10(sd);
> +
> +	ov05c10_sensor_power_set(ov05c10, false);
> +	return 0;
> +}
> +
> +static DEFINE_RUNTIME_DEV_PM_OPS(ov05c10_pm_ops, ov05c10_runtime_suspend,
> +				 ov05c10_runtime_resume, NULL);
> +
> +static const struct i2c_device_id ov05c10_i2c_ids[] = {
> +	{"ov05c10", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, ov05c10_i2c_ids);
> +
> +static struct i2c_driver ov05c10_i2c_driver = {
> +	.driver = {
> +		.name = DRV_NAME,
> +		.pm = pm_ptr(&ov05c10_pm_ops),
> +	},
> +	.id_table = ov05c10_i2c_ids,
> +	.probe = ov05c10_probe,
> +	.remove = ov05c10_remove,
> +};
> +
> +module_i2c_driver(ov05c10_i2c_driver);
> +
> +MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
> +MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@amd.com>");
> +MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
> +MODULE_DESCRIPTION("OmniVision OV05C1010 sensor driver");
> +MODULE_LICENSE("GPL");

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-14 22:52     ` Laurent Pinchart
@ 2025-06-16 22:33       ` Nirujogi, Pratap
  2025-06-29  7:40         ` Sakari Ailus
  2025-06-23 11:27       ` Sakari Ailus
  1 sibling, 1 reply; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-16 22:33 UTC (permalink / raw)
  To: Laurent Pinchart, Sakari Ailus
  Cc: Hao Yao, Pratap Nirujogi, mchehab, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, dongcheng.yan, jason.z.chen, jimmy.su

Hi Laurent,

Thank you for reviewing and sharing your feedback.

On 6/14/2025 6:52 PM, Laurent Pinchart wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> On Fri, Jun 13, 2025 at 10:02:49PM +0000, Sakari Ailus wrote:
>> On Fri, Jun 13, 2025 at 12:55:46PM +0800, Hao Yao wrote:
>>> Hi Pratap,
>>>
>>> Thanks for your patch.
>>>
>>> This patch is written for your camera sensor module, which seems very
>>> different from those already applied on Dell laptops (some of "Dell Pro"
>>> series). Looking into the driver, I think this version will break the
>>> devices using ov05c10 sensor.
>>
>> There never was such a driver in upstream so nothing breaks. However, in
>> order to support these, could you check what would it take to support them
>> using this driver and post patches, please?
> 
> I'll repeat Kieran's comment here, there's a "first come, first serve"
> policiy in the kernel in general. That's one more reason to handle
> upstreaming early, if you want your driver to be merged :-)
> 
>>> I think this patch is better to be validated on existing devices, but please
>>> do some fixes before we can do validation. Please check my comments inline.
>>>
>>> On 2025/6/10 03:42, Pratap Nirujogi wrote:
>>>> Add driver for OmniVision 5.2M OV05C10 sensor. This driver
>>>> supports only the full size normal 2888x1808@30fps 2-lane
>>>> sensor profile.
>>>>
>>>> Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>>>> Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>>>> Co-developed-by: Bin Du <bin.du@amd.com>
>>>> Signed-off-by: Bin Du <bin.du@amd.com>
>>>> Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
>>>> ---
>>>> Changes v2 -> v3:
>>>>
>>>> * Update "refclk" property variable as "clock-frequency".
>>>> * Update sensor GPIO connector id name.
>>>> * Fix sensor v4l2 compliance issue.
>>>> * Fix license info.
>>>> * Address review comments.
>>>>
>>>>    MAINTAINERS                 |    8 +
>>>>    drivers/media/i2c/Kconfig   |   10 +
>>>>    drivers/media/i2c/Makefile  |    1 +
>>>>    drivers/media/i2c/ov05c10.c | 1061 +++++++++++++++++++++++++++++++++++
>>>>    4 files changed, 1080 insertions(+)
>>>>    create mode 100644 drivers/media/i2c/ov05c10.c
>>>>
>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>> index a92290fffa16..caca25d00bf2 100644
>>>> --- a/MAINTAINERS
>>>> +++ b/MAINTAINERS
>>>> @@ -18303,6 +18303,14 @@ T:       git git://linuxtv.org/media.git
>>>>    F:      Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml
>>>>    F:      drivers/media/i2c/ov02e10.c
>>>> +OMNIVISION OV05C10 SENSOR DRIVER
>>>> +M:       Nirujogi Pratap <pratap.nirujogi@amd.com>
>>>> +M:       Bin Du <bin.du@amd.com>
>>>> +L:       linux-media@vger.kernel.org
>>>> +S:       Maintained
>>>> +T:       git git://linuxtv.org/media.git
>>>> +F:       drivers/media/i2c/ov05c10.c
>>>> +
>>>>    OMNIVISION OV08D10 SENSOR DRIVER
>>>>    M:      Jimmy Su <jimmy.su@intel.com>
>>>>    L:      linux-media@vger.kernel.org
>>>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>>>> index e68202954a8f..1662fb29d75c 100644
>>>> --- a/drivers/media/i2c/Kconfig
>>>> +++ b/drivers/media/i2c/Kconfig
>>>> @@ -377,6 +377,16 @@ config VIDEO_OV02C10
>>>>              To compile this driver as a module, choose M here: the
>>>>              module will be called ov02c10.
>>>> +config VIDEO_OV05C10
>>>> + tristate "OmniVision OV05C10 sensor support"
>>>> + select V4L2_CCI_I2C
>>>> + help
>>>> +   This is a Video4Linux2 sensor driver for the OmniVision
>>>> +   OV05C10 camera.
>>>> +
>>>> +   To compile this driver as a module, choose M here: the
>>>> +   module will be called OV05C10.
>>>> +
>>>>    config VIDEO_OV08D10
>>>>            tristate "OmniVision OV08D10 sensor support"
>>>>            help
>>>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>>>> index 5873d29433ee..b4a1d721a7f2 100644
>>>> --- a/drivers/media/i2c/Makefile
>>>> +++ b/drivers/media/i2c/Makefile
>>>> @@ -85,6 +85,7 @@ obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
>>>>    obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
>>>>    obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o
>>>>    obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o
>>>> +obj-$(CONFIG_VIDEO_OV05C10) += ov05c10.o
>>>>    obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
>>>>    obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
>>>>    obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
>>>> diff --git a/drivers/media/i2c/ov05c10.c b/drivers/media/i2c/ov05c10.c
>>>> new file mode 100644
>>>> index 000000000000..9a1e493c4073
>>>> --- /dev/null
>>>> +++ b/drivers/media/i2c/ov05c10.c
>>>> @@ -0,0 +1,1061 @@
>>>> +// SPDX-License-Identifier: GPL-2.0+
>>>> +// Copyright (C) 2025 Advanced Micro Devices, Inc.
>>>> +
>>>> +#include <linux/clk.h>
>>>> +#include <linux/delay.h>
>>>> +#include <linux/gpio.h>
>>>> +#include <linux/i2c.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +#include <linux/units.h>
>>>> +#include <media/v4l2-cci.h>
>>>> +#include <media/v4l2-ctrls.h>
>>>> +#include <media/v4l2-device.h>
>>>> +#include <media/v4l2-fwnode.h>
>>>> +
>>>> +#define DRV_NAME                 "ov05c10"
>>>> +#define OV05C10_REF_CLK                  (24 * HZ_PER_MHZ)
>>>
>>> Seems your module use 24 MHz clock input. The Dell's modules always use
>>> 19.2MHz, which means your the PLL settings will not work on Dell's.
>>
>> This is ok as further work. Please send a patch. :-)
> 
> The patch should calculate PLL configuration dynamically, perhaps using
> the ccs-pll calculator if applicable.
> 
We have a dependency on the sensor vendor to address this suggestion. At 
present, we are getting the PLL configuration from the vendor as part of 
the sensor mode settings. We are thinking the PLL specific registers 
should be isolated first from the complete set of control registers to 
add a new method for dynamically configuring the PLL. But unfortunatly 
we dont have any information of the PLL of this sensor...:/, we need to 
work with the sensor vendor to get these details.

>>>> +
>>>> +#define MODE_WIDTH  2888
>>>> +#define MODE_HEIGHT 1808
>>>> +
>>>> +#define PAGE_NUM_MASK                    0xff000000
>>>> +#define PAGE_NUM_SHIFT                   24
>>>> +#define REG_ADDR_MASK                    0x00ffffff
>>>> +
>>>> +#define OV05C10_SYSCTL_PAGE              (0 << PAGE_NUM_SHIFT)
>>>> +#define OV05C10_CISCTL_PAGE              (1 << PAGE_NUM_SHIFT)
>>>> +#define OV05C10_ISPCTL_PAGE              (4 << PAGE_NUM_SHIFT)
>>>> +
>>>> +/* Chip ID */
>>>> +#define OV05C10_REG_CHIP_ID              (CCI_REG24(0x00) | OV05C10_SYSCTL_PAGE)
>>>> +#define OV05C10_CHIP_ID                  0x43055610
>>>> +
>>>> +/* Control registers */
>>>> +#define OV05C10_REG_TRIGGER              (CCI_REG8(0x01) | OV05C10_CISCTL_PAGE)
>>>> +#define OV05C_REG_TRIGGER_START          BIT(0)
>>>> +
>>>> +/* Exposure control */
>>>> +#define OV05C10_REG_EXPOSURE             (CCI_REG24(0x02) | OV05C10_CISCTL_PAGE)
>>>> +#define OV05C10_EXPOSURE_MAX_MARGIN      33
>>>> +#define OV05C10_EXPOSURE_MIN             4
>>>> +#define OV05C10_EXPOSURE_STEP            1
>>>> +#define OV05C10_EXPOSURE_DEFAULT 0x40
>>>> +
>>>> +/* V_TIMING internal */
>>>> +#define OV05C10_REG_VTS                  (CCI_REG16(0x05) | OV05C10_CISCTL_PAGE)
>>>> +#define OV05C10_VTS_30FPS                1860
>>>> +#define OV05C10_VTS_MAX                  0x7fff
>>>> +
>>>> +/* Test Pattern Control */
>>>> +#define OV05C10_REG_TEST_PATTERN (CCI_REG8(0x12) | OV05C10_ISPCTL_PAGE)
>>>> +#define OV05C10_TEST_PATTERN_ENABLE      BIT(0)
>>>> +#define OV05C10_REG_TEST_PATTERN_CTL     (CCI_REG8(0xf3) | OV05C10_ISPCTL_PAGE)
>>>> +#define OV05C10_REG_TEST_PATTERN_XXX     BIT(0)
>>>> +
>>>> +/* Digital gain control */
>>>> +#define OV05C10_REG_DGTL_GAIN_H          (CCI_REG8(0x21) | OV05C10_CISCTL_PAGE)
>>>> +#define OV05C10_REG_DGTL_GAIN_L          (CCI_REG8(0x22) | OV05C10_CISCTL_PAGE)
>>>> +
>>>> +#define OV05C10_DGTL_GAIN_MIN            0x40
>>>> +#define OV05C10_DGTL_GAIN_MAX            0xff
>>>> +#define OV05C10_DGTL_GAIN_DEFAULT        0x40
>>>> +#define OV05C10_DGTL_GAIN_STEP           1
>>>> +
>>>> +#define OV05C10_DGTL_GAIN_L_MASK 0xff
>>>> +#define OV05C10_DGTL_GAIN_H_SHIFT        8
>>>> +#define OV05C10_DGTL_GAIN_H_MASK 0xff00
>>>> +
>>>> +/* Analog gain control */
>>>> +#define OV05C10_REG_ANALOG_GAIN          (CCI_REG8(0x24) | OV05C10_CISCTL_PAGE)
>>>> +#define OV05C10_ANA_GAIN_MIN             0x80
>>>> +#define OV05C10_ANA_GAIN_MAX             0x07c0
>>>> +#define OV05C10_ANA_GAIN_STEP            1
>>>> +#define OV05C10_ANA_GAIN_DEFAULT 0x80
>>>> +
>>>> +/* H TIMING internal */
>>>> +#define OV05C10_REG_HTS                  (CCI_REG16(0x37) | OV05C10_CISCTL_PAGE)
>>>> +#define OV05C10_HTS_30FPS                0x0280
>>>> +
>>>> +/* Page selection */
>>>> +#define OV05C10_REG_PAGE_CTL             CCI_REG8(0xfd)
>>>> +
>>>> +#define NUM_OF_PADS 1
>>>> +
>>>> +#define OV05C10_GET_PAGE_NUM(reg)        (((reg) & PAGE_NUM_MASK) >>\
>>>> +                                  PAGE_NUM_SHIFT)
>>>> +#define OV05C10_GET_REG_ADDR(reg)        ((reg) & REG_ADDR_MASK)
>>>> +
>>>> +enum {
>>>> + OV05C10_LINK_FREQ_900MHZ_INDEX,
>>>> +};
>>>> +
>>>> +struct ov05c10_reg_list {
>>>> + u32 num_of_regs;
>>>> + const struct cci_reg_sequence *regs;
>>>> +};
>>>> +
>>>> +/* Mode : resolution and related config&values */
>>>> +struct ov05c10_mode {
>>>> + /* Frame width */
>>>> + u32 width;
>>>> + /* Frame height */
>>>> + u32 height;
>>>> + /* number of lanes */
>>>> + u32 lanes;
>>>> +
>>>> + /* V-timing */
>>>> + u32 vts_def;
>>>> + u32 vts_min;
>>>> +
>>>> + /* HTS */
>>>> + u32 hts;
>>>> +
>>>> + /* Index of Link frequency config to be used */
>>>> + u32 link_freq_index;
>>>> +
>>>> + /* Default register values */
>>>> + struct ov05c10_reg_list reg_list;
>>>> +};
>>>> +
>>>> +static const s64 ov05c10_link_frequencies[] = {
>>>> + 925 * HZ_PER_MHZ,
>>>> +};
>>>
>>> Is it 900 MHz, or 925 MHz?
>>>
>>>> +
>>>> +/* 2888x1808 30fps, 1800mbps, 2lane, 24mhz */
>>>
>>> Currently Dell's devices with ov05c10 use a CV chip to passthrough MIPI CSI
>>> signals, but it supports max 750 MHz link frequency. That's why this
>>> version:
>>> https://github.com/intel/ipu6-drivers/blob/master/drivers/media/i2c/ov05c10.c
>>> uses 480 MHz link frequency and a different resolution setting (2800x1576).
>>> At least the setting in out-of-tree Github driver should be merged into this
>>> version.
>>
>> Ditto.
>>
>>>> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
>>>> + { CCI_REG8(0xfd),  0x00 },
>>>> + { CCI_REG8(0x20),  0x00 },
>>>> + { CCI_REG8(0xfd),  0x00 },
>>>> + { CCI_REG8(0x20),  0x0b },
>>>> + { CCI_REG8(0xc1),  0x09 },
>>>> + { CCI_REG8(0x21),  0x06 },
>>>> + { CCI_REG8(0x14),  0x78 },
>>>> + { CCI_REG8(0xe7),  0x03 },
>>>> + { CCI_REG8(0xe7),  0x00 },
>>>> + { CCI_REG8(0x21),  0x00 },
>>>> + { CCI_REG8(0xfd),  0x01 },
>>>> + { CCI_REG8(0x03),  0x00 },
>>>> + { CCI_REG8(0x04),  0x06 },
>>>> + { CCI_REG8(0x05),  0x07 },
>>>> + { CCI_REG8(0x06),  0x44 },
>>>> + { CCI_REG8(0x07),  0x08 },
>>>> + { CCI_REG8(0x1b),  0x01 },
>>>> + { CCI_REG8(0x24),  0xff },
>>>> + { CCI_REG8(0x32),  0x03 },
>>>> + { CCI_REG8(0x42),  0x5d },
>>>> + { CCI_REG8(0x43),  0x08 },
>>>> + { CCI_REG8(0x44),  0x81 },
>>>> + { CCI_REG8(0x46),  0x5f },
>>>> + { CCI_REG8(0x48),  0x18 },
>>>> + { CCI_REG8(0x49),  0x04 },
>>>> + { CCI_REG8(0x5c),  0x18 },
>>>> + { CCI_REG8(0x5e),  0x13 },
>>>> + { CCI_REG8(0x70),  0x15 },
>>>> + { CCI_REG8(0x77),  0x35 },
>>>> + { CCI_REG8(0x79),  0x00 },
>>>> + { CCI_REG8(0x7b),  0x08 },
>>>> + { CCI_REG8(0x7d),  0x08 },
>>>> + { CCI_REG8(0x7e),  0x08 },
>>>> + { CCI_REG8(0x7f),  0x08 },
>>>> + { CCI_REG8(0x90),  0x37 },
>>>> + { CCI_REG8(0x91),  0x05 },
>>>> + { CCI_REG8(0x92),  0x18 },
>>>> + { CCI_REG8(0x93),  0x27 },
>>>> + { CCI_REG8(0x94),  0x05 },
>>>> + { CCI_REG8(0x95),  0x38 },
>>>> + { CCI_REG8(0x9b),  0x00 },
>>>> + { CCI_REG8(0x9c),  0x06 },
>>>> + { CCI_REG8(0x9d),  0x28 },
>>>> + { CCI_REG8(0x9e),  0x06 },
>>>> + { CCI_REG8(0xb2),  0x0f },
>>>> + { CCI_REG8(0xb3),  0x29 },
>>>> + { CCI_REG8(0xbf),  0x3c },
>>>> + { CCI_REG8(0xc2),  0x04 },
>>>> + { CCI_REG8(0xc4),  0x00 },
>>>> + { CCI_REG8(0xca),  0x20 },
>>>> + { CCI_REG8(0xcb),  0x20 },
>>>> + { CCI_REG8(0xcc),  0x28 },
>>>> + { CCI_REG8(0xcd),  0x28 },
>>>> + { CCI_REG8(0xce),  0x20 },
>>>> + { CCI_REG8(0xcf),  0x20 },
>>>> + { CCI_REG8(0xd0),  0x2a },
>>>> + { CCI_REG8(0xd1),  0x2a },
>>>> + { CCI_REG8(0xfd),  0x0f },
>>>> + { CCI_REG8(0x00),  0x00 },
>>>> + { CCI_REG8(0x01),  0xa0 },
>>>> + { CCI_REG8(0x02),  0x48 },
>>>> + { CCI_REG8(0x07),  0x8f },
>>>> + { CCI_REG8(0x08),  0x70 },
>>>> + { CCI_REG8(0x09),  0x01 },
>>>> + { CCI_REG8(0x0b),  0x40 },
>>>> + { CCI_REG8(0x0d),  0x07 },
>>>> + { CCI_REG8(0x11),  0x33 },
>>>> + { CCI_REG8(0x12),  0x77 },
>>>> + { CCI_REG8(0x13),  0x66 },
>>>> + { CCI_REG8(0x14),  0x65 },
>>>> + { CCI_REG8(0x15),  0x37 },
>>>> + { CCI_REG8(0x16),  0xbf },
>>>> + { CCI_REG8(0x17),  0xff },
>>>> + { CCI_REG8(0x18),  0xff },
>>>> + { CCI_REG8(0x19),  0x12 },
>>>> + { CCI_REG8(0x1a),  0x10 },
>>>> + { CCI_REG8(0x1c),  0x77 },
>>>> + { CCI_REG8(0x1d),  0x77 },
>>>> + { CCI_REG8(0x20),  0x0f },
>>>> + { CCI_REG8(0x21),  0x0f },
>>>> + { CCI_REG8(0x22),  0x0f },
>>>> + { CCI_REG8(0x23),  0x0f },
>>>> + { CCI_REG8(0x2b),  0x20 },
>>>> + { CCI_REG8(0x2c),  0x20 },
>>>> + { CCI_REG8(0x2d),  0x04 },
>>>> + { CCI_REG8(0xfd),  0x03 },
>>>> + { CCI_REG8(0x9d),  0x0f },
>>>> + { CCI_REG8(0x9f),  0x40 },
>>>> + { CCI_REG8(0xfd),  0x00 },
>>>> + { CCI_REG8(0x20),  0x1b },
>>>> + { CCI_REG8(0xfd),  0x04 },
>>>> + { CCI_REG8(0x19),  0x60 },
>>>> + { CCI_REG8(0xfd),  0x02 },
>>>> + { CCI_REG8(0x75),  0x05 },
>>>> + { CCI_REG8(0x7f),  0x06 },
>>>> + { CCI_REG8(0x9a),  0x03 },
>>>> + { CCI_REG8(0xa2),  0x07 },
>>>> + { CCI_REG8(0xa3),  0x10 },
>>>> + { CCI_REG8(0xa5),  0x02 },
>>>> + { CCI_REG8(0xa6),  0x0b },
>>>> + { CCI_REG8(0xa7),  0x48 },
>>>> + { CCI_REG8(0xfd),  0x07 },
>>>> + { CCI_REG8(0x42),  0x00 },
>>>> + { CCI_REG8(0x43),  0x80 },
>>>> + { CCI_REG8(0x44),  0x00 },
>>>> + { CCI_REG8(0x45),  0x80 },
>>>> + { CCI_REG8(0x46),  0x00 },
>>>> + { CCI_REG8(0x47),  0x80 },
>>>> + { CCI_REG8(0x48),  0x00 },
>>>> + { CCI_REG8(0x49),  0x80 },
>>>> + { CCI_REG8(0x00),  0xf7 },
>>>> + { CCI_REG8(0xfd),  0x00 },
>>>> + { CCI_REG8(0xe7),  0x03 },
>>>> + { CCI_REG8(0xe7),  0x00 },
>>>> + { CCI_REG8(0xfd),  0x00 },
>>>> + { CCI_REG8(0x93),  0x18 },
>>>> + { CCI_REG8(0x94),  0xff },
>>>> + { CCI_REG8(0x95),  0xbd },
>>>> + { CCI_REG8(0x96),  0x1a },
>>>> + { CCI_REG8(0x98),  0x04 },
>>>> + { CCI_REG8(0x99),  0x08 },
>>>> + { CCI_REG8(0x9b),  0x10 },
>>>> + { CCI_REG8(0x9c),  0x3f },
>>>> + { CCI_REG8(0xa1),  0x05 },
>>>> + { CCI_REG8(0xa4),  0x2f },
>>>> + { CCI_REG8(0xc0),  0x0c },
>>>> + { CCI_REG8(0xc1),  0x08 },
>>>> + { CCI_REG8(0xc2),  0x00 },
>>>> + { CCI_REG8(0xb6),  0x20 },
>>>> + { CCI_REG8(0xbb),  0x80 },
>>>> + { CCI_REG8(0xfd),  0x00 },
>>>> + { CCI_REG8(0xa0),  0x01 },
>>>> + { CCI_REG8(0xfd),  0x01 },
> 
> Please replace these with names macros where possible. I'm sure quite a
> few of the registers configured here are documented in the datasheet.
> The registers that configure the mode (analog crop, digital crop,
> binning, skipping, ...) should be computed dynamically from the subdev
> pad format and selection rectangles, not hardcoded.
> 
I agree, but we get the sensor settings based on our requirements from 
the vendor, i will check if we can get some more info regarding the 
crop, binning, skipping etc...

Thanks,
Pratap

>>>> +};
>>>> +
>>>> +static const struct cci_reg_sequence mode_OV05C10_stream_on_regs[] = {
>>>> + { CCI_REG8(0xfd), 0x01 },
>>>> + { CCI_REG8(0x33), 0x03 },
>>>> + { CCI_REG8(0x01), 0x02 },
>>>> + { CCI_REG8(0xfd), 0x00 },
>>>> + { CCI_REG8(0x20), 0x1f },
>>>> + { CCI_REG8(0xfd), 0x01 },
>>>> +};
>>>> +
>>>> +static const struct cci_reg_sequence mode_OV05C10_stream_off_regs[] = {
>>>> + { CCI_REG8(0xfd), 0x00 },
>>>> + { CCI_REG8(0x20), 0x5b },
>>>> + { CCI_REG8(0xfd), 0x01 },
>>>> + { CCI_REG8(0x33), 0x02 },
>>>> + { CCI_REG8(0x01), 0x02 },
>>>> +};
>>>> +
>>>> +static const char * const ov05c10_test_pattern_menu[] = {
>>>> + "Disabled",
>>>> + "Vertical Color Bar Type 1",
>>>> + "Vertical Color Bar Type 2",
>>>> + "Vertical Color Bar Type 3",
>>>> + "Vertical Color Bar Type 4"
>>>> +};
>>>> +
>>>> +/* Configurations for supported link frequencies */
>>>> +#define OV05C10_LINK_FREQ_900MHZ (900 * HZ_PER_MHZ)
>>>> +
>>>> +/* Number of lanes supported */
>>>> +#define OV05C10_DATA_LANES               2
>>>> +
>>>> +/* Bits per sample of sensor output */
>>>> +#define OV05C10_BITS_PER_SAMPLE          10
>>>> +
>>>> +/*
>>>> + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
>>>> + * data rate => double data rate; number of lanes => 2; bits per pixel => 10
>>>> + */
>>>> +static u64 link_freq_to_pixel_rate(u64 f, u32 lane_nr)
>>>> +{
>>>> + f *= 2 * lane_nr;
>>>> + do_div(f, OV05C10_BITS_PER_SAMPLE);
>>>> +
>>>> + return f;
>>>> +}
>>>> +
>>>> +/* Menu items for LINK_FREQ V4L2 control */
>>>> +static const s64 ov05c10_link_freq_menu_items[] = {
>>>> + OV05C10_LINK_FREQ_900MHZ,
>>>> +};
>>>> +
>>>> +/* Mode configs, currently, only support 1 mode */
>>>> +static const struct ov05c10_mode supported_mode = {
>>>> + .width = MODE_WIDTH,
>>>> + .height = MODE_HEIGHT,
>>>> + .vts_def = OV05C10_VTS_30FPS,
>>>> + .vts_min = OV05C10_VTS_30FPS,
>>>> + .hts = 640,
>>>> + .lanes = 2,
>>>> + .reg_list = {
>>>> +         .num_of_regs = ARRAY_SIZE(ov05c10_2888x1808_regs),
>>>> +         .regs = ov05c10_2888x1808_regs,
>>>> + },
>>>> + .link_freq_index = OV05C10_LINK_FREQ_900MHZ_INDEX,
>>>> +};
>>>> +
>>>> +struct ov05c10 {
>>>> + struct v4l2_subdev sd;
>>>> + struct media_pad pad;
>>>> +
>>>> + /* V4L2 control handler */
>>>> + struct v4l2_ctrl_handler ctrl_handler;
>>>> +
>>>> + /* V4L2 Controls */
>>>> + struct v4l2_ctrl *link_freq;
>>>> + struct v4l2_ctrl *pixel_rate;
>>>> + struct v4l2_ctrl *vblank;
>>>> + struct v4l2_ctrl *hblank;
>>>> + struct v4l2_ctrl *exposure;
>>>> +
>>>> + struct regmap *regmap;
>>>> +
>>>> + /* gpio descriptor */
>>>> + struct gpio_desc *enable_gpio;
>>>> +
>>>> + /* Current page for sensor register control */
>>>> + int cur_page;
>>>> +};
>>>> +
>>>> +#define to_ov05c10(_sd)  container_of(_sd, struct ov05c10, sd)
>>>> +
>>>> +static int ov05c10_init_state(struct v4l2_subdev *sd,
>>>> +                       struct v4l2_subdev_state *sd_state)
>>>> +{
>>>> + struct v4l2_mbus_framefmt *frame_fmt;
>>>> + struct v4l2_subdev_format fmt = {
>>>> +         .which = V4L2_SUBDEV_FORMAT_TRY,
>>>> +         .format = {
>>>> +                 .width = MODE_WIDTH,
>>>> +                 .height = MODE_HEIGHT,
>>>> +                 .code = MEDIA_BUS_FMT_SGRBG10_1X10,
>>>> +                 .field = V4L2_FIELD_NONE,
>>>> +         }
>>>> + };
>>>> +
>>>> + frame_fmt = v4l2_subdev_state_get_format(sd_state, 0);
>>>> + *frame_fmt = fmt.format;
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page, int *err)
>>>
>>> Seems nobody cares the return value of ov05c10_switch_page() or
>>> ov05c10_reg_write(), etc.. It should be better to use void return, or use
>>> return value instead of int *err.
>>
>> As this is a function that has two users, I'd use a more common pattern of
>> returning a value.
>>
>>>> +{
>>>> + int ret = 0;
>>>> +
>>>> + if (err && *err)
>>>> +         return *err;
>>>> +
>>>> + if (page != ov05c10->cur_page) {
>>>> +         cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, &ret);
>>>> +         if (!ret)
>>>> +                 ov05c10->cur_page = page;
>>>> + }
>>>> +
>>>> + if (err)
>>>> +         *err = ret;
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +/* refer to the implementation of cci_read */
>>>> +static int ov05c10_reg_read(struct ov05c10 *ov05c10, u32 reg,
>>>> +                     u64 *val, int *err)
>>>> +{
>>>> + u32 page;
>>>> + u32 addr;
>>>> + int ret = 0;
>>>> +
>>>> + if (err && *err)
>>>> +         return *err;
>>>> +
>>>> + page = OV05C10_GET_PAGE_NUM(reg);
>>>> + addr = OV05C10_GET_REG_ADDR(reg);
>>>> + ov05c10_switch_page(ov05c10, page, &ret);
>>>> + cci_read(ov05c10->regmap, addr, val, &ret);
>>>> + if (err)
>>>> +         *err = ret;
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +/* refer to the implementation of cci_write */
>>>> +static int ov05c10_reg_write(struct ov05c10 *ov05c10, u32 reg,
>>>> +                      u64 val, int *err)
>>>> +{
>>>> + u32 page;
>>>> + u32 addr;
>>>> + int ret = 0;
>>>> +
>>>> + if (err && *err)
>>>> +         return *err;
>>>> +
>>>> + page = OV05C10_GET_PAGE_NUM(reg);
>>>> + addr = OV05C10_GET_REG_ADDR(reg);
>>>> + ov05c10_switch_page(ov05c10, page, &ret);
>>>> + cci_write(ov05c10->regmap, addr, val, &ret);
>>>> + if (err)
>>>> +         *err = ret;
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int ov05c10_update_vblank(struct ov05c10 *ov05c10, u32 vblank)
>>>> +{
>>>> + const struct ov05c10_mode *mode = &supported_mode;
>>>> + u64 val;
>>>> + int ret = 0;
>>>> +
>>>> + val = mode->height + vblank;
>>>> + ov05c10_reg_write(ov05c10, OV05C10_REG_VTS, val, &ret);
>>>> + ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>>> +                   OV05C_REG_TRIGGER_START, &ret);
>>>> +
>>>> + return ret;
>>>> +}
>>>
>>> I remembered that the ov05c10 VTS control (P1:0x05~0x06) is a bit weird.
>>> This register seems take the increment of VTS value, so direct write of VTS
>>> value will not set it properly. Does this version make AE working on your
>>> platform?
>>>
>>>> +
>>>> +static int ov05c10_update_exposure(struct ov05c10 *ov05c10, u32 exposure)
>>>> +{
>>>> + int ret = 0;
>>>> +
>>>> + ov05c10_reg_write(ov05c10, OV05C10_REG_EXPOSURE, exposure, &ret);
>>>> + ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>>> +                   OV05C_REG_TRIGGER_START, &ret);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int ov05c10_update_analog_gain(struct ov05c10 *ov05c10, u32 a_gain)
>>>> +{
>>>> + int ret = 0;
>>>> +
>>>> + ov05c10_reg_write(ov05c10, OV05C10_REG_ANALOG_GAIN, a_gain, &ret);
>>>> + ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>>> +                   OV05C_REG_TRIGGER_START, &ret);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int ov05c10_update_digital_gain(struct ov05c10 *ov05c10, u32 d_gain)
>>>> +{
>>>> + u64 val;
>>>> + int ret = 0;
>>>> +
>>>> + val = d_gain & OV05C10_DGTL_GAIN_L_MASK;
>>>> + ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_L, val, &ret);
>>>> +
>>>> + val = (d_gain & OV05C10_DGTL_GAIN_H_MASK) >> OV05C10_DGTL_GAIN_H_SHIFT;
>>>> + ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_H, val, &ret);
>>>> +
>>>> + ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>>> +                   OV05C_REG_TRIGGER_START, &ret);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int ov05c10_enable_test_pattern(struct ov05c10 *ov05c10, u32 pattern)
>>>> +{
>>>> + u64 val;
>>>> + int ret = 0;
>>>> +
>>>> + if (pattern) {
>>>> +         ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
>>>> +                          &val, &ret);
>>>> +         ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
>>>> +                           val | OV05C10_REG_TEST_PATTERN_XXX, &ret);
>>>> +         ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
>>>> +         val |= OV05C10_TEST_PATTERN_ENABLE;
>>>> + } else {
>>>> +         ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
>>>> +         val &= ~OV05C10_TEST_PATTERN_ENABLE;
>>>> + }
>>>> +
>>>> + ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN, val, &ret);
>>>> + ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>>> +                   OV05C_REG_TRIGGER_START, &ret);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int ov05c10_set_ctrl(struct v4l2_ctrl *ctrl)
>>>> +{
>>>> + struct ov05c10 *ov05c10 = container_of(ctrl->handler,
>>>> +                                        struct ov05c10, ctrl_handler);
>>>> + struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>>>> + const struct ov05c10_mode *mode = &supported_mode;
>>>> + s64 max;
>>>> + int ret = 0;
>>>> +
>>>> + /* Propagate change of current control to all related controls */
>>>> + if (ctrl->id == V4L2_CID_VBLANK) {
>>>> +         s64 cur_exp = ov05c10->exposure->cur.val;
>>>> +
>>>> +         /* Update max exposure while meeting expected vblanking */
>>>> +         max = mode->height + ctrl->val - OV05C10_EXPOSURE_MAX_MARGIN;
>>>> +         cur_exp = clamp(cur_exp, ov05c10->exposure->minimum, max);
>>>> +         ret = __v4l2_ctrl_modify_range(ov05c10->exposure,
>>>> +                                        ov05c10->exposure->minimum,
>>>> +                                        max, ov05c10->exposure->step,
>>>> +                                        cur_exp);
>>>> +         if (!ret)
>>>> +                 return ret;
>>>> + }
>>>> +
>>>> + /*
>>>> +  * Applying V4L2 control value only happens
>>>> +  * when power is up for streaming
>>>> +  */
>>>> + if (!pm_runtime_get_if_in_use(&client->dev))
>>>> +         return 0;
>>>> +
>>>> + switch (ctrl->id) {
>>>> + case V4L2_CID_ANALOGUE_GAIN:
>>>> +         ret = ov05c10_update_analog_gain(ov05c10, ctrl->val);
>>>> +         break;
>>>> + case V4L2_CID_DIGITAL_GAIN:
>>>> +         ret = ov05c10_update_digital_gain(ov05c10, ctrl->val);
>>>> +         break;
>>>> + case V4L2_CID_EXPOSURE:
>>>> +         ret = ov05c10_update_exposure(ov05c10, ctrl->val);
>>>> +         break;
>>>> + case V4L2_CID_VBLANK:
>>>> +         ret = ov05c10_update_vblank(ov05c10, ctrl->val);
>>>> +         break;
>>>> + case V4L2_CID_TEST_PATTERN:
>>>> +         ret = ov05c10_enable_test_pattern(ov05c10, ctrl->val);
>>>> +         break;
>>>> + default:
>>>> +         ret = -ENOTTY;
>>>> +         dev_err(&client->dev,
>>>> +                 "ctrl(id:0x%x,val:0x%x) is not handled\n",
>>>> +                 ctrl->id, ctrl->val);
>>>> +         break;
>>>> + }
>>>> +
>>>> + pm_runtime_put(&client->dev);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static const struct v4l2_ctrl_ops ov05c10_ctrl_ops = {
>>>> + .s_ctrl = ov05c10_set_ctrl,
>>>> +};
>>>> +
>>>> +static int ov05c10_enum_mbus_code(struct v4l2_subdev *sd,
>>>> +                           struct v4l2_subdev_state *sd_state,
>>>> +                           struct v4l2_subdev_mbus_code_enum *code)
>>>> +{
>>>> + /* Only one bayer order(GRBG) is supported */
>>>> + if (code->index > 0)
>>>> +         return -EINVAL;
>>>> +
>>>> + code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int ov05c10_enum_frame_size(struct v4l2_subdev *sd,
>>>> +                            struct v4l2_subdev_state *sd_state,
>>>> +                            struct v4l2_subdev_frame_size_enum *fse)
>>>> +{
>>>> + /* ov05c10 driver currently only supports 1 mode*/
>>>> + if (fse->index != 0)
>>>> +         return -EINVAL;
>>>> +
>>>> + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
>>>> +         return -EINVAL;
>>>> +
>>>> + fse->min_width = supported_mode.width;
>>>> + fse->max_width = fse->min_width;
>>>> + fse->min_height = supported_mode.height;
>>>> + fse->max_height = fse->min_height;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static void ov05c10_update_pad_format(const struct ov05c10_mode *mode,
>>>> +                               struct v4l2_subdev_format *fmt)
>>>> +{
>>>> + fmt->format.width = mode->width;
>>>> + fmt->format.height = mode->height;
>>>> + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
>>>> + fmt->format.field = V4L2_FIELD_NONE;
>>>> +}
>>>> +
>>>> +static int ov05c10_set_pad_format(struct v4l2_subdev *sd,
>>>> +                           struct v4l2_subdev_state *sd_state,
>>>> +                           struct v4l2_subdev_format *fmt)
>>>> +{
>>>> + struct v4l2_mbus_framefmt *framefmt;
>>>> + struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>>> + const struct ov05c10_mode *mode;
>>>> + s32 vblank_def;
>>>> + s32 vblank_min;
>>>> + s64 pixel_rate;
>>>> + s64 link_freq;
>>>> + s64 h_blank;
>>>> +
>>>> + /* Only one raw bayer(GRBG) order is supported */
>>>> + if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10)
>>>> +         fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
>>>> +
>>>> + mode = &supported_mode;
>>>> + ov05c10_update_pad_format(mode, fmt);
>>>> + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
>>>> +         framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
>>>> +         *framefmt = fmt->format;
>>>> + } else {
>>>> +         __v4l2_ctrl_s_ctrl(ov05c10->link_freq, mode->link_freq_index);
>>>> +         link_freq = ov05c10_link_freq_menu_items[mode->link_freq_index];
>>>> +         pixel_rate = link_freq_to_pixel_rate(link_freq,
>>>> +                                              mode->lanes);
>>>> +         __v4l2_ctrl_s_ctrl_int64(ov05c10->pixel_rate, pixel_rate);
>>>> +
>>>> +         /* Update limits and set FPS to default */
>>>> +         vblank_def = mode->vts_def - mode->height;
>>>> +         vblank_min = mode->vts_min - mode->height;
>>>> +         __v4l2_ctrl_modify_range(ov05c10->vblank, vblank_min,
>>>> +                                  OV05C10_VTS_MAX - mode->height,
>>>> +                                  1, vblank_def);
>>>> +         __v4l2_ctrl_s_ctrl(ov05c10->vblank, vblank_def);
>>>> +         h_blank = mode->hts;
>>>> +         __v4l2_ctrl_modify_range(ov05c10->hblank, h_blank,
>>>> +                                  h_blank, 1, h_blank);
>>>> + }
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int ov05c10_start_streaming(struct ov05c10 *ov05c10)
>>>> +{
>>>> + struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>>>> + const struct ov05c10_mode *mode = &supported_mode;
>>>> + const struct ov05c10_reg_list *reg_list;
>>>> + int ret = 0;
>>>> +
>>>> + /* Apply default values of current mode */
>>>> + reg_list = &mode->reg_list;
>>>> + cci_multi_reg_write(ov05c10->regmap, reg_list->regs,
>>>> +                     reg_list->num_of_regs, &ret);
>>>> + if (ret) {
>>>> +         dev_err(&client->dev, "fail to set mode, ret: %d\n", ret);
>>>> +         return ret;
>>>> + }
>>>> +
>>>> + /* Apply customized values from user */
>>>> + ret =  __v4l2_ctrl_handler_setup(ov05c10->sd.ctrl_handler);
>>>> + if (ret) {
>>>> +         dev_err(&client->dev, "failed to setup v4l2 handler %d\n", ret);
>>>> +         return ret;
>>>> + }
>>>> +
>>>> + cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_on_regs,
>>>> +                     ARRAY_SIZE(mode_OV05C10_stream_on_regs), &ret);
>>>> + if (ret)
>>>> +         dev_err(&client->dev, "fail to start the streaming\n");
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int ov05c10_stop_streaming(struct ov05c10 *ov05c10)
>>>> +{
>>>> + struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>>>> + int ret = 0;
>>>> +
>>>> + cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_off_regs,
>>>> +                     ARRAY_SIZE(mode_OV05C10_stream_off_regs), &ret);
>>>> + if (ret)
>>>> +         dev_err(&client->dev, "fail to stop the streaming\n");
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static void ov05c10_sensor_power_set(struct ov05c10 *ov05c10, bool on)
>>>> +{
>>>> + if (on) {
>>>> +         gpiod_set_value(ov05c10->enable_gpio, 0);
>>>> +         usleep_range(10, 20);
>>>> +
>>>> +         gpiod_set_value(ov05c10->enable_gpio, 1);
>>>> +         usleep_range(1000, 2000);
>>>
>>> According to the datasheet, ov05c10 needs at least 8 ms to work after its
>>> XSHUTDN pin pulled to high. 1 ms maybe too quick, did you tested it? Or the
>>> enable_gpio is actually not the XSHUTDN pin?
>>>
>>> On Intel platforms, if the sensor driver controls the module power, ususally
>>> it requires GPIO "reset", regulator "avdd" and clk "img_clk" assigned by
>>> kernel driver intel_skl_int3472_discrete. I'm not sure whether any devices
>>> on market using this power control solution, but if any, missing those
>>> resources will stop them from powering-up cameras.
>>
>> Please post a patch.
>>
>>>> + } else {
>>>> +         gpiod_set_value(ov05c10->enable_gpio, 0);
>>>> +         usleep_range(10, 20);
>>>> + }
>>>> +}
>>>> +
>>>> +static int ov05c10_enable_streams(struct v4l2_subdev *sd,
>>>> +                           struct v4l2_subdev_state *state, u32 pad,
>>>> +                           u64 streams_mask)
>>>> +{
>>>> + struct i2c_client *client = v4l2_get_subdevdata(sd);
>>>> + struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>>> + int ret = 0;
>>>> +
>>>> + ret = pm_runtime_resume_and_get(&client->dev);
>>>> + if (ret < 0)
>>>> +         return ret;
>>>> +
>>>> + ov05c10->cur_page = -1;
>>>> +
>>>> + ret = ov05c10_start_streaming(ov05c10);
>>>> + if (ret)
>>>> +         goto err_rpm_put;
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_rpm_put:
>>>> + pm_runtime_put(&client->dev);
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int ov05c10_disable_streams(struct v4l2_subdev *sd,
>>>> +                            struct v4l2_subdev_state *state, u32 pad,
>>>> +                            u64 streams_mask)
>>>> +{
>>>> + struct i2c_client *client = v4l2_get_subdevdata(sd);
>>>> + struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>>> +
>>>> + ov05c10_stop_streaming(ov05c10);
>>>> + pm_runtime_put(&client->dev);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct v4l2_subdev_video_ops ov05c10_video_ops = {
>>>> + .s_stream = v4l2_subdev_s_stream_helper,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_pad_ops ov05c10_pad_ops = {
>>>> + .enum_mbus_code = ov05c10_enum_mbus_code,
>>>> + .get_fmt = v4l2_subdev_get_fmt,
>>>> + .set_fmt = ov05c10_set_pad_format,
>>>> + .enum_frame_size = ov05c10_enum_frame_size,
>>>> + .enable_streams = ov05c10_enable_streams,
>>>> + .disable_streams = ov05c10_disable_streams,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_ops ov05c10_subdev_ops = {
>>>> + .video = &ov05c10_video_ops,
>>>> + .pad = &ov05c10_pad_ops,
>>>> +};
>>>> +
>>>> +static const struct media_entity_operations ov05c10_subdev_entity_ops = {
>>>> + .link_validate = v4l2_subdev_link_validate,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_internal_ops ov05c10_internal_ops = {
>>>> + .init_state = ov05c10_init_state,
>>>> +};
>>>> +
>>>> +static int ov05c10_init_controls(struct ov05c10 *ov05c10)
>>>> +{
>>>> + struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>>>> + const struct ov05c10_mode *mode = &supported_mode;
>>>> + struct v4l2_fwnode_device_properties props;
>>>> + struct v4l2_ctrl_handler *ctrl_hdlr;
>>>> + s64 pixel_rate_max;
>>>> + s64 exposure_max;
>>>> + s64 vblank_def;
>>>> + s64 vblank_min;
>>>> + u32 max_items;
>>>> + s64 hblank;
>>>> + int ret;
>>>> +
>>>> + ret = v4l2_ctrl_handler_init(&ov05c10->ctrl_handler, 10);
>>>> + if (ret)
>>>> +         return ret;
>>>> +
>>>> + ctrl_hdlr = &ov05c10->ctrl_handler;
>>>> +
>>>> + max_items = ARRAY_SIZE(ov05c10_link_freq_menu_items) - 1;
>>>> + ov05c10->link_freq =
>>>> +         v4l2_ctrl_new_int_menu(ctrl_hdlr,
>>>> +                                NULL,
>>>> +                                V4L2_CID_LINK_FREQ,
>>>> +                                max_items,
>>>> +                                0,
>>>> +                                ov05c10_link_freq_menu_items);
>>>> + if (ov05c10->link_freq)
>>>> +         ov05c10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>> +
>>>> + pixel_rate_max =
>>>> +         link_freq_to_pixel_rate(ov05c10_link_freq_menu_items[0],
>>>> +                                 supported_mode.lanes);
>>>> + ov05c10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
>>>> +                                         V4L2_CID_PIXEL_RATE,
>>>> +                                         0, pixel_rate_max,
>>>> +                                         1, pixel_rate_max);
>>>> +
>>>> + vblank_def = mode->vts_def - mode->height;
>>>> + vblank_min = mode->vts_min - mode->height;
>>>> + ov05c10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
>>>> +                                     V4L2_CID_VBLANK,
>>>> +                                     vblank_min,
>>>> +                                     OV05C10_VTS_MAX - mode->height,
>>>> +                                     1, vblank_def);
>>>> +
>>>> + hblank = (mode->hts > mode->width) ? (mode->hts - mode->width) : 0;
>>>
>>> Here your hts uses 640 but width is 2888, which means hblank is set to 0
>>> here. This is wrong, please fix your configuration.
>>>
>>>> + ov05c10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
>>>> +                                     V4L2_CID_HBLANK,
>>>> +                                     hblank, hblank, 1, hblank);
>>>> + if (ov05c10->hblank)
>>>> +         ov05c10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>>> +
>>>> + exposure_max = mode->vts_def - OV05C10_EXPOSURE_MAX_MARGIN;
>>>> + ov05c10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
>>>> +                                       V4L2_CID_EXPOSURE,
>>>> +                                       OV05C10_EXPOSURE_MIN,
>>>> +                                       exposure_max,
>>>> +                                       OV05C10_EXPOSURE_STEP,
>>>> +                                       exposure_max);
>>>> +
>>>> + v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
>>>> +                   OV05C10_ANA_GAIN_MIN, OV05C10_ANA_GAIN_MAX,
>>>> +                   OV05C10_ANA_GAIN_STEP, OV05C10_ANA_GAIN_DEFAULT);
>>>> +
>>>> + v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
>>>> +                   OV05C10_DGTL_GAIN_MIN, OV05C10_DGTL_GAIN_MAX,
>>>> +                   OV05C10_DGTL_GAIN_STEP, OV05C10_DGTL_GAIN_DEFAULT);
>>>> +
>>>> + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov05c10_ctrl_ops,
>>>> +                              V4L2_CID_TEST_PATTERN,
>>>> +                              ARRAY_SIZE(ov05c10_test_pattern_menu) - 1,
>>>> +                              0, 0, ov05c10_test_pattern_menu);
>>>> +
>>>> + if (ctrl_hdlr->error) {
>>>> +         ret = ctrl_hdlr->error;
>>>> +         dev_err(&client->dev, "V4L2 control init failed (%d)\n", ret);
>>>> +         goto err_hdl_free;
>>>> + }
>>>> +
>>>> + ret = v4l2_fwnode_device_parse(&client->dev, &props);
>>>> + if (ret)
>>>> +         goto err_hdl_free;
>>>> +
>>>> + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov05c10_ctrl_ops,
>>>> +                                       &props);
>>>> + if (ret)
>>>> +         goto err_hdl_free;
>>>> +
>>>> + ov05c10->sd.ctrl_handler = ctrl_hdlr;
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_hdl_free:
>>>> + v4l2_ctrl_handler_free(ctrl_hdlr);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int ov05c10_parse_endpoint(struct device *dev,
>>>> +                           struct fwnode_handle *fwnode)
>>>> +{
>>>> + struct v4l2_fwnode_endpoint bus_cfg = {
>>>> +         .bus_type = V4L2_MBUS_CSI2_DPHY
>>>> + };
>>>> + struct fwnode_handle *ep;
>>>> + unsigned long bitmap;
>>>> + int ret;
>>>> +
>>>> + ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
>>>> + if (!ep) {
>>>> +         dev_err(dev, "Failed to get next endpoint\n");
>>>> +         return -ENXIO;
>>>> + }
>>>> +
>>>> + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
>>>> + fwnode_handle_put(ep);
>>>> + if (ret)
>>>> +         return ret;
>>>> +
>>>> + if (bus_cfg.bus.mipi_csi2.num_data_lanes != supported_mode.lanes) {
>>>> +         dev_err(dev,
>>>> +                 "number of CSI2 data lanes %d is not supported\n",
>>>> +                 bus_cfg.bus.mipi_csi2.num_data_lanes);
>>>> +         ret = -EINVAL;
>>>> +         goto err_endpoint_free;
>>>> + }
>>>> +
>>>> + ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
>>>> +                                bus_cfg.nr_of_link_frequencies,
>>>> +                                ov05c10_link_frequencies,
>>>> +                                ARRAY_SIZE(ov05c10_link_frequencies),
>>>> +                                &bitmap);
>>>> + if (ret)
>>>> +         dev_err(dev, "v4l2_link_freq_to_bitmap fail with %d\n", ret);
>>>> +err_endpoint_free:
>>>> + v4l2_fwnode_endpoint_free(&bus_cfg);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int ov05c10_probe(struct i2c_client *client)
>>>> +{
>>>> + struct ov05c10 *ov05c10;
>>>> + u32 clkfreq;
>>>> + int ret;
>>>> +
>>>> + ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), GFP_KERNEL);
>>>> + if (!ov05c10)
>>>> +         return -ENOMEM;
>>>> +
>>>> + struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
>>>> +
>>>> + ret = fwnode_property_read_u32(fwnode, "clock-frequency", &clkfreq);
>>>
>>> Maybe it's better to separate this part fwnode and GPIO code into a
>>> standalone function?
>>
>> I don't mind, the probe() function isn't very long anyway.
>>
>>>> + if (ret)
>>>> +         return  dev_err_probe(&client->dev, -EINVAL,
>>>> +                               "fail to get clock freq\n");
>>>> + if (clkfreq != OV05C10_REF_CLK)
>>>> +         return dev_err_probe(&client->dev, -EINVAL,
>>>> +                              "fail invalid clock freq %u, %lu expected\n",
>>>> +                              clkfreq, OV05C10_REF_CLK);
>>>> +
>>>> + ret = ov05c10_parse_endpoint(&client->dev, fwnode);
>>>> + if (ret)
>>>> +         return dev_err_probe(&client->dev, -EINVAL,
>>>> +                              "fail to parse endpoint\n");
>>>> +
>>>> + ov05c10->enable_gpio = devm_gpiod_get(&client->dev, "enable",
>>>> +                                       GPIOD_OUT_LOW);
>>>> + if (IS_ERR(ov05c10->enable_gpio))
>>>> +         return dev_err_probe(&client->dev,
>>>> +                              PTR_ERR(ov05c10->enable_gpio),
>>>> +                              "fail to get enable gpio\n");
>>>> +
>>>> + v4l2_i2c_subdev_init(&ov05c10->sd, client, &ov05c10_subdev_ops);
>>>> +
>>>> + ov05c10->regmap = devm_cci_regmap_init_i2c(client, 8);
>>>> + if (IS_ERR(ov05c10->regmap))
>>>> +         return dev_err_probe(&client->dev, PTR_ERR(ov05c10->regmap),
>>>> +                              "fail to init cci\n");
>>>> +
>>>> + ov05c10->cur_page = -1;
>>>> +
>>>> + ret = ov05c10_init_controls(ov05c10);
>>>> + if (ret)
>>>> +         return dev_err_probe(&client->dev, ret, "fail to init ctl\n");
>>>> +
>>>> + ov05c10->sd.internal_ops = &ov05c10_internal_ops;
>>>> + ov05c10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>>> + ov05c10->sd.entity.ops = &ov05c10_subdev_entity_ops;
>>>> + ov05c10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
>>>> +
>>>> + ov05c10->pad.flags = MEDIA_PAD_FL_SOURCE;
>>>> +
>>>> + ret = media_entity_pads_init(&ov05c10->sd.entity, NUM_OF_PADS,
>>>> +                              &ov05c10->pad);
>>>> + if (ret)
>>>> +         goto err_hdl_free;
>>>> +
>>>> + ret = v4l2_subdev_init_finalize(&ov05c10->sd);
>>>> + if (ret < 0)
>>>> +         goto err_media_entity_cleanup;
>>>> +
>>>> + ret = v4l2_async_register_subdev_sensor(&ov05c10->sd);
>>>> + if (ret)
>>>> +         goto err_media_entity_cleanup;
>>>> +
>>>> + pm_runtime_set_active(&client->dev);
>>>> + pm_runtime_enable(&client->dev);
>>>> + pm_runtime_idle(&client->dev);
>>>> + pm_runtime_set_autosuspend_delay(&client->dev, 1000);
>>>> + pm_runtime_use_autosuspend(&client->dev);
>>>> + return 0;
>>>> +
>>>> +err_media_entity_cleanup:
>>>> + media_entity_cleanup(&ov05c10->sd.entity);
>>>> +
>>>> +err_hdl_free:
>>>> + v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static void ov05c10_remove(struct i2c_client *client)
>>>> +{
>>>> + struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>>> + struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>>> +
>>>> + v4l2_async_unregister_subdev(sd);
>>>> + media_entity_cleanup(&sd->entity);
>>>> + v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
>>>> +
>>>> + pm_runtime_disable(&client->dev);
>>>> + pm_runtime_set_suspended(&client->dev);
>>>> +}
>>>> +
>>>> +static int ov05c10_runtime_resume(struct device *dev)
>>>> +{
>>>> + struct v4l2_subdev *sd = dev_get_drvdata(dev);
>>>> + struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>>> +
>>>> + ov05c10_sensor_power_set(ov05c10, true);
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int ov05c10_runtime_suspend(struct device *dev)
>>>> +{
>>>> + struct v4l2_subdev *sd = dev_get_drvdata(dev);
>>>> + struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>>> +
>>>> + ov05c10_sensor_power_set(ov05c10, false);
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static DEFINE_RUNTIME_DEV_PM_OPS(ov05c10_pm_ops, ov05c10_runtime_suspend,
>>>> +                          ov05c10_runtime_resume, NULL);
>>>> +
>>>> +static const struct i2c_device_id ov05c10_i2c_ids[] = {
>>>> + {"ov05c10", 0 },
>>>> + { }
>>>> +};
>>>> +MODULE_DEVICE_TABLE(i2c, ov05c10_i2c_ids);
>>>> +
>>>> +static struct i2c_driver ov05c10_i2c_driver = {
>>>> + .driver = {
>>>> +         .name = DRV_NAME,
>>>> +         .pm = pm_ptr(&ov05c10_pm_ops),
>>>> + },
>>>> + .id_table = ov05c10_i2c_ids,
>>>> + .probe = ov05c10_probe,
>>>> + .remove = ov05c10_remove,
>>>> +};
>>>> +
>>>> +module_i2c_driver(ov05c10_i2c_driver);
>>>> +
>>>> +MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
>>>> +MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@amd.com>");
>>>> +MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
>>>> +MODULE_DESCRIPTION("OmniVision OV05C1010 sensor driver");
>>>
>>> OV05C10
>>>
>>>> +MODULE_LICENSE("GPL");
>>>
>>> Hi Sakari,
>>>
>>> Seems there are already several camera sensors using page-based registers.
>>> Is it a good idea to add page support in CCI interface?
>>
>> Sounds like a good idea as such but I'm not sure how common this really is,
>> I think I've seen a few Omnivision sensors doing this. If implemented, I
>> think it would be nice if the page could be encoded in the register address
>> which V4L2 CCI would store and switch page if needed only. This would
>> require serialising accesses, too. There's some room in CCI register raw
>> value space so this could be done without even changing that, say, with
>> 8-bit page and 8-bit register address.
> 
> Ack. I've worked on a driver for the AP1302 external ISP, which also
> uses pages registers. The full address space spans 32 bits though, but
> fortunately the driver doesn't need to access anything above 0x00ffffff.
> 
> --
> Regards,
> 
> Laurent Pinchart


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-15  0:09 ` Laurent Pinchart
@ 2025-06-16 22:49   ` Nirujogi, Pratap
  2025-06-23 21:51     ` Nirujogi, Pratap
  0 siblings, 1 reply; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-16 22:49 UTC (permalink / raw)
  To: Laurent Pinchart, Pratap Nirujogi
  Cc: mchehab, sakari.ailus, hverkuil, bryan.odonoghue, krzk,
	dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen, linux-media,
	linux-kernel, benjamin.chan, bin.du, grosikop, king.li, dantony,
	vengutta

Hi Laurent,

On 6/14/2025 8:09 PM, Laurent Pinchart wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> Hi Pratap,
> 
> Thank you for the patch.
> 
> On Mon, Jun 09, 2025 at 03:42:22PM -0400, Pratap Nirujogi wrote:
>> Add driver for OmniVision 5.2M OV05C10 sensor. This driver
>> supports only the full size normal 2888x1808@30fps 2-lane
>> sensor profile.
>>
>> Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>> Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>> Co-developed-by: Bin Du <bin.du@amd.com>
>> Signed-off-by: Bin Du <bin.du@amd.com>
>> Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
>> ---
>> Changes v2 -> v3:
>>
>> * Update "refclk" property variable as "clock-frequency".
>> * Update sensor GPIO connector id name.
>> * Fix sensor v4l2 compliance issue.
>> * Fix license info.
>> * Address review comments.
>>
>>   MAINTAINERS                 |    8 +
>>   drivers/media/i2c/Kconfig   |   10 +
>>   drivers/media/i2c/Makefile  |    1 +
>>   drivers/media/i2c/ov05c10.c | 1061 +++++++++++++++++++++++++++++++++++
>>   4 files changed, 1080 insertions(+)
>>   create mode 100644 drivers/media/i2c/ov05c10.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index a92290fffa16..caca25d00bf2 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -18303,6 +18303,14 @@ T:   git git://linuxtv.org/media.git
>>   F:   Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml
>>   F:   drivers/media/i2c/ov02e10.c
>>
>> +OMNIVISION OV05C10 SENSOR DRIVER
>> +M:   Nirujogi Pratap <pratap.nirujogi@amd.com>
>> +M:   Bin Du <bin.du@amd.com>
>> +L:   linux-media@vger.kernel.org
>> +S:   Maintained
>> +T:   git git://linuxtv.org/media.git
>> +F:   drivers/media/i2c/ov05c10.c
>> +
>>   OMNIVISION OV08D10 SENSOR DRIVER
>>   M:   Jimmy Su <jimmy.su@intel.com>
>>   L:   linux-media@vger.kernel.org
>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>> index e68202954a8f..1662fb29d75c 100644
>> --- a/drivers/media/i2c/Kconfig
>> +++ b/drivers/media/i2c/Kconfig
>> @@ -377,6 +377,16 @@ config VIDEO_OV02C10
>>          To compile this driver as a module, choose M here: the
>>          module will be called ov02c10.
>>
>> +config VIDEO_OV05C10
>> +     tristate "OmniVision OV05C10 sensor support"
>> +     select V4L2_CCI_I2C
>> +     help
>> +       This is a Video4Linux2 sensor driver for the OmniVision
>> +       OV05C10 camera.
>> +
>> +       To compile this driver as a module, choose M here: the
>> +       module will be called OV05C10.
>> +
>>   config VIDEO_OV08D10
>>           tristate "OmniVision OV08D10 sensor support"
>>           help
>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>> index 5873d29433ee..b4a1d721a7f2 100644
>> --- a/drivers/media/i2c/Makefile
>> +++ b/drivers/media/i2c/Makefile
>> @@ -85,6 +85,7 @@ obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
>>   obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
>>   obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o
>>   obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o
>> +obj-$(CONFIG_VIDEO_OV05C10) += ov05c10.o
>>   obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
>>   obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
>>   obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
>> diff --git a/drivers/media/i2c/ov05c10.c b/drivers/media/i2c/ov05c10.c
>> new file mode 100644
>> index 000000000000..9a1e493c4073
>> --- /dev/null
>> +++ b/drivers/media/i2c/ov05c10.c
>> @@ -0,0 +1,1061 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +// Copyright (C) 2025 Advanced Micro Devices, Inc.
>> +
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/gpio.h>
>> +#include <linux/i2c.h>
>> +#include <linux/module.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/units.h>
>> +#include <media/v4l2-cci.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-fwnode.h>
>> +
>> +#define DRV_NAME                     "ov05c10"
>> +#define OV05C10_REF_CLK                      (24 * HZ_PER_MHZ)
>> +
>> +#define MODE_WIDTH  2888
>> +#define MODE_HEIGHT 1808
>> +
>> +#define PAGE_NUM_MASK                        0xff000000
>> +#define PAGE_NUM_SHIFT                       24
>> +#define REG_ADDR_MASK                        0x00ffffff
>> +
>> +#define OV05C10_SYSCTL_PAGE          (0 << PAGE_NUM_SHIFT)
>> +#define OV05C10_CISCTL_PAGE          (1 << PAGE_NUM_SHIFT)
>> +#define OV05C10_ISPCTL_PAGE          (4 << PAGE_NUM_SHIFT)
>> +
>> +/* Chip ID */
>> +#define OV05C10_REG_CHIP_ID          (CCI_REG24(0x00) | OV05C10_SYSCTL_PAGE)
>> +#define OV05C10_CHIP_ID                      0x43055610
>> +
>> +/* Control registers */
>> +#define OV05C10_REG_TRIGGER          (CCI_REG8(0x01) | OV05C10_CISCTL_PAGE)
>> +#define OV05C_REG_TRIGGER_START              BIT(0)
>> +
>> +/* Exposure control */
>> +#define OV05C10_REG_EXPOSURE         (CCI_REG24(0x02) | OV05C10_CISCTL_PAGE)
>> +#define OV05C10_EXPOSURE_MAX_MARGIN  33
>> +#define OV05C10_EXPOSURE_MIN         4
>> +#define OV05C10_EXPOSURE_STEP                1
>> +#define OV05C10_EXPOSURE_DEFAULT     0x40
>> +
>> +/* V_TIMING internal */
>> +#define OV05C10_REG_VTS                      (CCI_REG16(0x05) | OV05C10_CISCTL_PAGE)
>> +#define OV05C10_VTS_30FPS            1860
>> +#define OV05C10_VTS_MAX                      0x7fff
>> +
>> +/* Test Pattern Control */
>> +#define OV05C10_REG_TEST_PATTERN     (CCI_REG8(0x12) | OV05C10_ISPCTL_PAGE)
>> +#define OV05C10_TEST_PATTERN_ENABLE  BIT(0)
>> +#define OV05C10_REG_TEST_PATTERN_CTL (CCI_REG8(0xf3) | OV05C10_ISPCTL_PAGE)
>> +#define OV05C10_REG_TEST_PATTERN_XXX BIT(0)
> 
> What's XXX ?
> 
I agree, XXX is not an appropriate name, its arbitrarily picked to 
distinguish it from TEST_PATTERN_ENABLE, we will update it with a 
meaninful name.

>> +
>> +/* Digital gain control */
>> +#define OV05C10_REG_DGTL_GAIN_H              (CCI_REG8(0x21) | OV05C10_CISCTL_PAGE)
>> +#define OV05C10_REG_DGTL_GAIN_L              (CCI_REG8(0x22) | OV05C10_CISCTL_PAGE)
> 
> Can you make this a 16-bit register ?
> 
> #define OV05C10_REG_DGTL_GAIN           (CCI_REG16(0x21) | OV05C10_CISCTL_PAGE)
> 
sure, we will do that.

>> +
>> +#define OV05C10_DGTL_GAIN_MIN                0x40
>> +#define OV05C10_DGTL_GAIN_MAX                0xff
>> +#define OV05C10_DGTL_GAIN_DEFAULT    0x40
>> +#define OV05C10_DGTL_GAIN_STEP               1
>> +
>> +#define OV05C10_DGTL_GAIN_L_MASK     0xff
>> +#define OV05C10_DGTL_GAIN_H_SHIFT    8
>> +#define OV05C10_DGTL_GAIN_H_MASK     0xff00
>> +
>> +/* Analog gain control */
>> +#define OV05C10_REG_ANALOG_GAIN              (CCI_REG8(0x24) | OV05C10_CISCTL_PAGE)
>> +#define OV05C10_ANA_GAIN_MIN         0x80
>> +#define OV05C10_ANA_GAIN_MAX         0x07c0
>> +#define OV05C10_ANA_GAIN_STEP                1
>> +#define OV05C10_ANA_GAIN_DEFAULT     0x80
>> +
>> +/* H TIMING internal */
>> +#define OV05C10_REG_HTS                      (CCI_REG16(0x37) | OV05C10_CISCTL_PAGE)
>> +#define OV05C10_HTS_30FPS            0x0280
>> +
>> +/* Page selection */
>> +#define OV05C10_REG_PAGE_CTL         CCI_REG8(0xfd)
>> +
>> +#define NUM_OF_PADS 1
> 
> OV05C10_NUM_OF_PADS
> 
Thanks, it will be changed.

>> +
>> +#define OV05C10_GET_PAGE_NUM(reg)    (((reg) & PAGE_NUM_MASK) >>\
>> +                                      PAGE_NUM_SHIFT)
>> +#define OV05C10_GET_REG_ADDR(reg)    ((reg) & REG_ADDR_MASK)
>> +
>> +enum {
>> +     OV05C10_LINK_FREQ_900MHZ_INDEX,
>> +};
>> +
>> +struct ov05c10_reg_list {
>> +     u32 num_of_regs;
>> +     const struct cci_reg_sequence *regs;
>> +};
>> +
>> +/* Mode : resolution and related config&values */
>> +struct ov05c10_mode {
>> +     /* Frame width */
>> +     u32 width;
>> +     /* Frame height */
>> +     u32 height;
>> +     /* number of lanes */
>> +     u32 lanes;
>> +
>> +     /* V-timing */
>> +     u32 vts_def;
>> +     u32 vts_min;
>> +
>> +     /* HTS */
>> +     u32 hts;
>> +
>> +     /* Index of Link frequency config to be used */
>> +     u32 link_freq_index;
>> +
>> +     /* Default register values */
>> +     struct ov05c10_reg_list reg_list;
>> +};
>> +
>> +static const s64 ov05c10_link_frequencies[] = {
>> +     925 * HZ_PER_MHZ,
>> +};
>> +
>> +/* 2888x1808 30fps, 1800mbps, 2lane, 24mhz */
>> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
>> +     { CCI_REG8(0xfd),  0x00 },
>> +     { CCI_REG8(0x20),  0x00 },
>> +     { CCI_REG8(0xfd),  0x00 },
>> +     { CCI_REG8(0x20),  0x0b },
>> +     { CCI_REG8(0xc1),  0x09 },
>> +     { CCI_REG8(0x21),  0x06 },
>> +     { CCI_REG8(0x14),  0x78 },
>> +     { CCI_REG8(0xe7),  0x03 },
>> +     { CCI_REG8(0xe7),  0x00 },
>> +     { CCI_REG8(0x21),  0x00 },
>> +     { CCI_REG8(0xfd),  0x01 },
>> +     { CCI_REG8(0x03),  0x00 },
>> +     { CCI_REG8(0x04),  0x06 },
>> +     { CCI_REG8(0x05),  0x07 },
>> +     { CCI_REG8(0x06),  0x44 },
>> +     { CCI_REG8(0x07),  0x08 },
>> +     { CCI_REG8(0x1b),  0x01 },
>> +     { CCI_REG8(0x24),  0xff },
>> +     { CCI_REG8(0x32),  0x03 },
>> +     { CCI_REG8(0x42),  0x5d },
>> +     { CCI_REG8(0x43),  0x08 },
>> +     { CCI_REG8(0x44),  0x81 },
>> +     { CCI_REG8(0x46),  0x5f },
>> +     { CCI_REG8(0x48),  0x18 },
>> +     { CCI_REG8(0x49),  0x04 },
>> +     { CCI_REG8(0x5c),  0x18 },
>> +     { CCI_REG8(0x5e),  0x13 },
>> +     { CCI_REG8(0x70),  0x15 },
>> +     { CCI_REG8(0x77),  0x35 },
>> +     { CCI_REG8(0x79),  0x00 },
>> +     { CCI_REG8(0x7b),  0x08 },
>> +     { CCI_REG8(0x7d),  0x08 },
>> +     { CCI_REG8(0x7e),  0x08 },
>> +     { CCI_REG8(0x7f),  0x08 },
>> +     { CCI_REG8(0x90),  0x37 },
>> +     { CCI_REG8(0x91),  0x05 },
>> +     { CCI_REG8(0x92),  0x18 },
>> +     { CCI_REG8(0x93),  0x27 },
>> +     { CCI_REG8(0x94),  0x05 },
>> +     { CCI_REG8(0x95),  0x38 },
>> +     { CCI_REG8(0x9b),  0x00 },
>> +     { CCI_REG8(0x9c),  0x06 },
>> +     { CCI_REG8(0x9d),  0x28 },
>> +     { CCI_REG8(0x9e),  0x06 },
>> +     { CCI_REG8(0xb2),  0x0f },
>> +     { CCI_REG8(0xb3),  0x29 },
>> +     { CCI_REG8(0xbf),  0x3c },
>> +     { CCI_REG8(0xc2),  0x04 },
>> +     { CCI_REG8(0xc4),  0x00 },
>> +     { CCI_REG8(0xca),  0x20 },
>> +     { CCI_REG8(0xcb),  0x20 },
>> +     { CCI_REG8(0xcc),  0x28 },
>> +     { CCI_REG8(0xcd),  0x28 },
>> +     { CCI_REG8(0xce),  0x20 },
>> +     { CCI_REG8(0xcf),  0x20 },
>> +     { CCI_REG8(0xd0),  0x2a },
>> +     { CCI_REG8(0xd1),  0x2a },
>> +     { CCI_REG8(0xfd),  0x0f },
>> +     { CCI_REG8(0x00),  0x00 },
>> +     { CCI_REG8(0x01),  0xa0 },
>> +     { CCI_REG8(0x02),  0x48 },
>> +     { CCI_REG8(0x07),  0x8f },
>> +     { CCI_REG8(0x08),  0x70 },
>> +     { CCI_REG8(0x09),  0x01 },
>> +     { CCI_REG8(0x0b),  0x40 },
>> +     { CCI_REG8(0x0d),  0x07 },
>> +     { CCI_REG8(0x11),  0x33 },
>> +     { CCI_REG8(0x12),  0x77 },
>> +     { CCI_REG8(0x13),  0x66 },
>> +     { CCI_REG8(0x14),  0x65 },
>> +     { CCI_REG8(0x15),  0x37 },
>> +     { CCI_REG8(0x16),  0xbf },
>> +     { CCI_REG8(0x17),  0xff },
>> +     { CCI_REG8(0x18),  0xff },
>> +     { CCI_REG8(0x19),  0x12 },
>> +     { CCI_REG8(0x1a),  0x10 },
>> +     { CCI_REG8(0x1c),  0x77 },
>> +     { CCI_REG8(0x1d),  0x77 },
>> +     { CCI_REG8(0x20),  0x0f },
>> +     { CCI_REG8(0x21),  0x0f },
>> +     { CCI_REG8(0x22),  0x0f },
>> +     { CCI_REG8(0x23),  0x0f },
>> +     { CCI_REG8(0x2b),  0x20 },
>> +     { CCI_REG8(0x2c),  0x20 },
>> +     { CCI_REG8(0x2d),  0x04 },
>> +     { CCI_REG8(0xfd),  0x03 },
>> +     { CCI_REG8(0x9d),  0x0f },
>> +     { CCI_REG8(0x9f),  0x40 },
>> +     { CCI_REG8(0xfd),  0x00 },
>> +     { CCI_REG8(0x20),  0x1b },
>> +     { CCI_REG8(0xfd),  0x04 },
>> +     { CCI_REG8(0x19),  0x60 },
>> +     { CCI_REG8(0xfd),  0x02 },
>> +     { CCI_REG8(0x75),  0x05 },
>> +     { CCI_REG8(0x7f),  0x06 },
>> +     { CCI_REG8(0x9a),  0x03 },
>> +     { CCI_REG8(0xa2),  0x07 },
>> +     { CCI_REG8(0xa3),  0x10 },
>> +     { CCI_REG8(0xa5),  0x02 },
>> +     { CCI_REG8(0xa6),  0x0b },
>> +     { CCI_REG8(0xa7),  0x48 },
>> +     { CCI_REG8(0xfd),  0x07 },
>> +     { CCI_REG8(0x42),  0x00 },
>> +     { CCI_REG8(0x43),  0x80 },
>> +     { CCI_REG8(0x44),  0x00 },
>> +     { CCI_REG8(0x45),  0x80 },
>> +     { CCI_REG8(0x46),  0x00 },
>> +     { CCI_REG8(0x47),  0x80 },
>> +     { CCI_REG8(0x48),  0x00 },
>> +     { CCI_REG8(0x49),  0x80 },
>> +     { CCI_REG8(0x00),  0xf7 },
>> +     { CCI_REG8(0xfd),  0x00 },
>> +     { CCI_REG8(0xe7),  0x03 },
>> +     { CCI_REG8(0xe7),  0x00 },
>> +     { CCI_REG8(0xfd),  0x00 },
>> +     { CCI_REG8(0x93),  0x18 },
>> +     { CCI_REG8(0x94),  0xff },
>> +     { CCI_REG8(0x95),  0xbd },
>> +     { CCI_REG8(0x96),  0x1a },
>> +     { CCI_REG8(0x98),  0x04 },
>> +     { CCI_REG8(0x99),  0x08 },
>> +     { CCI_REG8(0x9b),  0x10 },
>> +     { CCI_REG8(0x9c),  0x3f },
>> +     { CCI_REG8(0xa1),  0x05 },
>> +     { CCI_REG8(0xa4),  0x2f },
>> +     { CCI_REG8(0xc0),  0x0c },
>> +     { CCI_REG8(0xc1),  0x08 },
>> +     { CCI_REG8(0xc2),  0x00 },
>> +     { CCI_REG8(0xb6),  0x20 },
>> +     { CCI_REG8(0xbb),  0x80 },
>> +     { CCI_REG8(0xfd),  0x00 },
>> +     { CCI_REG8(0xa0),  0x01 },
>> +     { CCI_REG8(0xfd),  0x01 },
>> +};
>> +
>> +static const struct cci_reg_sequence mode_OV05C10_stream_on_regs[] = {
>> +     { CCI_REG8(0xfd), 0x01 },
>> +     { CCI_REG8(0x33), 0x03 },
>> +     { CCI_REG8(0x01), 0x02 },
>> +     { CCI_REG8(0xfd), 0x00 },
>> +     { CCI_REG8(0x20), 0x1f },
>> +     { CCI_REG8(0xfd), 0x01 },
>> +};
>> +
>> +static const struct cci_reg_sequence mode_OV05C10_stream_off_regs[] = {
>> +     { CCI_REG8(0xfd), 0x00 },
>> +     { CCI_REG8(0x20), 0x5b },
>> +     { CCI_REG8(0xfd), 0x01 },
>> +     { CCI_REG8(0x33), 0x02 },
>> +     { CCI_REG8(0x01), 0x02 },
>> +};
> 
> Add named macros for the registers you set when starting or stopping
> streaming, as well as macros for the register fields.
> 
Thanks, we’ll reach out to the vendor to see if we can get the 
descriptions for these registers.

>> +
>> +static const char * const ov05c10_test_pattern_menu[] = {
>> +     "Disabled",
>> +     "Vertical Color Bar Type 1",
>> +     "Vertical Color Bar Type 2",
>> +     "Vertical Color Bar Type 3",
>> +     "Vertical Color Bar Type 4"
>> +};
> 
> Move this just above ov05c10_init_controls().
> 
>> +
>> +/* Configurations for supported link frequencies */
>> +#define OV05C10_LINK_FREQ_900MHZ     (900 * HZ_PER_MHZ)
>> +
>> +/* Number of lanes supported */
>> +#define OV05C10_DATA_LANES           2
>> +
>> +/* Bits per sample of sensor output */
>> +#define OV05C10_BITS_PER_SAMPLE              10
> 
> Move the macros above, with the other ones.
> 
Thanks, it will be moved in next version.
>> +
>> +/*
>> + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
>> + * data rate => double data rate; number of lanes => 2; bits per pixel => 10
>> + */
>> +static u64 link_freq_to_pixel_rate(u64 f, u32 lane_nr)
>> +{
>> +     f *= 2 * lane_nr;
>> +     do_div(f, OV05C10_BITS_PER_SAMPLE);
>> +
>> +     return f;
>> +}
>> +
>> +/* Menu items for LINK_FREQ V4L2 control */
>> +static const s64 ov05c10_link_freq_menu_items[] = {
>> +     OV05C10_LINK_FREQ_900MHZ,
>> +};
>> +
>> +/* Mode configs, currently, only support 1 mode */
>> +static const struct ov05c10_mode supported_mode = {
>> +     .width = MODE_WIDTH,
>> +     .height = MODE_HEIGHT,
>> +     .vts_def = OV05C10_VTS_30FPS,
>> +     .vts_min = OV05C10_VTS_30FPS,
>> +     .hts = 640,
>> +     .lanes = 2,
>> +     .reg_list = {
>> +             .num_of_regs = ARRAY_SIZE(ov05c10_2888x1808_regs),
>> +             .regs = ov05c10_2888x1808_regs,
>> +     },
>> +     .link_freq_index = OV05C10_LINK_FREQ_900MHZ_INDEX,
>> +};
>> +
>> +struct ov05c10 {
>> +     struct v4l2_subdev sd;
>> +     struct media_pad pad;
>> +
>> +     /* V4L2 control handler */
>> +     struct v4l2_ctrl_handler ctrl_handler;
>> +
>> +     /* V4L2 Controls */
>> +     struct v4l2_ctrl *link_freq;
>> +     struct v4l2_ctrl *pixel_rate;
>> +     struct v4l2_ctrl *vblank;
>> +     struct v4l2_ctrl *hblank;
>> +     struct v4l2_ctrl *exposure;
>> +
>> +     struct regmap *regmap;
>> +
>> +     /* gpio descriptor */
>> +     struct gpio_desc *enable_gpio;
>> +
>> +     /* Current page for sensor register control */
>> +     int cur_page;
>> +};
>> +
>> +#define to_ov05c10(_sd)      container_of(_sd, struct ov05c10, sd)
> 
> We try to use inline functions for this, to improve type safety.
> 
> static inline struct ov05c10 *to_ov05c10(struct v4l2_subdev *_sd)
> {
>          return container_of(_sd, struct ov05c10, sd);
> }
> 
I agree, this will be fixed in next version

>> +
>> +static int ov05c10_init_state(struct v4l2_subdev *sd,
>> +                           struct v4l2_subdev_state *sd_state)
> 
> Move this function below with the other subdev ops, after
> ov05c10_disable_streams().
> 
>> +{
>> +     struct v4l2_mbus_framefmt *frame_fmt;
>> +     struct v4l2_subdev_format fmt = {
> 
> static const
> 
Oops. Yes this will be fixed as well in next version

>> +             .which = V4L2_SUBDEV_FORMAT_TRY,
>> +             .format = {
>> +                     .width = MODE_WIDTH,
>> +                     .height = MODE_HEIGHT,
>> +                     .code = MEDIA_BUS_FMT_SGRBG10_1X10,
>> +                     .field = V4L2_FIELD_NONE,
> 
> You also need to set the colorspace fields.
> 
Thanks, it will be added in next version.

>> +             }
>> +     };
>> +
>> +     frame_fmt = v4l2_subdev_state_get_format(sd_state, 0);
>> +     *frame_fmt = fmt.format;
>> +     return 0;
>> +}
>> +
>> +static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page, int *err)
>> +{
>> +     int ret = 0;
>> +
>> +     if (err && *err)
>> +             return *err;
> 
> This function is never called with *err != 0. I think you can simplify
> it by dropping the err argument:
> 
> static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page)
> {
>          int ret;
> 
>          if (page == ov05c10->cur_page)
>                  return 0;
> 
>          ret = cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, NULL);
>          if (ret)
>                  return ret;
> 
>          ov05c10->cur_page = page;
> 
>          return 0;
> }
> 
Thanks for the proposal, it will be updated in next version.

>> +
>> +     if (page != ov05c10->cur_page) {
>> +             cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, &ret);
>> +             if (!ret)
>> +                     ov05c10->cur_page = page;
>> +     }
>> +
>> +     if (err)
>> +             *err = ret;
>> +
>> +     return ret;
>> +}
>> +
>> +/* refer to the implementation of cci_read */
>> +static int ov05c10_reg_read(struct ov05c10 *ov05c10, u32 reg,
>> +                         u64 *val, int *err)
>> +{
>> +     u32 page;
>> +     u32 addr;
>> +     int ret = 0;
>> +
>> +     if (err && *err)
>> +             return *err;
>> +
>> +     page = OV05C10_GET_PAGE_NUM(reg);
>> +     addr = OV05C10_GET_REG_ADDR(reg);
>> +     ov05c10_switch_page(ov05c10, page, &ret);
>> +     cci_read(ov05c10->regmap, addr, val, &ret);
>> +     if (err)
>> +             *err = ret;
>> +
>> +     return ret;
>> +}
>> +
>> +/* refer to the implementation of cci_write */
>> +static int ov05c10_reg_write(struct ov05c10 *ov05c10, u32 reg,
>> +                          u64 val, int *err)
>> +{
>> +     u32 page;
>> +     u32 addr;
>> +     int ret = 0;
>> +
>> +     if (err && *err)
>> +             return *err;
>> +
>> +     page = OV05C10_GET_PAGE_NUM(reg);
>> +     addr = OV05C10_GET_REG_ADDR(reg);
>> +     ov05c10_switch_page(ov05c10, page, &ret);
>> +     cci_write(ov05c10->regmap, addr, val, &ret);
>> +     if (err)
>> +             *err = ret;
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_update_vblank(struct ov05c10 *ov05c10, u32 vblank)
>> +{
>> +     const struct ov05c10_mode *mode = &supported_mode;
> 
> To prepare for making the sensor freely configurable, let's not access
> modes here. You can get the data you need from the format retrieved from
> the active state.
> 
Yes, I agree, will update in the next version.

>> +     u64 val;
> 
> u32 is enough. Same below.
> 
Thanks, this will be fixed.

>> +     int ret = 0;
>> +
>> +     val = mode->height + vblank;
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_VTS, val, &ret);
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>> +                       OV05C_REG_TRIGGER_START, &ret);
> 
> Does the OV05C10_REG_TRIGGER register need to be set after any change to
> controls ? If so you could move it to the end of the ov05c10_set_ctrl()
> function.
> 
I will check that but i think so. Will be moved to the end of the function.
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_update_exposure(struct ov05c10 *ov05c10, u32 exposure)
>> +{
>> +     int ret = 0;
>> +
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_EXPOSURE, exposure, &ret);
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>> +                       OV05C_REG_TRIGGER_START, &ret);
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_update_analog_gain(struct ov05c10 *ov05c10, u32 a_gain)
>> +{
>> +     int ret = 0;
>> +
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_ANALOG_GAIN, a_gain, &ret);
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>> +                       OV05C_REG_TRIGGER_START, &ret);
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_update_digital_gain(struct ov05c10 *ov05c10, u32 d_gain)
>> +{
>> +     u64 val;
>> +     int ret = 0;
>> +
>> +     val = d_gain & OV05C10_DGTL_GAIN_L_MASK;
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_L, val, &ret);
>> +
>> +     val = (d_gain & OV05C10_DGTL_GAIN_H_MASK) >> OV05C10_DGTL_GAIN_H_SHIFT;
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_H, val, &ret);
>> +
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>> +                       OV05C_REG_TRIGGER_START, &ret);
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_enable_test_pattern(struct ov05c10 *ov05c10, u32 pattern)
>> +{
>> +     u64 val;
>> +     int ret = 0;
>> +
>> +     if (pattern) {
>> +             ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
>> +                              &val, &ret);
>> +             ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
>> +                               val | OV05C10_REG_TEST_PATTERN_XXX, &ret);
>> +             ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
>> +             val |= OV05C10_TEST_PATTERN_ENABLE;
>> +     } else {
>> +             ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
>> +             val &= ~OV05C10_TEST_PATTERN_ENABLE;
>> +     }
>> +
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN, val, &ret);
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>> +                       OV05C_REG_TRIGGER_START, &ret);
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_set_ctrl(struct v4l2_ctrl *ctrl)
>> +{
>> +     struct ov05c10 *ov05c10 = container_of(ctrl->handler,
>> +                                            struct ov05c10, ctrl_handler);
>> +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
> 
> You use client solely to access client->dev. Store a struct device *dev
> in struct ov05c10, and access it below. Same in the rest of the driver.
> 
Thanks, will do that.

>> +     const struct ov05c10_mode *mode = &supported_mode;
> 
> Here too you can get the data you need from the format retrieved from
> the active state.
> 
I agree this will be fixed.

>> +     s64 max;
>> +     int ret = 0;
>> +
>> +     /* Propagate change of current control to all related controls */
>> +     if (ctrl->id == V4L2_CID_VBLANK) {
>> +             s64 cur_exp = ov05c10->exposure->cur.val;
>> +
>> +             /* Update max exposure while meeting expected vblanking */
>> +             max = mode->height + ctrl->val - OV05C10_EXPOSURE_MAX_MARGIN;
>> +             cur_exp = clamp(cur_exp, ov05c10->exposure->minimum, max);
>> +             ret = __v4l2_ctrl_modify_range(ov05c10->exposure,
>> +                                            ov05c10->exposure->minimum,
>> +                                            max, ov05c10->exposure->step,
>> +                                            cur_exp);
>> +             if (!ret)
>> +                     return ret;
>> +     }
>> +
>> +     /*
>> +      * Applying V4L2 control value only happens
>> +      * when power is up for streaming
>> +      */
>> +     if (!pm_runtime_get_if_in_use(&client->dev))
>> +             return 0;
>> +
>> +     switch (ctrl->id) {
>> +     case V4L2_CID_ANALOGUE_GAIN:
>> +             ret = ov05c10_update_analog_gain(ov05c10, ctrl->val);
>> +             break;
>> +     case V4L2_CID_DIGITAL_GAIN:
>> +             ret = ov05c10_update_digital_gain(ov05c10, ctrl->val);
>> +             break;
>> +     case V4L2_CID_EXPOSURE:
>> +             ret = ov05c10_update_exposure(ov05c10, ctrl->val);
>> +             break;
>> +     case V4L2_CID_VBLANK:
>> +             ret = ov05c10_update_vblank(ov05c10, ctrl->val);
>> +             break;
>> +     case V4L2_CID_TEST_PATTERN:
>> +             ret = ov05c10_enable_test_pattern(ov05c10, ctrl->val);
>> +             break;
>> +     default:
>> +             ret = -ENOTTY;
>> +             dev_err(&client->dev,
>> +                     "ctrl(id:0x%x,val:0x%x) is not handled\n",
>> +                     ctrl->id, ctrl->val);
>> +             break;
>> +     }
>> +
>> +     pm_runtime_put(&client->dev);
> 
> Use the autosuspend variant (and unless the pm_runtime_mark_last_busy()
> has been folded in - Sakari has submitted patches - you will need to
> call it too).
> 
Ok, I will check the Sakari patches and include the changes in next version.

>> +
>> +     return ret;
>> +}
>> +
>> +static const struct v4l2_ctrl_ops ov05c10_ctrl_ops = {
>> +     .s_ctrl = ov05c10_set_ctrl,
>> +};
>> +
>> +static int ov05c10_enum_mbus_code(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *sd_state,
>> +                               struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +     /* Only one bayer order(GRBG) is supported */
>> +     if (code->index > 0)
>> +             return -EINVAL;
>> +
>> +     code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
>> +
>> +     return 0;
>> +}
>> +
>> +static int ov05c10_enum_frame_size(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *sd_state,
>> +                                struct v4l2_subdev_frame_size_enum *fse)
>> +{
>> +     /* ov05c10 driver currently only supports 1 mode*/
>> +     if (fse->index != 0)
>> +             return -EINVAL;
>> +
>> +     if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
>> +             return -EINVAL;
>> +
>> +     fse->min_width = supported_mode.width;
>> +     fse->max_width = fse->min_width;
>> +     fse->min_height = supported_mode.height;
>> +     fse->max_height = fse->min_height;
>> +
>> +     return 0;
>> +}
>> +
>> +static void ov05c10_update_pad_format(const struct ov05c10_mode *mode,
>> +                                   struct v4l2_subdev_format *fmt)
>> +{
>> +     fmt->format.width = mode->width;
>> +     fmt->format.height = mode->height;
>> +     fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
>> +     fmt->format.field = V4L2_FIELD_NONE;
> 
> Move this code to the single caller below.
> 
> You also need to handle the colorspace fields.
> 
Thanks, will be fixed in the next version.

>> +}
>> +
>> +static int ov05c10_set_pad_format(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *sd_state,
>> +                               struct v4l2_subdev_format *fmt)
>> +{
>> +     struct v4l2_mbus_framefmt *framefmt;
>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>> +     const struct ov05c10_mode *mode;
>> +     s32 vblank_def;
>> +     s32 vblank_min;
>> +     s64 pixel_rate;
>> +     s64 link_freq;
>> +     s64 h_blank;
>> +
>> +     /* Only one raw bayer(GRBG) order is supported */
>> +     if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10)
>> +             fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
> 
> Just do
> 
>          /* Only one raw bayer(GRBG) order is supported */
>          fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
> 
> unconditionally.
> 
Thanks, it will be fixed in next version.

>> +
>> +     mode = &supported_mode;
>> +     ov05c10_update_pad_format(mode, fmt);
>> +     if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
>> +             framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
>> +             *framefmt = fmt->format;
> 
> This needs to be done for the active state too.
> 
Thanks, it will be fixed in next version.

>> +     } else {
>> +             __v4l2_ctrl_s_ctrl(ov05c10->link_freq, mode->link_freq_index);
>> +             link_freq = ov05c10_link_freq_menu_items[mode->link_freq_index];
>> +             pixel_rate = link_freq_to_pixel_rate(link_freq,
>> +                                                  mode->lanes);
>> +             __v4l2_ctrl_s_ctrl_int64(ov05c10->pixel_rate, pixel_rate);
>> +
>> +             /* Update limits and set FPS to default */
>> +             vblank_def = mode->vts_def - mode->height;
>> +             vblank_min = mode->vts_min - mode->height;
>> +             __v4l2_ctrl_modify_range(ov05c10->vblank, vblank_min,
>> +                                      OV05C10_VTS_MAX - mode->height,
>> +                                      1, vblank_def);
>> +             __v4l2_ctrl_s_ctrl(ov05c10->vblank, vblank_def);
>> +             h_blank = mode->hts;
>> +             __v4l2_ctrl_modify_range(ov05c10->hblank, h_blank,
>> +                                      h_blank, 1, h_blank);
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int ov05c10_start_streaming(struct ov05c10 *ov05c10)
>> +{
>> +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>> +     const struct ov05c10_mode *mode = &supported_mode;
>> +     const struct ov05c10_reg_list *reg_list;
>> +     int ret = 0;
>> +
>> +     /* Apply default values of current mode */
>> +     reg_list = &mode->reg_list;
>> +     cci_multi_reg_write(ov05c10->regmap, reg_list->regs,
>> +                         reg_list->num_of_regs, &ret);
>> +     if (ret) {
>> +             dev_err(&client->dev, "fail to set mode, ret: %d\n", ret);
>> +             return ret;
>> +     }
>> +
>> +     /* Apply customized values from user */
>> +     ret =  __v4l2_ctrl_handler_setup(ov05c10->sd.ctrl_handler);
>> +     if (ret) {
>> +             dev_err(&client->dev, "failed to setup v4l2 handler %d\n", ret);
>> +             return ret;
>> +     }
>> +
>> +     cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_on_regs,
>> +                         ARRAY_SIZE(mode_OV05C10_stream_on_regs), &ret);
>> +     if (ret)
>> +             dev_err(&client->dev, "fail to start the streaming\n");
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_stop_streaming(struct ov05c10 *ov05c10)
>> +{
>> +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>> +     int ret = 0;
>> +
>> +     cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_off_regs,
>> +                         ARRAY_SIZE(mode_OV05C10_stream_off_regs), &ret);
>> +     if (ret)
>> +             dev_err(&client->dev, "fail to stop the streaming\n");
>> +
>> +     return ret;
>> +}
>> +
>> +static void ov05c10_sensor_power_set(struct ov05c10 *ov05c10, bool on)
>> +{
>> +     if (on) {
>> +             gpiod_set_value(ov05c10->enable_gpio, 0);
>> +             usleep_range(10, 20);
>> +
>> +             gpiod_set_value(ov05c10->enable_gpio, 1);
>> +             usleep_range(1000, 2000);
>> +     } else {
>> +             gpiod_set_value(ov05c10->enable_gpio, 0);
>> +             usleep_range(10, 20);
>> +     }
>> +}
>> +
>> +static int ov05c10_enable_streams(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *state, u32 pad,
>> +                               u64 streams_mask)
>> +{
>> +     struct i2c_client *client = v4l2_get_subdevdata(sd);
>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>> +     int ret = 0;
>> +
>> +     ret = pm_runtime_resume_and_get(&client->dev);
>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     ov05c10->cur_page = -1;
> 
> If you think the page number can't be trusted after resuming, then move
> this to the resume handler. It can also be dropped from the probe
> function.
> 
Thanks, it will be fixed in next version.

>> +
>> +     ret = ov05c10_start_streaming(ov05c10);
> 
> As ov05c10_start_streaming() is called here only, I would just move the
> code here. Same for ov05c10_stop_streaming() below.
> 
Thanks, it will be fixed in next version.

>> +     if (ret)
>> +             goto err_rpm_put;
>> +
>> +     return 0;
>> +
>> +err_rpm_put:
>> +     pm_runtime_put(&client->dev);
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_disable_streams(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state, u32 pad,
>> +                                u64 streams_mask)
>> +{
>> +     struct i2c_client *client = v4l2_get_subdevdata(sd);
>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>> +
>> +     ov05c10_stop_streaming(ov05c10);
>> +     pm_runtime_put(&client->dev);
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_video_ops ov05c10_video_ops = {
>> +     .s_stream = v4l2_subdev_s_stream_helper,
>> +};
>> +
>> +static const struct v4l2_subdev_pad_ops ov05c10_pad_ops = {
>> +     .enum_mbus_code = ov05c10_enum_mbus_code,
>> +     .get_fmt = v4l2_subdev_get_fmt,
>> +     .set_fmt = ov05c10_set_pad_format,
>> +     .enum_frame_size = ov05c10_enum_frame_size,
>> +     .enable_streams = ov05c10_enable_streams,
>> +     .disable_streams = ov05c10_disable_streams,
>> +};
>> +
>> +static const struct v4l2_subdev_ops ov05c10_subdev_ops = {
>> +     .video = &ov05c10_video_ops,
>> +     .pad = &ov05c10_pad_ops,
>> +};
>> +
>> +static const struct media_entity_operations ov05c10_subdev_entity_ops = {
>> +     .link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops ov05c10_internal_ops = {
>> +     .init_state = ov05c10_init_state,
>> +};
>> +
>> +static int ov05c10_init_controls(struct ov05c10 *ov05c10)
>> +{
>> +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>> +     const struct ov05c10_mode *mode = &supported_mode;
>> +     struct v4l2_fwnode_device_properties props;
>> +     struct v4l2_ctrl_handler *ctrl_hdlr;
> 
>          struct v4l2_ctrl_handler *ctrl_hdlr = &ov05c10->ctrl_handler;
> 
>> +     s64 pixel_rate_max;
>> +     s64 exposure_max;
>> +     s64 vblank_def;
>> +     s64 vblank_min;
>> +     u32 max_items;
>> +     s64 hblank;
>> +     int ret;
>> +
>> +     ret = v4l2_ctrl_handler_init(&ov05c10->ctrl_handler, 10);
> 
> You can use ctrl_hdlr here.
> 
Thanks, it will be fixed in next version.

>> +     if (ret)
>> +             return ret;
>> +
>> +     ctrl_hdlr = &ov05c10->ctrl_handler;
> 
> Drop this line.
> 
Thanks, it will be fixed in next version.

>> +
>> +     max_items = ARRAY_SIZE(ov05c10_link_freq_menu_items) - 1;
>> +     ov05c10->link_freq =
>> +             v4l2_ctrl_new_int_menu(ctrl_hdlr,
>> +                                    NULL,
>> +                                    V4L2_CID_LINK_FREQ,
>> +                                    max_items,
>> +                                    0,
>> +                                    ov05c10_link_freq_menu_items);
>> +     if (ov05c10->link_freq)
>> +             ov05c10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>> +
>> +     pixel_rate_max =
>> +             link_freq_to_pixel_rate(ov05c10_link_freq_menu_items[0],
>> +                                     supported_mode.lanes);
>> +     ov05c10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
>> +                                             V4L2_CID_PIXEL_RATE,
>> +                                             0, pixel_rate_max,
>> +                                             1, pixel_rate_max);
>> +
>> +     vblank_def = mode->vts_def - mode->height;
>> +     vblank_min = mode->vts_min - mode->height;
>> +     ov05c10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
>> +                                         V4L2_CID_VBLANK,
>> +                                         vblank_min,
>> +                                         OV05C10_VTS_MAX - mode->height,
>> +                                         1, vblank_def);
>> +
>> +     hblank = (mode->hts > mode->width) ? (mode->hts - mode->width) : 0;
> 
> No need for parentheses.
> 
Thanks, it will be fixed in next version.

>> +     ov05c10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
>> +                                         V4L2_CID_HBLANK,
>> +                                         hblank, hblank, 1, hblank);
>> +     if (ov05c10->hblank)
>> +             ov05c10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>> +
>> +     exposure_max = mode->vts_def - OV05C10_EXPOSURE_MAX_MARGIN;
>> +     ov05c10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
>> +                                           V4L2_CID_EXPOSURE,
>> +                                           OV05C10_EXPOSURE_MIN,
>> +                                           exposure_max,
>> +                                           OV05C10_EXPOSURE_STEP,
>> +                                           exposure_max);
>> +
>> +     v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
>> +                       OV05C10_ANA_GAIN_MIN, OV05C10_ANA_GAIN_MAX,
>> +                       OV05C10_ANA_GAIN_STEP, OV05C10_ANA_GAIN_DEFAULT);
>> +
>> +     v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
>> +                       OV05C10_DGTL_GAIN_MIN, OV05C10_DGTL_GAIN_MAX,
>> +                       OV05C10_DGTL_GAIN_STEP, OV05C10_DGTL_GAIN_DEFAULT);
>> +
>> +     v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov05c10_ctrl_ops,
>> +                                  V4L2_CID_TEST_PATTERN,
>> +                                  ARRAY_SIZE(ov05c10_test_pattern_menu) - 1,
>> +                                  0, 0, ov05c10_test_pattern_menu);
>> +
>> +     if (ctrl_hdlr->error) {
>> +             ret = ctrl_hdlr->error;
>> +             dev_err(&client->dev, "V4L2 control init failed (%d)\n", ret);
>> +             goto err_hdl_free;
>> +     }
>> +
>> +     ret = v4l2_fwnode_device_parse(&client->dev, &props);
>> +     if (ret)
>> +             goto err_hdl_free;
> 
> Move this to the top of the function.
> 
Thanks, it will be fixed in next version.

>> +
>> +     ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov05c10_ctrl_ops,
>> +                                           &props);
>> +     if (ret)
>> +             goto err_hdl_free;
> 
> And this before the ctrl_hdlr->error check.
> 
>> +
>> +     ov05c10->sd.ctrl_handler = ctrl_hdlr;
>> +
>> +     return 0;
>> +
>> +err_hdl_free:
>> +     v4l2_ctrl_handler_free(ctrl_hdlr);
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_parse_endpoint(struct device *dev,
>> +                               struct fwnode_handle *fwnode)
>> +{
>> +     struct v4l2_fwnode_endpoint bus_cfg = {
>> +             .bus_type = V4L2_MBUS_CSI2_DPHY
>> +     };
>> +     struct fwnode_handle *ep;
>> +     unsigned long bitmap;
>> +     int ret;
>> +
>> +     ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
>> +     if (!ep) {
>> +             dev_err(dev, "Failed to get next endpoint\n");
>> +             return -ENXIO;
>> +     }
>> +
>> +     ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
>> +     fwnode_handle_put(ep);
>> +     if (ret)
>> +             return ret;
>> +
>> +     if (bus_cfg.bus.mipi_csi2.num_data_lanes != supported_mode.lanes) {
> 
> Ideally the driver should support different number of lanes. That's not
> the case yet, but let's decouple the number of lanes from the mode. Drop
> the ov05c10_mode.lanes field and use OV05C10_DATA_LANES here.
> 
Thanks, it will be fixed in next version.

>> +             dev_err(dev,
>> +                     "number of CSI2 data lanes %d is not supported\n",
>> +                     bus_cfg.bus.mipi_csi2.num_data_lanes);
>> +             ret = -EINVAL;
>> +             goto err_endpoint_free;
>> +     }
>> +
>> +     ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
>> +                                    bus_cfg.nr_of_link_frequencies,
>> +                                    ov05c10_link_frequencies,
>> +                                    ARRAY_SIZE(ov05c10_link_frequencies),
>> +                                    &bitmap);
> 
> You're supposed to use that bitmap to select which link frequencies to
> expose to userspace.
> 
Thanks, it will be fixed in next version.

>> +     if (ret)
>> +             dev_err(dev, "v4l2_link_freq_to_bitmap fail with %d\n", ret);
>> +err_endpoint_free:
>> +     v4l2_fwnode_endpoint_free(&bus_cfg);
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_probe(struct i2c_client *client)
>> +{
>> +     struct ov05c10 *ov05c10;
>> +     u32 clkfreq;
>> +     int ret;
>> +
>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), GFP_KERNEL);
>> +     if (!ov05c10)
>> +             return -ENOMEM;
>> +
>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
>> +
>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency", &clkfreq);
>> +     if (ret)
>> +             return  dev_err_probe(&client->dev, -EINVAL,
>> +                                   "fail to get clock freq\n");
> 
> Let's try to land
> https://lore.kernel.org/linux-media/20250521104115.176950-1-mehdi.djait@linux.intel.com/
> and replace the code above with devm_v4l2_sensor_clk_get().
> 
Ok, we will verify on our side.

>> +     if (clkfreq != OV05C10_REF_CLK)
>> +             return dev_err_probe(&client->dev, -EINVAL,
>> +                                  "fail invalid clock freq %u, %lu expected\n",
>> +                                  clkfreq, OV05C10_REF_CLK);
>> +
>> +     ret = ov05c10_parse_endpoint(&client->dev, fwnode);
>> +     if (ret)
>> +             return dev_err_probe(&client->dev, -EINVAL,
>> +                                  "fail to parse endpoint\n");
>> +
>> +     ov05c10->enable_gpio = devm_gpiod_get(&client->dev, "enable",
>> +                                           GPIOD_OUT_LOW);
>> +     if (IS_ERR(ov05c10->enable_gpio))
>> +             return dev_err_probe(&client->dev,
>> +                                  PTR_ERR(ov05c10->enable_gpio),
>> +                                  "fail to get enable gpio\n");
>> +
>> +     v4l2_i2c_subdev_init(&ov05c10->sd, client, &ov05c10_subdev_ops);
> 
> I would move this line below just before ov05c10_init_controls() to keep
> all the subdev initialization grouped.
> 
Yes, I agree, this will be fixed.

>> +
>> +     ov05c10->regmap = devm_cci_regmap_init_i2c(client, 8);
>> +     if (IS_ERR(ov05c10->regmap))
>> +             return dev_err_probe(&client->dev, PTR_ERR(ov05c10->regmap),
>> +                                  "fail to init cci\n");
>> +
>> +     ov05c10->cur_page = -1;
>> +
>> +     ret = ov05c10_init_controls(ov05c10);
>> +     if (ret)
>> +             return dev_err_probe(&client->dev, ret, "fail to init ctl\n");
>> +
>> +     ov05c10->sd.internal_ops = &ov05c10_internal_ops;
>> +     ov05c10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +     ov05c10->sd.entity.ops = &ov05c10_subdev_entity_ops;
>> +     ov05c10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
>> +
>> +     ov05c10->pad.flags = MEDIA_PAD_FL_SOURCE;
>> +
>> +     ret = media_entity_pads_init(&ov05c10->sd.entity, NUM_OF_PADS,
>> +                                  &ov05c10->pad);
>> +     if (ret)
>> +             goto err_hdl_free;
>> +
>> +     ret = v4l2_subdev_init_finalize(&ov05c10->sd);
>> +     if (ret < 0)
>> +             goto err_media_entity_cleanup;
>> +
>> +     ret = v4l2_async_register_subdev_sensor(&ov05c10->sd);
>> +     if (ret)
>> +             goto err_media_entity_cleanup;
>> +
>> +     pm_runtime_set_active(&client->dev);
>> +     pm_runtime_enable(&client->dev);
>> +     pm_runtime_idle(&client->dev);
>> +     pm_runtime_set_autosuspend_delay(&client->dev, 1000);
>> +     pm_runtime_use_autosuspend(&client->dev);
> 
> Initialization of runtime PM should be done before calling
> v4l2_async_register_subdev_sensor(), as the device can be used as soon
> as it gets registered.
> 
> This will also not work on platforms where CONFIG_PM is not enabled. See
> the imx290 driver for an example of how to enable (and disable) runtime
> PM properly.
> 
Thanks, I will check the imx290 driver and submit the fix in the next 
version.

Thanks,
Pratap

>> +     return 0;
>> +
>> +err_media_entity_cleanup:
>> +     media_entity_cleanup(&ov05c10->sd.entity);
>> +
>> +err_hdl_free:
>> +     v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
>> +
>> +     return ret;
>> +}
>> +
>> +static void ov05c10_remove(struct i2c_client *client)
>> +{
>> +     struct v4l2_subdev *sd = i2c_get_clientdata(client);
>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>> +
>> +     v4l2_async_unregister_subdev(sd);
>> +     media_entity_cleanup(&sd->entity);
>> +     v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
>> +
>> +     pm_runtime_disable(&client->dev);
>> +     pm_runtime_set_suspended(&client->dev);
>> +}
>> +
>> +static int ov05c10_runtime_resume(struct device *dev)
>> +{
>> +     struct v4l2_subdev *sd = dev_get_drvdata(dev);
>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>> +
>> +     ov05c10_sensor_power_set(ov05c10, true);
>> +     return 0;
>> +}
>> +
>> +static int ov05c10_runtime_suspend(struct device *dev)
>> +{
>> +     struct v4l2_subdev *sd = dev_get_drvdata(dev);
>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>> +
>> +     ov05c10_sensor_power_set(ov05c10, false);
>> +     return 0;
>> +}
>> +
>> +static DEFINE_RUNTIME_DEV_PM_OPS(ov05c10_pm_ops, ov05c10_runtime_suspend,
>> +                              ov05c10_runtime_resume, NULL);
>> +
>> +static const struct i2c_device_id ov05c10_i2c_ids[] = {
>> +     {"ov05c10", 0 },
>> +     { }
>> +};
>> +MODULE_DEVICE_TABLE(i2c, ov05c10_i2c_ids);
>> +
>> +static struct i2c_driver ov05c10_i2c_driver = {
>> +     .driver = {
>> +             .name = DRV_NAME,
>> +             .pm = pm_ptr(&ov05c10_pm_ops),
>> +     },
>> +     .id_table = ov05c10_i2c_ids,
>> +     .probe = ov05c10_probe,
>> +     .remove = ov05c10_remove,
>> +};
>> +
>> +module_i2c_driver(ov05c10_i2c_driver);
>> +
>> +MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
>> +MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@amd.com>");
>> +MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
>> +MODULE_DESCRIPTION("OmniVision OV05C1010 sensor driver");
>> +MODULE_LICENSE("GPL");
> 
> --
> Regards,
> 
> Laurent Pinchart


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-13 22:02   ` Sakari Ailus
  2025-06-14 22:52     ` Laurent Pinchart
@ 2025-06-16 23:12     ` Nirujogi, Pratap
  2025-06-23 12:09       ` Laurent Pinchart
  1 sibling, 1 reply; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-16 23:12 UTC (permalink / raw)
  To: Sakari Ailus, Hao Yao
  Cc: Pratap Nirujogi, mchehab, laurent.pinchart, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su



On 6/13/2025 6:02 PM, Sakari Ailus wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> Hi Hao Yao,
> 
> On Fri, Jun 13, 2025 at 12:55:46PM +0800, Hao Yao wrote:
>> Hi Pratap,
>>
>> Thanks for your patch.
>>
>> This patch is written for your camera sensor module, which seems very
>> different from those already applied on Dell laptops (some of "Dell Pro"
>> series). Looking into the driver, I think this version will break the
>> devices using ov05c10 sensor.
> 
> There never was such a driver in upstream so nothing breaks. However, in
> order to support these, could you check what would it take to support them
> using this driver and post patches, please?
> 
>>
>> I think this patch is better to be validated on existing devices, but please
>> do some fixes before we can do validation. Please check my comments inline.
>>
>>
>> On 2025/6/10 03:42, Pratap Nirujogi wrote:
>>> Add driver for OmniVision 5.2M OV05C10 sensor. This driver
>>> supports only the full size normal 2888x1808@30fps 2-lane
>>> sensor profile.
>>>
>>> Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>>> Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>>> Co-developed-by: Bin Du <bin.du@amd.com>
>>> Signed-off-by: Bin Du <bin.du@amd.com>
>>> Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
>>> ---
>>> Changes v2 -> v3:
>>>
>>> * Update "refclk" property variable as "clock-frequency".
>>> * Update sensor GPIO connector id name.
>>> * Fix sensor v4l2 compliance issue.
>>> * Fix license info.
>>> * Address review comments.
>>>
>>>    MAINTAINERS                 |    8 +
>>>    drivers/media/i2c/Kconfig   |   10 +
>>>    drivers/media/i2c/Makefile  |    1 +
>>>    drivers/media/i2c/ov05c10.c | 1061 +++++++++++++++++++++++++++++++++++
>>>    4 files changed, 1080 insertions(+)
>>>    create mode 100644 drivers/media/i2c/ov05c10.c
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index a92290fffa16..caca25d00bf2 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -18303,6 +18303,14 @@ T: git git://linuxtv.org/media.git
>>>    F:        Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml
>>>    F:        drivers/media/i2c/ov02e10.c
>>> +OMNIVISION OV05C10 SENSOR DRIVER
>>> +M: Nirujogi Pratap <pratap.nirujogi@amd.com>
>>> +M: Bin Du <bin.du@amd.com>
>>> +L: linux-media@vger.kernel.org
>>> +S: Maintained
>>> +T: git git://linuxtv.org/media.git
>>> +F: drivers/media/i2c/ov05c10.c
>>> +
>>>    OMNIVISION OV08D10 SENSOR DRIVER
>>>    M:        Jimmy Su <jimmy.su@intel.com>
>>>    L:        linux-media@vger.kernel.org
>>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>>> index e68202954a8f..1662fb29d75c 100644
>>> --- a/drivers/media/i2c/Kconfig
>>> +++ b/drivers/media/i2c/Kconfig
>>> @@ -377,6 +377,16 @@ config VIDEO_OV02C10
>>>        To compile this driver as a module, choose M here: the
>>>        module will be called ov02c10.
>>> +config VIDEO_OV05C10
>>> +   tristate "OmniVision OV05C10 sensor support"
>>> +   select V4L2_CCI_I2C
>>> +   help
>>> +     This is a Video4Linux2 sensor driver for the OmniVision
>>> +     OV05C10 camera.
>>> +
>>> +     To compile this driver as a module, choose M here: the
>>> +     module will be called OV05C10.
>>> +
>>>    config VIDEO_OV08D10
>>>            tristate "OmniVision OV08D10 sensor support"
>>>            help
>>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>>> index 5873d29433ee..b4a1d721a7f2 100644
>>> --- a/drivers/media/i2c/Makefile
>>> +++ b/drivers/media/i2c/Makefile
>>> @@ -85,6 +85,7 @@ obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
>>>    obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
>>>    obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o
>>>    obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o
>>> +obj-$(CONFIG_VIDEO_OV05C10) += ov05c10.o
>>>    obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
>>>    obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
>>>    obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
>>> diff --git a/drivers/media/i2c/ov05c10.c b/drivers/media/i2c/ov05c10.c
>>> new file mode 100644
>>> index 000000000000..9a1e493c4073
>>> --- /dev/null
>>> +++ b/drivers/media/i2c/ov05c10.c
>>> @@ -0,0 +1,1061 @@
>>> +// SPDX-License-Identifier: GPL-2.0+
>>> +// Copyright (C) 2025 Advanced Micro Devices, Inc.
>>> +
>>> +#include <linux/clk.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/gpio.h>
>>> +#include <linux/i2c.h>
>>> +#include <linux/module.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/units.h>
>>> +#include <media/v4l2-cci.h>
>>> +#include <media/v4l2-ctrls.h>
>>> +#include <media/v4l2-device.h>
>>> +#include <media/v4l2-fwnode.h>
>>> +
>>> +#define DRV_NAME                   "ov05c10"
>>> +#define OV05C10_REF_CLK                    (24 * HZ_PER_MHZ)
>>
>> Seems your module use 24 MHz clock input. The Dell's modules always use
>> 19.2MHz, which means your the PLL settings will not work on Dell's.
> 
> This is ok as further work. Please send a patch. :-)
> 
>>
>>> +
>>> +#define MODE_WIDTH  2888
>>> +#define MODE_HEIGHT 1808
>>> +
>>> +#define PAGE_NUM_MASK                      0xff000000
>>> +#define PAGE_NUM_SHIFT                     24
>>> +#define REG_ADDR_MASK                      0x00ffffff
>>> +
>>> +#define OV05C10_SYSCTL_PAGE                (0 << PAGE_NUM_SHIFT)
>>> +#define OV05C10_CISCTL_PAGE                (1 << PAGE_NUM_SHIFT)
>>> +#define OV05C10_ISPCTL_PAGE                (4 << PAGE_NUM_SHIFT)
>>> +
>>> +/* Chip ID */
>>> +#define OV05C10_REG_CHIP_ID                (CCI_REG24(0x00) | OV05C10_SYSCTL_PAGE)
>>> +#define OV05C10_CHIP_ID                    0x43055610
>>> +
>>> +/* Control registers */
>>> +#define OV05C10_REG_TRIGGER                (CCI_REG8(0x01) | OV05C10_CISCTL_PAGE)
>>> +#define OV05C_REG_TRIGGER_START            BIT(0)
>>> +
>>> +/* Exposure control */
>>> +#define OV05C10_REG_EXPOSURE               (CCI_REG24(0x02) | OV05C10_CISCTL_PAGE)
>>> +#define OV05C10_EXPOSURE_MAX_MARGIN        33
>>> +#define OV05C10_EXPOSURE_MIN               4
>>> +#define OV05C10_EXPOSURE_STEP              1
>>> +#define OV05C10_EXPOSURE_DEFAULT   0x40
>>> +
>>> +/* V_TIMING internal */
>>> +#define OV05C10_REG_VTS                    (CCI_REG16(0x05) | OV05C10_CISCTL_PAGE)
>>> +#define OV05C10_VTS_30FPS          1860
>>> +#define OV05C10_VTS_MAX                    0x7fff
>>> +
>>> +/* Test Pattern Control */
>>> +#define OV05C10_REG_TEST_PATTERN   (CCI_REG8(0x12) | OV05C10_ISPCTL_PAGE)
>>> +#define OV05C10_TEST_PATTERN_ENABLE        BIT(0)
>>> +#define OV05C10_REG_TEST_PATTERN_CTL       (CCI_REG8(0xf3) | OV05C10_ISPCTL_PAGE)
>>> +#define OV05C10_REG_TEST_PATTERN_XXX       BIT(0)
>>> +
>>> +/* Digital gain control */
>>> +#define OV05C10_REG_DGTL_GAIN_H            (CCI_REG8(0x21) | OV05C10_CISCTL_PAGE)
>>> +#define OV05C10_REG_DGTL_GAIN_L            (CCI_REG8(0x22) | OV05C10_CISCTL_PAGE)
>>> +
>>> +#define OV05C10_DGTL_GAIN_MIN              0x40
>>> +#define OV05C10_DGTL_GAIN_MAX              0xff
>>> +#define OV05C10_DGTL_GAIN_DEFAULT  0x40
>>> +#define OV05C10_DGTL_GAIN_STEP             1
>>> +
>>> +#define OV05C10_DGTL_GAIN_L_MASK   0xff
>>> +#define OV05C10_DGTL_GAIN_H_SHIFT  8
>>> +#define OV05C10_DGTL_GAIN_H_MASK   0xff00
>>> +
>>> +/* Analog gain control */
>>> +#define OV05C10_REG_ANALOG_GAIN            (CCI_REG8(0x24) | OV05C10_CISCTL_PAGE)
>>> +#define OV05C10_ANA_GAIN_MIN               0x80
>>> +#define OV05C10_ANA_GAIN_MAX               0x07c0
>>> +#define OV05C10_ANA_GAIN_STEP              1
>>> +#define OV05C10_ANA_GAIN_DEFAULT   0x80
>>> +
>>> +/* H TIMING internal */
>>> +#define OV05C10_REG_HTS                    (CCI_REG16(0x37) | OV05C10_CISCTL_PAGE)
>>> +#define OV05C10_HTS_30FPS          0x0280
>>> +
>>> +/* Page selection */
>>> +#define OV05C10_REG_PAGE_CTL               CCI_REG8(0xfd)
>>> +
>>> +#define NUM_OF_PADS 1
>>> +
>>> +#define OV05C10_GET_PAGE_NUM(reg)  (((reg) & PAGE_NUM_MASK) >>\
>>> +                                    PAGE_NUM_SHIFT)
>>> +#define OV05C10_GET_REG_ADDR(reg)  ((reg) & REG_ADDR_MASK)
>>> +
>>> +enum {
>>> +   OV05C10_LINK_FREQ_900MHZ_INDEX,
>>> +};
>>> +
>>> +struct ov05c10_reg_list {
>>> +   u32 num_of_regs;
>>> +   const struct cci_reg_sequence *regs;
>>> +};
>>> +
>>> +/* Mode : resolution and related config&values */
>>> +struct ov05c10_mode {
>>> +   /* Frame width */
>>> +   u32 width;
>>> +   /* Frame height */
>>> +   u32 height;
>>> +   /* number of lanes */
>>> +   u32 lanes;
>>> +
>>> +   /* V-timing */
>>> +   u32 vts_def;
>>> +   u32 vts_min;
>>> +
>>> +   /* HTS */
>>> +   u32 hts;
>>> +
>>> +   /* Index of Link frequency config to be used */
>>> +   u32 link_freq_index;
>>> +
>>> +   /* Default register values */
>>> +   struct ov05c10_reg_list reg_list;
>>> +};
>>> +
>>> +static const s64 ov05c10_link_frequencies[] = {
>>> +   925 * HZ_PER_MHZ,
>>> +};
>>
>> Is it 900 MHz, or 925 MHz?
>>
>>> +
>>> +/* 2888x1808 30fps, 1800mbps, 2lane, 24mhz */
>>
>> Currently Dell's devices with ov05c10 use a CV chip to passthrough MIPI CSI
>> signals, but it supports max 750 MHz link frequency. That's why this
>> version:
>> https://github.com/intel/ipu6-drivers/blob/master/drivers/media/i2c/ov05c10.c
>> uses 480 MHz link frequency and a different resolution setting (2800x1576).
>> At least the setting in out-of-tree Github driver should be merged into this
>> version.
> 
> Ditto.
> 
>>
>>> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
>>> +   { CCI_REG8(0xfd),  0x00 },
>>> +   { CCI_REG8(0x20),  0x00 },
>>> +   { CCI_REG8(0xfd),  0x00 },
>>> +   { CCI_REG8(0x20),  0x0b },
>>> +   { CCI_REG8(0xc1),  0x09 },
>>> +   { CCI_REG8(0x21),  0x06 },
>>> +   { CCI_REG8(0x14),  0x78 },
>>> +   { CCI_REG8(0xe7),  0x03 },
>>> +   { CCI_REG8(0xe7),  0x00 },
>>> +   { CCI_REG8(0x21),  0x00 },
>>> +   { CCI_REG8(0xfd),  0x01 },
>>> +   { CCI_REG8(0x03),  0x00 },
>>> +   { CCI_REG8(0x04),  0x06 },
>>> +   { CCI_REG8(0x05),  0x07 },
>>> +   { CCI_REG8(0x06),  0x44 },
>>> +   { CCI_REG8(0x07),  0x08 },
>>> +   { CCI_REG8(0x1b),  0x01 },
>>> +   { CCI_REG8(0x24),  0xff },
>>> +   { CCI_REG8(0x32),  0x03 },
>>> +   { CCI_REG8(0x42),  0x5d },
>>> +   { CCI_REG8(0x43),  0x08 },
>>> +   { CCI_REG8(0x44),  0x81 },
>>> +   { CCI_REG8(0x46),  0x5f },
>>> +   { CCI_REG8(0x48),  0x18 },
>>> +   { CCI_REG8(0x49),  0x04 },
>>> +   { CCI_REG8(0x5c),  0x18 },
>>> +   { CCI_REG8(0x5e),  0x13 },
>>> +   { CCI_REG8(0x70),  0x15 },
>>> +   { CCI_REG8(0x77),  0x35 },
>>> +   { CCI_REG8(0x79),  0x00 },
>>> +   { CCI_REG8(0x7b),  0x08 },
>>> +   { CCI_REG8(0x7d),  0x08 },
>>> +   { CCI_REG8(0x7e),  0x08 },
>>> +   { CCI_REG8(0x7f),  0x08 },
>>> +   { CCI_REG8(0x90),  0x37 },
>>> +   { CCI_REG8(0x91),  0x05 },
>>> +   { CCI_REG8(0x92),  0x18 },
>>> +   { CCI_REG8(0x93),  0x27 },
>>> +   { CCI_REG8(0x94),  0x05 },
>>> +   { CCI_REG8(0x95),  0x38 },
>>> +   { CCI_REG8(0x9b),  0x00 },
>>> +   { CCI_REG8(0x9c),  0x06 },
>>> +   { CCI_REG8(0x9d),  0x28 },
>>> +   { CCI_REG8(0x9e),  0x06 },
>>> +   { CCI_REG8(0xb2),  0x0f },
>>> +   { CCI_REG8(0xb3),  0x29 },
>>> +   { CCI_REG8(0xbf),  0x3c },
>>> +   { CCI_REG8(0xc2),  0x04 },
>>> +   { CCI_REG8(0xc4),  0x00 },
>>> +   { CCI_REG8(0xca),  0x20 },
>>> +   { CCI_REG8(0xcb),  0x20 },
>>> +   { CCI_REG8(0xcc),  0x28 },
>>> +   { CCI_REG8(0xcd),  0x28 },
>>> +   { CCI_REG8(0xce),  0x20 },
>>> +   { CCI_REG8(0xcf),  0x20 },
>>> +   { CCI_REG8(0xd0),  0x2a },
>>> +   { CCI_REG8(0xd1),  0x2a },
>>> +   { CCI_REG8(0xfd),  0x0f },
>>> +   { CCI_REG8(0x00),  0x00 },
>>> +   { CCI_REG8(0x01),  0xa0 },
>>> +   { CCI_REG8(0x02),  0x48 },
>>> +   { CCI_REG8(0x07),  0x8f },
>>> +   { CCI_REG8(0x08),  0x70 },
>>> +   { CCI_REG8(0x09),  0x01 },
>>> +   { CCI_REG8(0x0b),  0x40 },
>>> +   { CCI_REG8(0x0d),  0x07 },
>>> +   { CCI_REG8(0x11),  0x33 },
>>> +   { CCI_REG8(0x12),  0x77 },
>>> +   { CCI_REG8(0x13),  0x66 },
>>> +   { CCI_REG8(0x14),  0x65 },
>>> +   { CCI_REG8(0x15),  0x37 },
>>> +   { CCI_REG8(0x16),  0xbf },
>>> +   { CCI_REG8(0x17),  0xff },
>>> +   { CCI_REG8(0x18),  0xff },
>>> +   { CCI_REG8(0x19),  0x12 },
>>> +   { CCI_REG8(0x1a),  0x10 },
>>> +   { CCI_REG8(0x1c),  0x77 },
>>> +   { CCI_REG8(0x1d),  0x77 },
>>> +   { CCI_REG8(0x20),  0x0f },
>>> +   { CCI_REG8(0x21),  0x0f },
>>> +   { CCI_REG8(0x22),  0x0f },
>>> +   { CCI_REG8(0x23),  0x0f },
>>> +   { CCI_REG8(0x2b),  0x20 },
>>> +   { CCI_REG8(0x2c),  0x20 },
>>> +   { CCI_REG8(0x2d),  0x04 },
>>> +   { CCI_REG8(0xfd),  0x03 },
>>> +   { CCI_REG8(0x9d),  0x0f },
>>> +   { CCI_REG8(0x9f),  0x40 },
>>> +   { CCI_REG8(0xfd),  0x00 },
>>> +   { CCI_REG8(0x20),  0x1b },
>>> +   { CCI_REG8(0xfd),  0x04 },
>>> +   { CCI_REG8(0x19),  0x60 },
>>> +   { CCI_REG8(0xfd),  0x02 },
>>> +   { CCI_REG8(0x75),  0x05 },
>>> +   { CCI_REG8(0x7f),  0x06 },
>>> +   { CCI_REG8(0x9a),  0x03 },
>>> +   { CCI_REG8(0xa2),  0x07 },
>>> +   { CCI_REG8(0xa3),  0x10 },
>>> +   { CCI_REG8(0xa5),  0x02 },
>>> +   { CCI_REG8(0xa6),  0x0b },
>>> +   { CCI_REG8(0xa7),  0x48 },
>>> +   { CCI_REG8(0xfd),  0x07 },
>>> +   { CCI_REG8(0x42),  0x00 },
>>> +   { CCI_REG8(0x43),  0x80 },
>>> +   { CCI_REG8(0x44),  0x00 },
>>> +   { CCI_REG8(0x45),  0x80 },
>>> +   { CCI_REG8(0x46),  0x00 },
>>> +   { CCI_REG8(0x47),  0x80 },
>>> +   { CCI_REG8(0x48),  0x00 },
>>> +   { CCI_REG8(0x49),  0x80 },
>>> +   { CCI_REG8(0x00),  0xf7 },
>>> +   { CCI_REG8(0xfd),  0x00 },
>>> +   { CCI_REG8(0xe7),  0x03 },
>>> +   { CCI_REG8(0xe7),  0x00 },
>>> +   { CCI_REG8(0xfd),  0x00 },
>>> +   { CCI_REG8(0x93),  0x18 },
>>> +   { CCI_REG8(0x94),  0xff },
>>> +   { CCI_REG8(0x95),  0xbd },
>>> +   { CCI_REG8(0x96),  0x1a },
>>> +   { CCI_REG8(0x98),  0x04 },
>>> +   { CCI_REG8(0x99),  0x08 },
>>> +   { CCI_REG8(0x9b),  0x10 },
>>> +   { CCI_REG8(0x9c),  0x3f },
>>> +   { CCI_REG8(0xa1),  0x05 },
>>> +   { CCI_REG8(0xa4),  0x2f },
>>> +   { CCI_REG8(0xc0),  0x0c },
>>> +   { CCI_REG8(0xc1),  0x08 },
>>> +   { CCI_REG8(0xc2),  0x00 },
>>> +   { CCI_REG8(0xb6),  0x20 },
>>> +   { CCI_REG8(0xbb),  0x80 },
>>> +   { CCI_REG8(0xfd),  0x00 },
>>> +   { CCI_REG8(0xa0),  0x01 },
>>> +   { CCI_REG8(0xfd),  0x01 },
>>> +};
>>> +
>>> +static const struct cci_reg_sequence mode_OV05C10_stream_on_regs[] = {
>>> +   { CCI_REG8(0xfd), 0x01 },
>>> +   { CCI_REG8(0x33), 0x03 },
>>> +   { CCI_REG8(0x01), 0x02 },
>>> +   { CCI_REG8(0xfd), 0x00 },
>>> +   { CCI_REG8(0x20), 0x1f },
>>> +   { CCI_REG8(0xfd), 0x01 },
>>> +};
>>> +
>>> +static const struct cci_reg_sequence mode_OV05C10_stream_off_regs[] = {
>>> +   { CCI_REG8(0xfd), 0x00 },
>>> +   { CCI_REG8(0x20), 0x5b },
>>> +   { CCI_REG8(0xfd), 0x01 },
>>> +   { CCI_REG8(0x33), 0x02 },
>>> +   { CCI_REG8(0x01), 0x02 },
>>> +};
>>> +
>>> +static const char * const ov05c10_test_pattern_menu[] = {
>>> +   "Disabled",
>>> +   "Vertical Color Bar Type 1",
>>> +   "Vertical Color Bar Type 2",
>>> +   "Vertical Color Bar Type 3",
>>> +   "Vertical Color Bar Type 4"
>>> +};
>>> +
>>> +/* Configurations for supported link frequencies */
>>> +#define OV05C10_LINK_FREQ_900MHZ   (900 * HZ_PER_MHZ)
>>> +
>>> +/* Number of lanes supported */
>>> +#define OV05C10_DATA_LANES         2
>>> +
>>> +/* Bits per sample of sensor output */
>>> +#define OV05C10_BITS_PER_SAMPLE            10
>>> +
>>> +/*
>>> + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
>>> + * data rate => double data rate; number of lanes => 2; bits per pixel => 10
>>> + */
>>> +static u64 link_freq_to_pixel_rate(u64 f, u32 lane_nr)
>>> +{
>>> +   f *= 2 * lane_nr;
>>> +   do_div(f, OV05C10_BITS_PER_SAMPLE);
>>> +
>>> +   return f;
>>> +}
>>> +
>>> +/* Menu items for LINK_FREQ V4L2 control */
>>> +static const s64 ov05c10_link_freq_menu_items[] = {
>>> +   OV05C10_LINK_FREQ_900MHZ,
>>> +};
>>> +
>>> +/* Mode configs, currently, only support 1 mode */
>>> +static const struct ov05c10_mode supported_mode = {
>>> +   .width = MODE_WIDTH,
>>> +   .height = MODE_HEIGHT,
>>> +   .vts_def = OV05C10_VTS_30FPS,
>>> +   .vts_min = OV05C10_VTS_30FPS,
>>> +   .hts = 640,
>>> +   .lanes = 2,
>>> +   .reg_list = {
>>> +           .num_of_regs = ARRAY_SIZE(ov05c10_2888x1808_regs),
>>> +           .regs = ov05c10_2888x1808_regs,
>>> +   },
>>> +   .link_freq_index = OV05C10_LINK_FREQ_900MHZ_INDEX,
>>> +};
>>> +
>>> +struct ov05c10 {
>>> +   struct v4l2_subdev sd;
>>> +   struct media_pad pad;
>>> +
>>> +   /* V4L2 control handler */
>>> +   struct v4l2_ctrl_handler ctrl_handler;
>>> +
>>> +   /* V4L2 Controls */
>>> +   struct v4l2_ctrl *link_freq;
>>> +   struct v4l2_ctrl *pixel_rate;
>>> +   struct v4l2_ctrl *vblank;
>>> +   struct v4l2_ctrl *hblank;
>>> +   struct v4l2_ctrl *exposure;
>>> +
>>> +   struct regmap *regmap;
>>> +
>>> +   /* gpio descriptor */
>>> +   struct gpio_desc *enable_gpio;
>>> +
>>> +   /* Current page for sensor register control */
>>> +   int cur_page;
>>> +};
>>> +
>>> +#define to_ov05c10(_sd)    container_of(_sd, struct ov05c10, sd)
>>> +
>>> +static int ov05c10_init_state(struct v4l2_subdev *sd,
>>> +                         struct v4l2_subdev_state *sd_state)
>>> +{
>>> +   struct v4l2_mbus_framefmt *frame_fmt;
>>> +   struct v4l2_subdev_format fmt = {
>>> +           .which = V4L2_SUBDEV_FORMAT_TRY,
>>> +           .format = {
>>> +                   .width = MODE_WIDTH,
>>> +                   .height = MODE_HEIGHT,
>>> +                   .code = MEDIA_BUS_FMT_SGRBG10_1X10,
>>> +                   .field = V4L2_FIELD_NONE,
>>> +           }
>>> +   };
>>> +
>>> +   frame_fmt = v4l2_subdev_state_get_format(sd_state, 0);
>>> +   *frame_fmt = fmt.format;
>>> +   return 0;
>>> +}
>>> +
>>> +static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page, int *err)
>>
>> Seems nobody cares the return value of ov05c10_switch_page() or
>> ov05c10_reg_write(), etc.. It should be better to use void return, or use
>> return value instead of int *err.
> 
> As this is a function that has two users, I'd use a more common pattern of
> returning a value.
> 
>>
>>> +{
>>> +   int ret = 0;
>>> +
>>> +   if (err && *err)
>>> +           return *err;
>>> +
>>> +   if (page != ov05c10->cur_page) {
>>> +           cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, &ret);
>>> +           if (!ret)
>>> +                   ov05c10->cur_page = page;
>>> +   }
>>> +
>>> +   if (err)
>>> +           *err = ret;
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +/* refer to the implementation of cci_read */
>>> +static int ov05c10_reg_read(struct ov05c10 *ov05c10, u32 reg,
>>> +                       u64 *val, int *err)
>>> +{
>>> +   u32 page;
>>> +   u32 addr;
>>> +   int ret = 0;
>>> +
>>> +   if (err && *err)
>>> +           return *err;
>>> +
>>> +   page = OV05C10_GET_PAGE_NUM(reg);
>>> +   addr = OV05C10_GET_REG_ADDR(reg);
>>> +   ov05c10_switch_page(ov05c10, page, &ret);
>>> +   cci_read(ov05c10->regmap, addr, val, &ret);
>>> +   if (err)
>>> +           *err = ret;
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +/* refer to the implementation of cci_write */
>>> +static int ov05c10_reg_write(struct ov05c10 *ov05c10, u32 reg,
>>> +                        u64 val, int *err)
>>> +{
>>> +   u32 page;
>>> +   u32 addr;
>>> +   int ret = 0;
>>> +
>>> +   if (err && *err)
>>> +           return *err;
>>> +
>>> +   page = OV05C10_GET_PAGE_NUM(reg);
>>> +   addr = OV05C10_GET_REG_ADDR(reg);
>>> +   ov05c10_switch_page(ov05c10, page, &ret);
>>> +   cci_write(ov05c10->regmap, addr, val, &ret);
>>> +   if (err)
>>> +           *err = ret;
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static int ov05c10_update_vblank(struct ov05c10 *ov05c10, u32 vblank)
>>> +{
>>> +   const struct ov05c10_mode *mode = &supported_mode;
>>> +   u64 val;
>>> +   int ret = 0;
>>> +
>>> +   val = mode->height + vblank;
>>> +   ov05c10_reg_write(ov05c10, OV05C10_REG_VTS, val, &ret);
>>> +   ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>> +                     OV05C_REG_TRIGGER_START, &ret);
>>> +
>>> +   return ret;
>>> +}
>>
>> I remembered that the ov05c10 VTS control (P1:0x05~0x06) is a bit weird.
>> This register seems take the increment of VTS value, so direct write of VTS
>> value will not set it properly. Does this version make AE working on your
>> platform?
>>
>>> +
>>> +static int ov05c10_update_exposure(struct ov05c10 *ov05c10, u32 exposure)
>>> +{
>>> +   int ret = 0;
>>> +
>>> +   ov05c10_reg_write(ov05c10, OV05C10_REG_EXPOSURE, exposure, &ret);
>>> +   ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>> +                     OV05C_REG_TRIGGER_START, &ret);
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static int ov05c10_update_analog_gain(struct ov05c10 *ov05c10, u32 a_gain)
>>> +{
>>> +   int ret = 0;
>>> +
>>> +   ov05c10_reg_write(ov05c10, OV05C10_REG_ANALOG_GAIN, a_gain, &ret);
>>> +   ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>> +                     OV05C_REG_TRIGGER_START, &ret);
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static int ov05c10_update_digital_gain(struct ov05c10 *ov05c10, u32 d_gain)
>>> +{
>>> +   u64 val;
>>> +   int ret = 0;
>>> +
>>> +   val = d_gain & OV05C10_DGTL_GAIN_L_MASK;
>>> +   ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_L, val, &ret);
>>> +
>>> +   val = (d_gain & OV05C10_DGTL_GAIN_H_MASK) >> OV05C10_DGTL_GAIN_H_SHIFT;
>>> +   ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_H, val, &ret);
>>> +
>>> +   ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>> +                     OV05C_REG_TRIGGER_START, &ret);
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static int ov05c10_enable_test_pattern(struct ov05c10 *ov05c10, u32 pattern)
>>> +{
>>> +   u64 val;
>>> +   int ret = 0;
>>> +
>>> +   if (pattern) {
>>> +           ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
>>> +                            &val, &ret);
>>> +           ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
>>> +                             val | OV05C10_REG_TEST_PATTERN_XXX, &ret);
>>> +           ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
>>> +           val |= OV05C10_TEST_PATTERN_ENABLE;
>>> +   } else {
>>> +           ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
>>> +           val &= ~OV05C10_TEST_PATTERN_ENABLE;
>>> +   }
>>> +
>>> +   ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN, val, &ret);
>>> +   ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>> +                     OV05C_REG_TRIGGER_START, &ret);
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static int ov05c10_set_ctrl(struct v4l2_ctrl *ctrl)
>>> +{
>>> +   struct ov05c10 *ov05c10 = container_of(ctrl->handler,
>>> +                                          struct ov05c10, ctrl_handler);
>>> +   struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>>> +   const struct ov05c10_mode *mode = &supported_mode;
>>> +   s64 max;
>>> +   int ret = 0;
>>> +
>>> +   /* Propagate change of current control to all related controls */
>>> +   if (ctrl->id == V4L2_CID_VBLANK) {
>>> +           s64 cur_exp = ov05c10->exposure->cur.val;
>>> +
>>> +           /* Update max exposure while meeting expected vblanking */
>>> +           max = mode->height + ctrl->val - OV05C10_EXPOSURE_MAX_MARGIN;
>>> +           cur_exp = clamp(cur_exp, ov05c10->exposure->minimum, max);
>>> +           ret = __v4l2_ctrl_modify_range(ov05c10->exposure,
>>> +                                          ov05c10->exposure->minimum,
>>> +                                          max, ov05c10->exposure->step,
>>> +                                          cur_exp);
>>> +           if (!ret)
>>> +                   return ret;
>>> +   }
>>> +
>>> +   /*
>>> +    * Applying V4L2 control value only happens
>>> +    * when power is up for streaming
>>> +    */
>>> +   if (!pm_runtime_get_if_in_use(&client->dev))
>>> +           return 0;
>>> +
>>> +   switch (ctrl->id) {
>>> +   case V4L2_CID_ANALOGUE_GAIN:
>>> +           ret = ov05c10_update_analog_gain(ov05c10, ctrl->val);
>>> +           break;
>>> +   case V4L2_CID_DIGITAL_GAIN:
>>> +           ret = ov05c10_update_digital_gain(ov05c10, ctrl->val);
>>> +           break;
>>> +   case V4L2_CID_EXPOSURE:
>>> +           ret = ov05c10_update_exposure(ov05c10, ctrl->val);
>>> +           break;
>>> +   case V4L2_CID_VBLANK:
>>> +           ret = ov05c10_update_vblank(ov05c10, ctrl->val);
>>> +           break;
>>> +   case V4L2_CID_TEST_PATTERN:
>>> +           ret = ov05c10_enable_test_pattern(ov05c10, ctrl->val);
>>> +           break;
>>> +   default:
>>> +           ret = -ENOTTY;
>>> +           dev_err(&client->dev,
>>> +                   "ctrl(id:0x%x,val:0x%x) is not handled\n",
>>> +                   ctrl->id, ctrl->val);
>>> +           break;
>>> +   }
>>> +
>>> +   pm_runtime_put(&client->dev);
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static const struct v4l2_ctrl_ops ov05c10_ctrl_ops = {
>>> +   .s_ctrl = ov05c10_set_ctrl,
>>> +};
>>> +
>>> +static int ov05c10_enum_mbus_code(struct v4l2_subdev *sd,
>>> +                             struct v4l2_subdev_state *sd_state,
>>> +                             struct v4l2_subdev_mbus_code_enum *code)
>>> +{
>>> +   /* Only one bayer order(GRBG) is supported */
>>> +   if (code->index > 0)
>>> +           return -EINVAL;
>>> +
>>> +   code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int ov05c10_enum_frame_size(struct v4l2_subdev *sd,
>>> +                              struct v4l2_subdev_state *sd_state,
>>> +                              struct v4l2_subdev_frame_size_enum *fse)
>>> +{
>>> +   /* ov05c10 driver currently only supports 1 mode*/
>>> +   if (fse->index != 0)
>>> +           return -EINVAL;
>>> +
>>> +   if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
>>> +           return -EINVAL;
>>> +
>>> +   fse->min_width = supported_mode.width;
>>> +   fse->max_width = fse->min_width;
>>> +   fse->min_height = supported_mode.height;
>>> +   fse->max_height = fse->min_height;
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static void ov05c10_update_pad_format(const struct ov05c10_mode *mode,
>>> +                                 struct v4l2_subdev_format *fmt)
>>> +{
>>> +   fmt->format.width = mode->width;
>>> +   fmt->format.height = mode->height;
>>> +   fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
>>> +   fmt->format.field = V4L2_FIELD_NONE;
>>> +}
>>> +
>>> +static int ov05c10_set_pad_format(struct v4l2_subdev *sd,
>>> +                             struct v4l2_subdev_state *sd_state,
>>> +                             struct v4l2_subdev_format *fmt)
>>> +{
>>> +   struct v4l2_mbus_framefmt *framefmt;
>>> +   struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>> +   const struct ov05c10_mode *mode;
>>> +   s32 vblank_def;
>>> +   s32 vblank_min;
>>> +   s64 pixel_rate;
>>> +   s64 link_freq;
>>> +   s64 h_blank;
>>> +
>>> +   /* Only one raw bayer(GRBG) order is supported */
>>> +   if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10)
>>> +           fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
>>> +
>>> +   mode = &supported_mode;
>>> +   ov05c10_update_pad_format(mode, fmt);
>>> +   if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
>>> +           framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
>>> +           *framefmt = fmt->format;
>>> +   } else {
>>> +           __v4l2_ctrl_s_ctrl(ov05c10->link_freq, mode->link_freq_index);
>>> +           link_freq = ov05c10_link_freq_menu_items[mode->link_freq_index];
>>> +           pixel_rate = link_freq_to_pixel_rate(link_freq,
>>> +                                                mode->lanes);
>>> +           __v4l2_ctrl_s_ctrl_int64(ov05c10->pixel_rate, pixel_rate);
>>> +
>>> +           /* Update limits and set FPS to default */
>>> +           vblank_def = mode->vts_def - mode->height;
>>> +           vblank_min = mode->vts_min - mode->height;
>>> +           __v4l2_ctrl_modify_range(ov05c10->vblank, vblank_min,
>>> +                                    OV05C10_VTS_MAX - mode->height,
>>> +                                    1, vblank_def);
>>> +           __v4l2_ctrl_s_ctrl(ov05c10->vblank, vblank_def);
>>> +           h_blank = mode->hts;
>>> +           __v4l2_ctrl_modify_range(ov05c10->hblank, h_blank,
>>> +                                    h_blank, 1, h_blank);
>>> +   }
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static int ov05c10_start_streaming(struct ov05c10 *ov05c10)
>>> +{
>>> +   struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>>> +   const struct ov05c10_mode *mode = &supported_mode;
>>> +   const struct ov05c10_reg_list *reg_list;
>>> +   int ret = 0;
>>> +
>>> +   /* Apply default values of current mode */
>>> +   reg_list = &mode->reg_list;
>>> +   cci_multi_reg_write(ov05c10->regmap, reg_list->regs,
>>> +                       reg_list->num_of_regs, &ret);
>>> +   if (ret) {
>>> +           dev_err(&client->dev, "fail to set mode, ret: %d\n", ret);
>>> +           return ret;
>>> +   }
>>> +
>>> +   /* Apply customized values from user */
>>> +   ret =  __v4l2_ctrl_handler_setup(ov05c10->sd.ctrl_handler);
>>> +   if (ret) {
>>> +           dev_err(&client->dev, "failed to setup v4l2 handler %d\n", ret);
>>> +           return ret;
>>> +   }
>>> +
>>> +   cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_on_regs,
>>> +                       ARRAY_SIZE(mode_OV05C10_stream_on_regs), &ret);
>>> +   if (ret)
>>> +           dev_err(&client->dev, "fail to start the streaming\n");
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static int ov05c10_stop_streaming(struct ov05c10 *ov05c10)
>>> +{
>>> +   struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>>> +   int ret = 0;
>>> +
>>> +   cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_off_regs,
>>> +                       ARRAY_SIZE(mode_OV05C10_stream_off_regs), &ret);
>>> +   if (ret)
>>> +           dev_err(&client->dev, "fail to stop the streaming\n");
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static void ov05c10_sensor_power_set(struct ov05c10 *ov05c10, bool on)
>>> +{
>>> +   if (on) {
>>> +           gpiod_set_value(ov05c10->enable_gpio, 0);
>>> +           usleep_range(10, 20);
>>> +
>>> +           gpiod_set_value(ov05c10->enable_gpio, 1);
>>> +           usleep_range(1000, 2000);
>>
>> According to the datasheet, ov05c10 needs at least 8 ms to work after its
>> XSHUTDN pin pulled to high. 1 ms maybe too quick, did you tested it? Or the
>> enable_gpio is actually not the XSHUTDN pin?
>>
>> On Intel platforms, if the sensor driver controls the module power, ususally
>> it requires GPIO "reset", regulator "avdd" and clk "img_clk" assigned by
>> kernel driver intel_skl_int3472_discrete. I'm not sure whether any devices
>> on market using this power control solution, but if any, missing those
>> resources will stop them from powering-up cameras.
> 
> Please post a patch.
> 
>>
>>> +   } else {
>>> +           gpiod_set_value(ov05c10->enable_gpio, 0);
>>> +           usleep_range(10, 20);
>>> +   }
>>> +}
>>> +
>>> +static int ov05c10_enable_streams(struct v4l2_subdev *sd,
>>> +                             struct v4l2_subdev_state *state, u32 pad,
>>> +                             u64 streams_mask)
>>> +{
>>> +   struct i2c_client *client = v4l2_get_subdevdata(sd);
>>> +   struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>> +   int ret = 0;
>>> +
>>> +   ret = pm_runtime_resume_and_get(&client->dev);
>>> +   if (ret < 0)
>>> +           return ret;
>>> +
>>> +   ov05c10->cur_page = -1;
>>> +
>>> +   ret = ov05c10_start_streaming(ov05c10);
>>> +   if (ret)
>>> +           goto err_rpm_put;
>>> +
>>> +   return 0;
>>> +
>>> +err_rpm_put:
>>> +   pm_runtime_put(&client->dev);
>>> +   return ret;
>>> +}
>>> +
>>> +static int ov05c10_disable_streams(struct v4l2_subdev *sd,
>>> +                              struct v4l2_subdev_state *state, u32 pad,
>>> +                              u64 streams_mask)
>>> +{
>>> +   struct i2c_client *client = v4l2_get_subdevdata(sd);
>>> +   struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>> +
>>> +   ov05c10_stop_streaming(ov05c10);
>>> +   pm_runtime_put(&client->dev);
>>> +
>>> +   return 0;
>>> +}
>>> +
>>> +static const struct v4l2_subdev_video_ops ov05c10_video_ops = {
>>> +   .s_stream = v4l2_subdev_s_stream_helper,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_pad_ops ov05c10_pad_ops = {
>>> +   .enum_mbus_code = ov05c10_enum_mbus_code,
>>> +   .get_fmt = v4l2_subdev_get_fmt,
>>> +   .set_fmt = ov05c10_set_pad_format,
>>> +   .enum_frame_size = ov05c10_enum_frame_size,
>>> +   .enable_streams = ov05c10_enable_streams,
>>> +   .disable_streams = ov05c10_disable_streams,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_ops ov05c10_subdev_ops = {
>>> +   .video = &ov05c10_video_ops,
>>> +   .pad = &ov05c10_pad_ops,
>>> +};
>>> +
>>> +static const struct media_entity_operations ov05c10_subdev_entity_ops = {
>>> +   .link_validate = v4l2_subdev_link_validate,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_internal_ops ov05c10_internal_ops = {
>>> +   .init_state = ov05c10_init_state,
>>> +};
>>> +
>>> +static int ov05c10_init_controls(struct ov05c10 *ov05c10)
>>> +{
>>> +   struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>>> +   const struct ov05c10_mode *mode = &supported_mode;
>>> +   struct v4l2_fwnode_device_properties props;
>>> +   struct v4l2_ctrl_handler *ctrl_hdlr;
>>> +   s64 pixel_rate_max;
>>> +   s64 exposure_max;
>>> +   s64 vblank_def;
>>> +   s64 vblank_min;
>>> +   u32 max_items;
>>> +   s64 hblank;
>>> +   int ret;
>>> +
>>> +   ret = v4l2_ctrl_handler_init(&ov05c10->ctrl_handler, 10);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   ctrl_hdlr = &ov05c10->ctrl_handler;
>>> +
>>> +   max_items = ARRAY_SIZE(ov05c10_link_freq_menu_items) - 1;
>>> +   ov05c10->link_freq =
>>> +           v4l2_ctrl_new_int_menu(ctrl_hdlr,
>>> +                                  NULL,
>>> +                                  V4L2_CID_LINK_FREQ,
>>> +                                  max_items,
>>> +                                  0,
>>> +                                  ov05c10_link_freq_menu_items);
>>> +   if (ov05c10->link_freq)
>>> +           ov05c10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>> +
>>> +   pixel_rate_max =
>>> +           link_freq_to_pixel_rate(ov05c10_link_freq_menu_items[0],
>>> +                                   supported_mode.lanes);
>>> +   ov05c10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
>>> +                                           V4L2_CID_PIXEL_RATE,
>>> +                                           0, pixel_rate_max,
>>> +                                           1, pixel_rate_max);
>>> +
>>> +   vblank_def = mode->vts_def - mode->height;
>>> +   vblank_min = mode->vts_min - mode->height;
>>> +   ov05c10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
>>> +                                       V4L2_CID_VBLANK,
>>> +                                       vblank_min,
>>> +                                       OV05C10_VTS_MAX - mode->height,
>>> +                                       1, vblank_def);
>>> +
>>> +   hblank = (mode->hts > mode->width) ? (mode->hts - mode->width) : 0;
>>
>> Here your hts uses 640 but width is 2888, which means hblank is set to 0
>> here. This is wrong, please fix your configuration.
>>
>>> +   ov05c10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
>>> +                                       V4L2_CID_HBLANK,
>>> +                                       hblank, hblank, 1, hblank);
>>> +   if (ov05c10->hblank)
>>> +           ov05c10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>> +
>>> +   exposure_max = mode->vts_def - OV05C10_EXPOSURE_MAX_MARGIN;
>>> +   ov05c10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
>>> +                                         V4L2_CID_EXPOSURE,
>>> +                                         OV05C10_EXPOSURE_MIN,
>>> +                                         exposure_max,
>>> +                                         OV05C10_EXPOSURE_STEP,
>>> +                                         exposure_max);
>>> +
>>> +   v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
>>> +                     OV05C10_ANA_GAIN_MIN, OV05C10_ANA_GAIN_MAX,
>>> +                     OV05C10_ANA_GAIN_STEP, OV05C10_ANA_GAIN_DEFAULT);
>>> +
>>> +   v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
>>> +                     OV05C10_DGTL_GAIN_MIN, OV05C10_DGTL_GAIN_MAX,
>>> +                     OV05C10_DGTL_GAIN_STEP, OV05C10_DGTL_GAIN_DEFAULT);
>>> +
>>> +   v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov05c10_ctrl_ops,
>>> +                                V4L2_CID_TEST_PATTERN,
>>> +                                ARRAY_SIZE(ov05c10_test_pattern_menu) - 1,
>>> +                                0, 0, ov05c10_test_pattern_menu);
>>> +
>>> +   if (ctrl_hdlr->error) {
>>> +           ret = ctrl_hdlr->error;
>>> +           dev_err(&client->dev, "V4L2 control init failed (%d)\n", ret);
>>> +           goto err_hdl_free;
>>> +   }
>>> +
>>> +   ret = v4l2_fwnode_device_parse(&client->dev, &props);
>>> +   if (ret)
>>> +           goto err_hdl_free;
>>> +
>>> +   ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov05c10_ctrl_ops,
>>> +                                         &props);
>>> +   if (ret)
>>> +           goto err_hdl_free;
>>> +
>>> +   ov05c10->sd.ctrl_handler = ctrl_hdlr;
>>> +
>>> +   return 0;
>>> +
>>> +err_hdl_free:
>>> +   v4l2_ctrl_handler_free(ctrl_hdlr);
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static int ov05c10_parse_endpoint(struct device *dev,
>>> +                             struct fwnode_handle *fwnode)
>>> +{
>>> +   struct v4l2_fwnode_endpoint bus_cfg = {
>>> +           .bus_type = V4L2_MBUS_CSI2_DPHY
>>> +   };
>>> +   struct fwnode_handle *ep;
>>> +   unsigned long bitmap;
>>> +   int ret;
>>> +
>>> +   ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
>>> +   if (!ep) {
>>> +           dev_err(dev, "Failed to get next endpoint\n");
>>> +           return -ENXIO;
>>> +   }
>>> +
>>> +   ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
>>> +   fwnode_handle_put(ep);
>>> +   if (ret)
>>> +           return ret;
>>> +
>>> +   if (bus_cfg.bus.mipi_csi2.num_data_lanes != supported_mode.lanes) {
>>> +           dev_err(dev,
>>> +                   "number of CSI2 data lanes %d is not supported\n",
>>> +                   bus_cfg.bus.mipi_csi2.num_data_lanes);
>>> +           ret = -EINVAL;
>>> +           goto err_endpoint_free;
>>> +   }
>>> +
>>> +   ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
>>> +                                  bus_cfg.nr_of_link_frequencies,
>>> +                                  ov05c10_link_frequencies,
>>> +                                  ARRAY_SIZE(ov05c10_link_frequencies),
>>> +                                  &bitmap);
>>> +   if (ret)
>>> +           dev_err(dev, "v4l2_link_freq_to_bitmap fail with %d\n", ret);
>>> +err_endpoint_free:
>>> +   v4l2_fwnode_endpoint_free(&bus_cfg);
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static int ov05c10_probe(struct i2c_client *client)
>>> +{
>>> +   struct ov05c10 *ov05c10;
>>> +   u32 clkfreq;
>>> +   int ret;
>>> +
>>> +   ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), GFP_KERNEL);
>>> +   if (!ov05c10)
>>> +           return -ENOMEM;
>>> +
>>> +   struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
>>> +
>>> +   ret = fwnode_property_read_u32(fwnode, "clock-frequency", &clkfreq);
>>
>> Maybe it's better to separate this part fwnode and GPIO code into a
>> standalone function?
> 
> I don't mind, the probe() function isn't very long anyway.
> 
>>
>>> +   if (ret)
>>> +           return  dev_err_probe(&client->dev, -EINVAL,
>>> +                                 "fail to get clock freq\n");
>>> +   if (clkfreq != OV05C10_REF_CLK)
>>> +           return dev_err_probe(&client->dev, -EINVAL,
>>> +                                "fail invalid clock freq %u, %lu expected\n",
>>> +                                clkfreq, OV05C10_REF_CLK);
>>> +
>>> +   ret = ov05c10_parse_endpoint(&client->dev, fwnode);
>>> +   if (ret)
>>> +           return dev_err_probe(&client->dev, -EINVAL,
>>> +                                "fail to parse endpoint\n");
>>> +
>>> +   ov05c10->enable_gpio = devm_gpiod_get(&client->dev, "enable",
>>> +                                         GPIOD_OUT_LOW);
>>> +   if (IS_ERR(ov05c10->enable_gpio))
>>> +           return dev_err_probe(&client->dev,
>>> +                                PTR_ERR(ov05c10->enable_gpio),
>>> +                                "fail to get enable gpio\n");
>>> +
>>> +   v4l2_i2c_subdev_init(&ov05c10->sd, client, &ov05c10_subdev_ops);
>>> +
>>> +   ov05c10->regmap = devm_cci_regmap_init_i2c(client, 8);
>>> +   if (IS_ERR(ov05c10->regmap))
>>> +           return dev_err_probe(&client->dev, PTR_ERR(ov05c10->regmap),
>>> +                                "fail to init cci\n");
>>> +
>>> +   ov05c10->cur_page = -1;
>>> +
>>> +   ret = ov05c10_init_controls(ov05c10);
>>> +   if (ret)
>>> +           return dev_err_probe(&client->dev, ret, "fail to init ctl\n");
>>> +
>>> +   ov05c10->sd.internal_ops = &ov05c10_internal_ops;
>>> +   ov05c10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>> +   ov05c10->sd.entity.ops = &ov05c10_subdev_entity_ops;
>>> +   ov05c10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
>>> +
>>> +   ov05c10->pad.flags = MEDIA_PAD_FL_SOURCE;
>>> +
>>> +   ret = media_entity_pads_init(&ov05c10->sd.entity, NUM_OF_PADS,
>>> +                                &ov05c10->pad);
>>> +   if (ret)
>>> +           goto err_hdl_free;
>>> +
>>> +   ret = v4l2_subdev_init_finalize(&ov05c10->sd);
>>> +   if (ret < 0)
>>> +           goto err_media_entity_cleanup;
>>> +
>>> +   ret = v4l2_async_register_subdev_sensor(&ov05c10->sd);
>>> +   if (ret)
>>> +           goto err_media_entity_cleanup;
>>> +
>>> +   pm_runtime_set_active(&client->dev);
>>> +   pm_runtime_enable(&client->dev);
>>> +   pm_runtime_idle(&client->dev);
>>> +   pm_runtime_set_autosuspend_delay(&client->dev, 1000);
>>> +   pm_runtime_use_autosuspend(&client->dev);
>>> +   return 0;
>>> +
>>> +err_media_entity_cleanup:
>>> +   media_entity_cleanup(&ov05c10->sd.entity);
>>> +
>>> +err_hdl_free:
>>> +   v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
>>> +
>>> +   return ret;
>>> +}
>>> +
>>> +static void ov05c10_remove(struct i2c_client *client)
>>> +{
>>> +   struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>> +   struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>> +
>>> +   v4l2_async_unregister_subdev(sd);
>>> +   media_entity_cleanup(&sd->entity);
>>> +   v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
>>> +
>>> +   pm_runtime_disable(&client->dev);
>>> +   pm_runtime_set_suspended(&client->dev);
>>> +}
>>> +
>>> +static int ov05c10_runtime_resume(struct device *dev)
>>> +{
>>> +   struct v4l2_subdev *sd = dev_get_drvdata(dev);
>>> +   struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>> +
>>> +   ov05c10_sensor_power_set(ov05c10, true);
>>> +   return 0;
>>> +}
>>> +
>>> +static int ov05c10_runtime_suspend(struct device *dev)
>>> +{
>>> +   struct v4l2_subdev *sd = dev_get_drvdata(dev);
>>> +   struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>> +
>>> +   ov05c10_sensor_power_set(ov05c10, false);
>>> +   return 0;
>>> +}
>>> +
>>> +static DEFINE_RUNTIME_DEV_PM_OPS(ov05c10_pm_ops, ov05c10_runtime_suspend,
>>> +                            ov05c10_runtime_resume, NULL);
>>> +
>>> +static const struct i2c_device_id ov05c10_i2c_ids[] = {
>>> +   {"ov05c10", 0 },
>>> +   { }
>>> +};
>>> +MODULE_DEVICE_TABLE(i2c, ov05c10_i2c_ids);
>>> +
>>> +static struct i2c_driver ov05c10_i2c_driver = {
>>> +   .driver = {
>>> +           .name = DRV_NAME,
>>> +           .pm = pm_ptr(&ov05c10_pm_ops),
>>> +   },
>>> +   .id_table = ov05c10_i2c_ids,
>>> +   .probe = ov05c10_probe,
>>> +   .remove = ov05c10_remove,
>>> +};
>>> +
>>> +module_i2c_driver(ov05c10_i2c_driver);
>>> +
>>> +MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
>>> +MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@amd.com>");
>>> +MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
>>> +MODULE_DESCRIPTION("OmniVision OV05C1010 sensor driver");
>>
>> OV05C10
>>
>>> +MODULE_LICENSE("GPL");
>>
>>
>> Hi Sakari,
>>
>> Seems there are already several camera sensors using page-based registers.
>> Is it a good idea to add page support in CCI interface?
> 
> Sounds like a good idea as such but I'm not sure how common this really is,
> I think I've seen a few Omnivision sensors doing this. If implemented, I
> think it would be nice if the page could be encoded in the register address
> which V4L2 CCI would store and switch page if needed only. This would
> require serialising accesses, too. There's some room in CCI register raw
> value space so this could be done without even changing that, say, with
> 8-bit page and 8-bit register address.
> 
Hi Sakari, thank you for sharing your insights and guiding us. Could you 
please suggest if we should take up this work implementing the helpers 
in CCI and submit the patch or is it okay to leave it as-is for now and 
take care of updating in future once the implementation is ready.

Thanks,
Pratap

> --
> Kind regards,
> 
> Sakari Ailus


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-13 12:05     ` Bryan O'Donoghue
@ 2025-06-16 23:15       ` Nirujogi, Pratap
  0 siblings, 0 replies; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-16 23:15 UTC (permalink / raw)
  To: Bryan O'Donoghue, Kieran Bingham, Hao Yao, Pratap Nirujogi,
	sakari.ailus
  Cc: mchehab, laurent.pinchart, hverkuil, krzk, dave.stevenson,
	hdegoede, jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	dongcheng.yan, jason.z.chen, jimmy.su



On 6/13/2025 8:05 AM, Bryan O'Donoghue wrote:
> Caution: This message originated from an External Source. Use proper 
> caution when opening attachments, clicking links, or responding.
> 
> 
> On 13/06/2025 12:02, Kieran Bingham wrote:
>> Quoting Hao Yao (2025-06-13 05:55:46)
>>> Hi Pratap,
>>>
>>> Thanks for your patch.
>>>
>>> This patch is written for your camera sensor module, which seems very
>>> different from those already applied on Dell laptops (some of "Dell Pro"
>>> series). Looking into the driver, I think this version will break the
>> Have there been existing efforts from Intel to upstream support for that
>> device?
> 
> FWIW +1
> 
> Qualcomm devices - Acer Swift 14 AI, HP OmniBook x14 both use this sensor.
> 
> I'd expect though that aside from OF bindings, regulators and clocks
> that any upstream configuration with the right number of lanes would
> "just work", including this one from AMD.
> 
> That has been the experience picking up OV02E10 and OV02C10 from the
> IPU6 repository where its ACPI binding and repurposing to OF/Qcom.
> 
> So how incompatible could OV05C10 be between different x86/ACPI systems
> ? Less than the gap between x86/ACPI and Arm/OF you'd imagine.
> 
> Getting any OV05C10 driver upstream would be great, we can work from
> there to bridge whatever gap needs be for !AMD.
> 
Hi Bryan, thanks for your support and we will be happy to contribute too 
to bridge the gaps and make the driver as generic as possible to support 
both AMD && !AMD platforms.

Thanks,
Pratap
> ---
> bod


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-13 11:02   ` Kieran Bingham
  2025-06-13 12:05     ` Bryan O'Donoghue
@ 2025-06-16 23:46     ` Nirujogi, Pratap
  2025-06-28 19:23       ` Sakari Ailus
  1 sibling, 1 reply; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-16 23:46 UTC (permalink / raw)
  To: Kieran Bingham, Hao Yao, Pratap Nirujogi, sakari.ailus
  Cc: mchehab, laurent.pinchart, hverkuil, bryan.odonoghue, krzk,
	dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen, linux-media,
	linux-kernel, benjamin.chan, bin.du, grosikop, king.li, dantony,
	vengutta, dongcheng.yan, jason.z.chen, jimmy.su

Hi Kieran,

Thank you for reviewing and sharing your feedback.

On 6/13/2025 7:02 AM, Kieran Bingham wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> Quoting Hao Yao (2025-06-13 05:55:46)
>> Hi Pratap,
>>
>> Thanks for your patch.
>>
>> This patch is written for your camera sensor module, which seems very
>> different from those already applied on Dell laptops (some of "Dell Pro"
>> series). Looking into the driver, I think this version will break the
> 
> Have there been existing efforts from Intel to upstream support for that
> device?
> 
> Or is this simply the case that Intel have lost the race by not working
> towards upstream?
> 
> I see plenty of references to OV05C10 in the intel/ipu6 repositories on
> github - but that doesn't count as upstream here ...
> 
> Perhaps could Intel work to support the additional requirements on top
> of AMD's driver ?
> 
> 
> But having multiple devices use the same sensor driver is a good thing
> here.
> 
> I think it will highlight that werever possible - the code below should
> be factored out to support the different configuration requirements.
> Cleaning up the large tables of register addresses and making those
> configurable functions for example configuring the link rate
> independently would be really beneficial!
> 
> That's precisely why we continually push for reducing the large
> "undocumented register" tables in sensor drivers...
> 
Yes, I agree, it is best to have documented settings and make 
calculation for sensor modes instead of array of undocumented settings. 
However the usual practice is that we send requirements to sensor vendor 
and they provide us the settings array to be applied. May be we can try 
to move PLL settings to different array but it is not always just the 
PLL, there are a lot of undocumented settings which are in sync with PLL 
and sometimes changing the PLL may result in misbehaviour of the sensor...

We will try to move PLL settings to separate array but cannot guarantee 
changing these settings alone will make the sensor functional.

> 
> 
>> devices using ov05c10 sensor.
>>
>> I think this patch is better to be validated on existing devices, but
>> please do some fixes before we can do validation. Please check my
>> comments inline.
>>
>>
>> On 2025/6/10 03:42, Pratap Nirujogi wrote:
>>> Add driver for OmniVision 5.2M OV05C10 sensor. This driver
>>> supports only the full size normal 2888x1808@30fps 2-lane
>>> sensor profile.
>>>
>>> Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>>> Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>>> Co-developed-by: Bin Du <bin.du@amd.com>
>>> Signed-off-by: Bin Du <bin.du@amd.com>
>>> Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
>>> ---
>>> Changes v2 -> v3:
>>>
>>> * Update "refclk" property variable as "clock-frequency".
>>> * Update sensor GPIO connector id name.
>>> * Fix sensor v4l2 compliance issue.
>>> * Fix license info.
>>> * Address review comments.
>>>
>>>    MAINTAINERS                 |    8 +
>>>    drivers/media/i2c/Kconfig   |   10 +
>>>    drivers/media/i2c/Makefile  |    1 +
>>>    drivers/media/i2c/ov05c10.c | 1061 +++++++++++++++++++++++++++++++++++
>>>    4 files changed, 1080 insertions(+)
>>>    create mode 100644 drivers/media/i2c/ov05c10.c
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index a92290fffa16..caca25d00bf2 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -18303,6 +18303,14 @@ T:   git git://linuxtv.org/media.git
>>>    F:  Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml
>>>    F:  drivers/media/i2c/ov02e10.c
>>>
>>> +OMNIVISION OV05C10 SENSOR DRIVER
>>> +M:   Nirujogi Pratap <pratap.nirujogi@amd.com>
>>> +M:   Bin Du <bin.du@amd.com>
>>> +L:   linux-media@vger.kernel.org
>>> +S:   Maintained
>>> +T:   git git://linuxtv.org/media.git
>>> +F:   drivers/media/i2c/ov05c10.c
>>> +
>>>    OMNIVISION OV08D10 SENSOR DRIVER
>>>    M:  Jimmy Su <jimmy.su@intel.com>
>>>    L:  linux-media@vger.kernel.org
>>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>>> index e68202954a8f..1662fb29d75c 100644
>>> --- a/drivers/media/i2c/Kconfig
>>> +++ b/drivers/media/i2c/Kconfig
>>> @@ -377,6 +377,16 @@ config VIDEO_OV02C10
>>>          To compile this driver as a module, choose M here: the
>>>          module will be called ov02c10.
>>>
>>> +config VIDEO_OV05C10
>>> +     tristate "OmniVision OV05C10 sensor support"
>>> +     select V4L2_CCI_I2C
>>> +     help
>>> +       This is a Video4Linux2 sensor driver for the OmniVision
>>> +       OV05C10 camera.
>>> +
>>> +       To compile this driver as a module, choose M here: the
>>> +       module will be called OV05C10.
>>> +
>>>    config VIDEO_OV08D10
>>>            tristate "OmniVision OV08D10 sensor support"
>>>            help
>>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>>> index 5873d29433ee..b4a1d721a7f2 100644
>>> --- a/drivers/media/i2c/Makefile
>>> +++ b/drivers/media/i2c/Makefile
>>> @@ -85,6 +85,7 @@ obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
>>>    obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
>>>    obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o
>>>    obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o
>>> +obj-$(CONFIG_VIDEO_OV05C10) += ov05c10.o
>>>    obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
>>>    obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
>>>    obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
>>> diff --git a/drivers/media/i2c/ov05c10.c b/drivers/media/i2c/ov05c10.c
>>> new file mode 100644
>>> index 000000000000..9a1e493c4073
>>> --- /dev/null
>>> +++ b/drivers/media/i2c/ov05c10.c
>>> @@ -0,0 +1,1061 @@
>>> +// SPDX-License-Identifier: GPL-2.0+
>>> +// Copyright (C) 2025 Advanced Micro Devices, Inc.
>>> +
>>> +#include <linux/clk.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/gpio.h>
>>> +#include <linux/i2c.h>
>>> +#include <linux/module.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/units.h>
>>> +#include <media/v4l2-cci.h>
>>> +#include <media/v4l2-ctrls.h>
>>> +#include <media/v4l2-device.h>
>>> +#include <media/v4l2-fwnode.h>
>>> +
>>> +#define DRV_NAME                     "ov05c10"
>>> +#define OV05C10_REF_CLK                      (24 * HZ_PER_MHZ)
>>
>> Seems your module use 24 MHz clock input. The Dell's modules always use
>> 19.2MHz, which means your the PLL settings will not work on Dell's.
> 
> That sounds like a feature that Dell and Intel could work towards
> supporting ?
> 
> For instance, we made the Sony IMX283 support multiple input frequencies:
> 
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/media/i2c/imx283.c#n263
> 
> Of course it would be even better if we could always dynamically
> calculate the PLLs correctly based on the input clocks.
> 
> And if we can make the drivers more freely configurable instead of
> mode-based - then they would be more adaptable to the different platform
> configurations possible in different environments.
> 
Yes, I agree, we will work with the sensor vendor to get more details 
about PLL registers and configurations that can be dynamically 
calculated and configured based on the input clocks. We will experiment 
based on the feedback received from sensor vendor and will update if we 
can make progress.

>>
>>> +
>>> +#define MODE_WIDTH  2888
>>> +#define MODE_HEIGHT 1808
>>> +
>>> +#define PAGE_NUM_MASK                        0xff000000
>>> +#define PAGE_NUM_SHIFT                       24
>>> +#define REG_ADDR_MASK                        0x00ffffff
>>> +
>>> +#define OV05C10_SYSCTL_PAGE          (0 << PAGE_NUM_SHIFT)
>>> +#define OV05C10_CISCTL_PAGE          (1 << PAGE_NUM_SHIFT)
>>> +#define OV05C10_ISPCTL_PAGE          (4 << PAGE_NUM_SHIFT)
>>> +
>>> +/* Chip ID */
>>> +#define OV05C10_REG_CHIP_ID          (CCI_REG24(0x00) | OV05C10_SYSCTL_PAGE)
>>> +#define OV05C10_CHIP_ID                      0x43055610
>>> +
>>> +/* Control registers */
>>> +#define OV05C10_REG_TRIGGER          (CCI_REG8(0x01) | OV05C10_CISCTL_PAGE)
>>> +#define OV05C_REG_TRIGGER_START              BIT(0)
>>> +
>>> +/* Exposure control */
>>> +#define OV05C10_REG_EXPOSURE         (CCI_REG24(0x02) | OV05C10_CISCTL_PAGE)
>>> +#define OV05C10_EXPOSURE_MAX_MARGIN  33
>>> +#define OV05C10_EXPOSURE_MIN         4
>>> +#define OV05C10_EXPOSURE_STEP                1
>>> +#define OV05C10_EXPOSURE_DEFAULT     0x40
>>> +
>>> +/* V_TIMING internal */
>>> +#define OV05C10_REG_VTS                      (CCI_REG16(0x05) | OV05C10_CISCTL_PAGE)
>>> +#define OV05C10_VTS_30FPS            1860
>>> +#define OV05C10_VTS_MAX                      0x7fff
>>> +
>>> +/* Test Pattern Control */
>>> +#define OV05C10_REG_TEST_PATTERN     (CCI_REG8(0x12) | OV05C10_ISPCTL_PAGE)
>>> +#define OV05C10_TEST_PATTERN_ENABLE  BIT(0)
>>> +#define OV05C10_REG_TEST_PATTERN_CTL (CCI_REG8(0xf3) | OV05C10_ISPCTL_PAGE)
>>> +#define OV05C10_REG_TEST_PATTERN_XXX BIT(0)
>>> +
>>> +/* Digital gain control */
>>> +#define OV05C10_REG_DGTL_GAIN_H              (CCI_REG8(0x21) | OV05C10_CISCTL_PAGE)
>>> +#define OV05C10_REG_DGTL_GAIN_L              (CCI_REG8(0x22) | OV05C10_CISCTL_PAGE)
>>> +
>>> +#define OV05C10_DGTL_GAIN_MIN                0x40
>>> +#define OV05C10_DGTL_GAIN_MAX                0xff
>>> +#define OV05C10_DGTL_GAIN_DEFAULT    0x40
>>> +#define OV05C10_DGTL_GAIN_STEP               1
>>> +
>>> +#define OV05C10_DGTL_GAIN_L_MASK     0xff
>>> +#define OV05C10_DGTL_GAIN_H_SHIFT    8
>>> +#define OV05C10_DGTL_GAIN_H_MASK     0xff00
>>> +
>>> +/* Analog gain control */
>>> +#define OV05C10_REG_ANALOG_GAIN              (CCI_REG8(0x24) | OV05C10_CISCTL_PAGE)
>>> +#define OV05C10_ANA_GAIN_MIN         0x80
>>> +#define OV05C10_ANA_GAIN_MAX         0x07c0
>>> +#define OV05C10_ANA_GAIN_STEP                1
>>> +#define OV05C10_ANA_GAIN_DEFAULT     0x80
>>> +
>>> +/* H TIMING internal */
>>> +#define OV05C10_REG_HTS                      (CCI_REG16(0x37) | OV05C10_CISCTL_PAGE)
>>> +#define OV05C10_HTS_30FPS            0x0280
>>> +
>>> +/* Page selection */
>>> +#define OV05C10_REG_PAGE_CTL         CCI_REG8(0xfd)
>>> +
>>> +#define NUM_OF_PADS 1
>>> +
>>> +#define OV05C10_GET_PAGE_NUM(reg)    (((reg) & PAGE_NUM_MASK) >>\
>>> +                                      PAGE_NUM_SHIFT)
>>> +#define OV05C10_GET_REG_ADDR(reg)    ((reg) & REG_ADDR_MASK)
>>> +
>>> +enum {
>>> +     OV05C10_LINK_FREQ_900MHZ_INDEX,
>>> +};
>>> +
>>> +struct ov05c10_reg_list {
>>> +     u32 num_of_regs;
>>> +     const struct cci_reg_sequence *regs;
>>> +};
>>> +
>>> +/* Mode : resolution and related config&values */
>>> +struct ov05c10_mode {
>>> +     /* Frame width */
>>> +     u32 width;
>>> +     /* Frame height */
>>> +     u32 height;
>>> +     /* number of lanes */
>>> +     u32 lanes;
>>> +
>>> +     /* V-timing */
>>> +     u32 vts_def;
>>> +     u32 vts_min;
>>> +
>>> +     /* HTS */
>>> +     u32 hts;
>>> +
>>> +     /* Index of Link frequency config to be used */
>>> +     u32 link_freq_index;
>>> +
>>> +     /* Default register values */
>>> +     struct ov05c10_reg_list reg_list;
>>> +};
>>> +
>>> +static const s64 ov05c10_link_frequencies[] = {
>>> +     925 * HZ_PER_MHZ,
>>> +};
>>
>> Is it 900 MHz, or 925 MHz?
>>
>>> +
>>> +/* 2888x1808 30fps, 1800mbps, 2lane, 24mhz */
>>
>> Currently Dell's devices with ov05c10 use a CV chip to passthrough MIPI
>> CSI signals, but it supports max 750 MHz link frequency. That's why this
>> version:
>> https://github.com/intel/ipu6-drivers/blob/master/drivers/media/i2c/ov05c10.c
>> uses 480 MHz link frequency and a different resolution setting
>> (2800x1576). At least the setting in out-of-tree Github driver should be
>> merged into this version.
> 
> If Pratap doesn't have that device, can you work towards adding the
> support and testing?
> 
> 
> 
>>
>>> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
>>> +     { CCI_REG8(0xfd),  0x00 },
>>> +     { CCI_REG8(0x20),  0x00 },
>>> +     { CCI_REG8(0xfd),  0x00 },
>>> +     { CCI_REG8(0x20),  0x0b },
>>> +     { CCI_REG8(0xc1),  0x09 },
>>> +     { CCI_REG8(0x21),  0x06 },
>>> +     { CCI_REG8(0x14),  0x78 },
>>> +     { CCI_REG8(0xe7),  0x03 },
>>> +     { CCI_REG8(0xe7),  0x00 },
>>> +     { CCI_REG8(0x21),  0x00 },
>>> +     { CCI_REG8(0xfd),  0x01 },
>>> +     { CCI_REG8(0x03),  0x00 },
>>> +     { CCI_REG8(0x04),  0x06 },
>>> +     { CCI_REG8(0x05),  0x07 },
>>> +     { CCI_REG8(0x06),  0x44 },
>>> +     { CCI_REG8(0x07),  0x08 },
>>> +     { CCI_REG8(0x1b),  0x01 },
>>> +     { CCI_REG8(0x24),  0xff },
>>> +     { CCI_REG8(0x32),  0x03 },
>>> +     { CCI_REG8(0x42),  0x5d },
>>> +     { CCI_REG8(0x43),  0x08 },
>>> +     { CCI_REG8(0x44),  0x81 },
>>> +     { CCI_REG8(0x46),  0x5f },
>>> +     { CCI_REG8(0x48),  0x18 },
>>> +     { CCI_REG8(0x49),  0x04 },
>>> +     { CCI_REG8(0x5c),  0x18 },
>>> +     { CCI_REG8(0x5e),  0x13 },
>>> +     { CCI_REG8(0x70),  0x15 },
>>> +     { CCI_REG8(0x77),  0x35 },
>>> +     { CCI_REG8(0x79),  0x00 },
>>> +     { CCI_REG8(0x7b),  0x08 },
>>> +     { CCI_REG8(0x7d),  0x08 },
>>> +     { CCI_REG8(0x7e),  0x08 },
>>> +     { CCI_REG8(0x7f),  0x08 },
>>> +     { CCI_REG8(0x90),  0x37 },
>>> +     { CCI_REG8(0x91),  0x05 },
>>> +     { CCI_REG8(0x92),  0x18 },
>>> +     { CCI_REG8(0x93),  0x27 },
>>> +     { CCI_REG8(0x94),  0x05 },
>>> +     { CCI_REG8(0x95),  0x38 },
>>> +     { CCI_REG8(0x9b),  0x00 },
>>> +     { CCI_REG8(0x9c),  0x06 },
>>> +     { CCI_REG8(0x9d),  0x28 },
>>> +     { CCI_REG8(0x9e),  0x06 },
>>> +     { CCI_REG8(0xb2),  0x0f },
>>> +     { CCI_REG8(0xb3),  0x29 },
>>> +     { CCI_REG8(0xbf),  0x3c },
>>> +     { CCI_REG8(0xc2),  0x04 },
>>> +     { CCI_REG8(0xc4),  0x00 },
>>> +     { CCI_REG8(0xca),  0x20 },
>>> +     { CCI_REG8(0xcb),  0x20 },
>>> +     { CCI_REG8(0xcc),  0x28 },
>>> +     { CCI_REG8(0xcd),  0x28 },
>>> +     { CCI_REG8(0xce),  0x20 },
>>> +     { CCI_REG8(0xcf),  0x20 },
>>> +     { CCI_REG8(0xd0),  0x2a },
>>> +     { CCI_REG8(0xd1),  0x2a },
>>> +     { CCI_REG8(0xfd),  0x0f },
>>> +     { CCI_REG8(0x00),  0x00 },
>>> +     { CCI_REG8(0x01),  0xa0 },
>>> +     { CCI_REG8(0x02),  0x48 },
>>> +     { CCI_REG8(0x07),  0x8f },
>>> +     { CCI_REG8(0x08),  0x70 },
>>> +     { CCI_REG8(0x09),  0x01 },
>>> +     { CCI_REG8(0x0b),  0x40 },
>>> +     { CCI_REG8(0x0d),  0x07 },
>>> +     { CCI_REG8(0x11),  0x33 },
>>> +     { CCI_REG8(0x12),  0x77 },
>>> +     { CCI_REG8(0x13),  0x66 },
>>> +     { CCI_REG8(0x14),  0x65 },
>>> +     { CCI_REG8(0x15),  0x37 },
>>> +     { CCI_REG8(0x16),  0xbf },
>>> +     { CCI_REG8(0x17),  0xff },
>>> +     { CCI_REG8(0x18),  0xff },
>>> +     { CCI_REG8(0x19),  0x12 },
>>> +     { CCI_REG8(0x1a),  0x10 },
>>> +     { CCI_REG8(0x1c),  0x77 },
>>> +     { CCI_REG8(0x1d),  0x77 },
>>> +     { CCI_REG8(0x20),  0x0f },
>>> +     { CCI_REG8(0x21),  0x0f },
>>> +     { CCI_REG8(0x22),  0x0f },
>>> +     { CCI_REG8(0x23),  0x0f },
>>> +     { CCI_REG8(0x2b),  0x20 },
>>> +     { CCI_REG8(0x2c),  0x20 },
>>> +     { CCI_REG8(0x2d),  0x04 },
>>> +     { CCI_REG8(0xfd),  0x03 },
>>> +     { CCI_REG8(0x9d),  0x0f },
>>> +     { CCI_REG8(0x9f),  0x40 },
>>> +     { CCI_REG8(0xfd),  0x00 },
>>> +     { CCI_REG8(0x20),  0x1b },
>>> +     { CCI_REG8(0xfd),  0x04 },
>>> +     { CCI_REG8(0x19),  0x60 },
>>> +     { CCI_REG8(0xfd),  0x02 },
>>> +     { CCI_REG8(0x75),  0x05 },
>>> +     { CCI_REG8(0x7f),  0x06 },
>>> +     { CCI_REG8(0x9a),  0x03 },
>>> +     { CCI_REG8(0xa2),  0x07 },
>>> +     { CCI_REG8(0xa3),  0x10 },
>>> +     { CCI_REG8(0xa5),  0x02 },
>>> +     { CCI_REG8(0xa6),  0x0b },
>>> +     { CCI_REG8(0xa7),  0x48 },
>>> +     { CCI_REG8(0xfd),  0x07 },
>>> +     { CCI_REG8(0x42),  0x00 },
>>> +     { CCI_REG8(0x43),  0x80 },
>>> +     { CCI_REG8(0x44),  0x00 },
>>> +     { CCI_REG8(0x45),  0x80 },
>>> +     { CCI_REG8(0x46),  0x00 },
>>> +     { CCI_REG8(0x47),  0x80 },
>>> +     { CCI_REG8(0x48),  0x00 },
>>> +     { CCI_REG8(0x49),  0x80 },
>>> +     { CCI_REG8(0x00),  0xf7 },
>>> +     { CCI_REG8(0xfd),  0x00 },
>>> +     { CCI_REG8(0xe7),  0x03 },
>>> +     { CCI_REG8(0xe7),  0x00 },
>>> +     { CCI_REG8(0xfd),  0x00 },
>>> +     { CCI_REG8(0x93),  0x18 },
>>> +     { CCI_REG8(0x94),  0xff },
>>> +     { CCI_REG8(0x95),  0xbd },
>>> +     { CCI_REG8(0x96),  0x1a },
>>> +     { CCI_REG8(0x98),  0x04 },
>>> +     { CCI_REG8(0x99),  0x08 },
>>> +     { CCI_REG8(0x9b),  0x10 },
>>> +     { CCI_REG8(0x9c),  0x3f },
>>> +     { CCI_REG8(0xa1),  0x05 },
>>> +     { CCI_REG8(0xa4),  0x2f },
>>> +     { CCI_REG8(0xc0),  0x0c },
>>> +     { CCI_REG8(0xc1),  0x08 },
>>> +     { CCI_REG8(0xc2),  0x00 },
>>> +     { CCI_REG8(0xb6),  0x20 },
>>> +     { CCI_REG8(0xbb),  0x80 },
>>> +     { CCI_REG8(0xfd),  0x00 },
>>> +     { CCI_REG8(0xa0),  0x01 },
>>> +     { CCI_REG8(0xfd),  0x01 },
>>> +};
>>> +
>>> +static const struct cci_reg_sequence mode_OV05C10_stream_on_regs[] = {
> 
> Why is the word 'mode_ in here ?
> 
We agree that the mode_ prefix isn't necessary, this was arbitrarily 
chosen, and we'll remove it in the next patch.

>>> +     { CCI_REG8(0xfd), 0x01 },
>>> +     { CCI_REG8(0x33), 0x03 },
>>> +     { CCI_REG8(0x01), 0x02 },
>>> +     { CCI_REG8(0xfd), 0x00 },
>>> +     { CCI_REG8(0x20), 0x1f },
>>> +     { CCI_REG8(0xfd), 0x01 },
>>> +};
>>> +
>>> +static const struct cci_reg_sequence mode_OV05C10_stream_off_regs[] = {
> 
> And why are these two register tables now OV05C10 rather than ov05c10
> (capital case, while everything else is lower case)
> 
I agree OV05C10 need not be in upper case, will change it to lower case 
in next patch.

> 
>>> +     { CCI_REG8(0xfd), 0x00 },
>>> +     { CCI_REG8(0x20), 0x5b },
>>> +     { CCI_REG8(0xfd), 0x01 },
>>> +     { CCI_REG8(0x33), 0x02 },
>>> +     { CCI_REG8(0x01), 0x02 },
> 
> Are there any registers in any of the tables we can name to make the
> driver more maintainable? Or extract to a directly coded function to
> make it more explicit perhaps?
> 
> In both those tables 0xfd gets written in a curious manner - is that
> another of the page selection type accesses ?
> 
> In fact, I can see that it is the page control register indeed - so
> perhaps it would make more sense to code this through the page
> accessors!
> 
To be honest this is the sequence received from sensor vendor and we are 
using it as-is. We need to check with them for the descriptions of the 
registers, will reachout to them and hoping they will answer..

> 
> 
>>> +};
>>> +
>>> +static const char * const ov05c10_test_pattern_menu[] = {
>>> +     "Disabled",
>>> +     "Vertical Color Bar Type 1",
>>> +     "Vertical Color Bar Type 2",
>>> +     "Vertical Color Bar Type 3",
>>> +     "Vertical Color Bar Type 4"
>>> +};
>>> +
>>> +/* Configurations for supported link frequencies */
>>> +#define OV05C10_LINK_FREQ_900MHZ     (900 * HZ_PER_MHZ)
>>> +
>>> +/* Number of lanes supported */
>>> +#define OV05C10_DATA_LANES           2
>>> +
>>> +/* Bits per sample of sensor output */
>>> +#define OV05C10_BITS_PER_SAMPLE              10
>>> +
>>> +/*
>>> + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
>>> + * data rate => double data rate; number of lanes => 2; bits per pixel => 10
>>> + */
>>> +static u64 link_freq_to_pixel_rate(u64 f, u32 lane_nr)
>>> +{
>>> +     f *= 2 * lane_nr;
>>> +     do_div(f, OV05C10_BITS_PER_SAMPLE);
>>> +
>>> +     return f;
>>> +}
>>> +
>>> +/* Menu items for LINK_FREQ V4L2 control */
>>> +static const s64 ov05c10_link_freq_menu_items[] = {
>>> +     OV05C10_LINK_FREQ_900MHZ,
>>> +};
>>> +
>>> +/* Mode configs, currently, only support 1 mode */
>>> +static const struct ov05c10_mode supported_mode = {
>>> +     .width = MODE_WIDTH,
>>> +     .height = MODE_HEIGHT,
>>> +     .vts_def = OV05C10_VTS_30FPS,
>>> +     .vts_min = OV05C10_VTS_30FPS,
>>> +     .hts = 640,
>>> +     .lanes = 2,
>>> +     .reg_list = {
>>> +             .num_of_regs = ARRAY_SIZE(ov05c10_2888x1808_regs),
>>> +             .regs = ov05c10_2888x1808_regs,
>>> +     },
>>> +     .link_freq_index = OV05C10_LINK_FREQ_900MHZ_INDEX,
>>> +};
>>> +
>>> +struct ov05c10 {
>>> +     struct v4l2_subdev sd;
>>> +     struct media_pad pad;
>>> +
>>> +     /* V4L2 control handler */
>>> +     struct v4l2_ctrl_handler ctrl_handler;
>>> +
>>> +     /* V4L2 Controls */
>>> +     struct v4l2_ctrl *link_freq;
>>> +     struct v4l2_ctrl *pixel_rate;
>>> +     struct v4l2_ctrl *vblank;
>>> +     struct v4l2_ctrl *hblank;
>>> +     struct v4l2_ctrl *exposure;
>>> +
>>> +     struct regmap *regmap;
>>> +
>>> +     /* gpio descriptor */
>>> +     struct gpio_desc *enable_gpio;
>>> +
>>> +     /* Current page for sensor register control */
>>> +     int cur_page;
>>> +};
>>> +
>>> +#define to_ov05c10(_sd)      container_of(_sd, struct ov05c10, sd)
>>> +
>>> +static int ov05c10_init_state(struct v4l2_subdev *sd,
>>> +                           struct v4l2_subdev_state *sd_state)
>>> +{
>>> +     struct v4l2_mbus_framefmt *frame_fmt;
>>> +     struct v4l2_subdev_format fmt = {
>>> +             .which = V4L2_SUBDEV_FORMAT_TRY,
>>> +             .format = {
>>> +                     .width = MODE_WIDTH,
>>> +                     .height = MODE_HEIGHT,
>>> +                     .code = MEDIA_BUS_FMT_SGRBG10_1X10,
>>> +                     .field = V4L2_FIELD_NONE,
>>> +             }
>>> +     };
>>> +
>>> +     frame_fmt = v4l2_subdev_state_get_format(sd_state, 0);
>>> +     *frame_fmt = fmt.format;
>>> +     return 0;
>>> +}
>>> +
>>> +static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page, int *err)
>>
>> Seems nobody cares the return value of ov05c10_switch_page() or
>> ov05c10_reg_write(), etc.. It should be better to use void return, or
>> use return value instead of int *err.
> 
> This style probably comes from the way that the CCI helpers were
> written, to allow consectuive writes to avoid having to duplicate error
> checks when writing lots of registers.
> 
> Depending on how the generic page switching could be implemented, there
> may still be benefit to tracking the *err and I would still keep the
> return ret type - as there are times where it can still make sense to
> write:
> 
>          ret = ov05c10_switch_page(..)
> etc...
> 
We will update this method as Laurent suggested. This will be fixed in 
the next version.
> 
>>
>>> +{
>>> +     int ret = 0;
>>> +
>>> +     if (err && *err)
>>> +             return *err;
>>> +
>>> +     if (page != ov05c10->cur_page) {
>>> +             cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, &ret);
>>> +             if (!ret)
>>> +                     ov05c10->cur_page = page;
>>> +     }
>>> +
>>> +     if (err)
>>> +             *err = ret;
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +/* refer to the implementation of cci_read */
>>> +static int ov05c10_reg_read(struct ov05c10 *ov05c10, u32 reg,
>>> +                         u64 *val, int *err)
>>> +{
>>> +     u32 page;
>>> +     u32 addr;
>>> +     int ret = 0;
>>> +
>>> +     if (err && *err)
>>> +             return *err;
>>> +
>>> +     page = OV05C10_GET_PAGE_NUM(reg);
>>> +     addr = OV05C10_GET_REG_ADDR(reg);
>>> +     ov05c10_switch_page(ov05c10, page, &ret);
>>> +     cci_read(ov05c10->regmap, addr, val, &ret);
>>> +     if (err)
>>> +             *err = ret;
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +/* refer to the implementation of cci_write */
>>> +static int ov05c10_reg_write(struct ov05c10 *ov05c10, u32 reg,
>>> +                          u64 val, int *err)
>>> +{
>>> +     u32 page;
>>> +     u32 addr;
>>> +     int ret = 0;
>>> +
>>> +     if (err && *err)
>>> +             return *err;
>>> +
>>> +     page = OV05C10_GET_PAGE_NUM(reg);
>>> +     addr = OV05C10_GET_REG_ADDR(reg);
>>> +     ov05c10_switch_page(ov05c10, page, &ret);
>>> +     cci_write(ov05c10->regmap, addr, val, &ret);
>>> +     if (err)
>>> +             *err = ret;
>>> +
>>> +     return ret;
>>> +}
> 
> One of the main goals of CCI helpers was to avoid all of the custom
> device accessors being duplicated in each driver, so I think extending
> the CCI helpers to support page based accesses in some common way would
> be beneficial.
> 
Yes, I agree. We can take on this enhancement either now or in the 
future, depending on the direction. We'll wait for Sakari's feedback to 
determine the best way to proceed.

Thanks,
Pratap

> 
>>> +
>>> +static int ov05c10_update_vblank(struct ov05c10 *ov05c10, u32 vblank)
>>> +{
>>> +     const struct ov05c10_mode *mode = &supported_mode;
>>> +     u64 val;
>>> +     int ret = 0;
>>> +
>>> +     val = mode->height + vblank;
>>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_VTS, val, &ret);
>>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>> +                       OV05C_REG_TRIGGER_START, &ret);
>>> +
>>> +     return ret;
>>> +}
>>
>> I remembered that the ov05c10 VTS control (P1:0x05~0x06) is a bit weird.
>> This register seems take the increment of VTS value, so direct write of
>> VTS value will not set it properly. Does this version make AE working on
>> your platform?
>>
>>> +
>>> +static int ov05c10_update_exposure(struct ov05c10 *ov05c10, u32 exposure)
>>> +{
>>> +     int ret = 0;
>>> +
>>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_EXPOSURE, exposure, &ret);
>>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>> +                       OV05C_REG_TRIGGER_START, &ret);
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static int ov05c10_update_analog_gain(struct ov05c10 *ov05c10, u32 a_gain)
>>> +{
>>> +     int ret = 0;
>>> +
>>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_ANALOG_GAIN, a_gain, &ret);
>>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>> +                       OV05C_REG_TRIGGER_START, &ret);
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static int ov05c10_update_digital_gain(struct ov05c10 *ov05c10, u32 d_gain)
>>> +{
>>> +     u64 val;
>>> +     int ret = 0;
>>> +
>>> +     val = d_gain & OV05C10_DGTL_GAIN_L_MASK;
>>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_L, val, &ret);
>>> +
>>> +     val = (d_gain & OV05C10_DGTL_GAIN_H_MASK) >> OV05C10_DGTL_GAIN_H_SHIFT;
>>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_H, val, &ret);
>>> +
>>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>> +                       OV05C_REG_TRIGGER_START, &ret);
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static int ov05c10_enable_test_pattern(struct ov05c10 *ov05c10, u32 pattern)
>>> +{
>>> +     u64 val;
>>> +     int ret = 0;
>>> +
>>> +     if (pattern) {
>>> +             ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
>>> +                              &val, &ret);
>>> +             ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
>>> +                               val | OV05C10_REG_TEST_PATTERN_XXX, &ret);
>>> +             ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
>>> +             val |= OV05C10_TEST_PATTERN_ENABLE;
>>> +     } else {
>>> +             ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, &val, &ret);
>>> +             val &= ~OV05C10_TEST_PATTERN_ENABLE;
>>> +     }
>>> +
>>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN, val, &ret);
>>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>>> +                       OV05C_REG_TRIGGER_START, &ret);
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static int ov05c10_set_ctrl(struct v4l2_ctrl *ctrl)
>>> +{
>>> +     struct ov05c10 *ov05c10 = container_of(ctrl->handler,
>>> +                                            struct ov05c10, ctrl_handler);
>>> +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>>> +     const struct ov05c10_mode *mode = &supported_mode;
>>> +     s64 max;
>>> +     int ret = 0;
>>> +
>>> +     /* Propagate change of current control to all related controls */
>>> +     if (ctrl->id == V4L2_CID_VBLANK) {
>>> +             s64 cur_exp = ov05c10->exposure->cur.val;
>>> +
>>> +             /* Update max exposure while meeting expected vblanking */
>>> +             max = mode->height + ctrl->val - OV05C10_EXPOSURE_MAX_MARGIN;
>>> +             cur_exp = clamp(cur_exp, ov05c10->exposure->minimum, max);
>>> +             ret = __v4l2_ctrl_modify_range(ov05c10->exposure,
>>> +                                            ov05c10->exposure->minimum,
>>> +                                            max, ov05c10->exposure->step,
>>> +                                            cur_exp);
>>> +             if (!ret)
>>> +                     return ret;
>>> +     }
>>> +
>>> +     /*
>>> +      * Applying V4L2 control value only happens
>>> +      * when power is up for streaming
>>> +      */
>>> +     if (!pm_runtime_get_if_in_use(&client->dev))
>>> +             return 0;
>>> +
>>> +     switch (ctrl->id) {
>>> +     case V4L2_CID_ANALOGUE_GAIN:
>>> +             ret = ov05c10_update_analog_gain(ov05c10, ctrl->val);
>>> +             break;
>>> +     case V4L2_CID_DIGITAL_GAIN:
>>> +             ret = ov05c10_update_digital_gain(ov05c10, ctrl->val);
>>> +             break;
>>> +     case V4L2_CID_EXPOSURE:
>>> +             ret = ov05c10_update_exposure(ov05c10, ctrl->val);
>>> +             break;
>>> +     case V4L2_CID_VBLANK:
>>> +             ret = ov05c10_update_vblank(ov05c10, ctrl->val);
>>> +             break;
>>> +     case V4L2_CID_TEST_PATTERN:
>>> +             ret = ov05c10_enable_test_pattern(ov05c10, ctrl->val);
>>> +             break;
>>> +     default:
>>> +             ret = -ENOTTY;
>>> +             dev_err(&client->dev,
>>> +                     "ctrl(id:0x%x,val:0x%x) is not handled\n",
>>> +                     ctrl->id, ctrl->val);
>>> +             break;
>>> +     }
>>> +
>>> +     pm_runtime_put(&client->dev);
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static const struct v4l2_ctrl_ops ov05c10_ctrl_ops = {
>>> +     .s_ctrl = ov05c10_set_ctrl,
>>> +};
>>> +
>>> +static int ov05c10_enum_mbus_code(struct v4l2_subdev *sd,
>>> +                               struct v4l2_subdev_state *sd_state,
>>> +                               struct v4l2_subdev_mbus_code_enum *code)
>>> +{
>>> +     /* Only one bayer order(GRBG) is supported */
>>> +     if (code->index > 0)
>>> +             return -EINVAL;
>>> +
>>> +     code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int ov05c10_enum_frame_size(struct v4l2_subdev *sd,
>>> +                                struct v4l2_subdev_state *sd_state,
>>> +                                struct v4l2_subdev_frame_size_enum *fse)
>>> +{
>>> +     /* ov05c10 driver currently only supports 1 mode*/
>>> +     if (fse->index != 0)
>>> +             return -EINVAL;
>>> +
>>> +     if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
>>> +             return -EINVAL;
>>> +
>>> +     fse->min_width = supported_mode.width;
>>> +     fse->max_width = fse->min_width;
>>> +     fse->min_height = supported_mode.height;
>>> +     fse->max_height = fse->min_height;
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static void ov05c10_update_pad_format(const struct ov05c10_mode *mode,
>>> +                                   struct v4l2_subdev_format *fmt)
>>> +{
>>> +     fmt->format.width = mode->width;
>>> +     fmt->format.height = mode->height;
>>> +     fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
>>> +     fmt->format.field = V4L2_FIELD_NONE;
>>> +}
>>> +
>>> +static int ov05c10_set_pad_format(struct v4l2_subdev *sd,
>>> +                               struct v4l2_subdev_state *sd_state,
>>> +                               struct v4l2_subdev_format *fmt)
>>> +{
>>> +     struct v4l2_mbus_framefmt *framefmt;
>>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>> +     const struct ov05c10_mode *mode;
>>> +     s32 vblank_def;
>>> +     s32 vblank_min;
>>> +     s64 pixel_rate;
>>> +     s64 link_freq;
>>> +     s64 h_blank;
>>> +
>>> +     /* Only one raw bayer(GRBG) order is supported */
>>> +     if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10)
>>> +             fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
>>> +
>>> +     mode = &supported_mode;
>>> +     ov05c10_update_pad_format(mode, fmt);
>>> +     if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
>>> +             framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
>>> +             *framefmt = fmt->format;
>>> +     } else {
>>> +             __v4l2_ctrl_s_ctrl(ov05c10->link_freq, mode->link_freq_index);
>>> +             link_freq = ov05c10_link_freq_menu_items[mode->link_freq_index];
>>> +             pixel_rate = link_freq_to_pixel_rate(link_freq,
>>> +                                                  mode->lanes);
>>> +             __v4l2_ctrl_s_ctrl_int64(ov05c10->pixel_rate, pixel_rate);
>>> +
>>> +             /* Update limits and set FPS to default */
>>> +             vblank_def = mode->vts_def - mode->height;
>>> +             vblank_min = mode->vts_min - mode->height;
>>> +             __v4l2_ctrl_modify_range(ov05c10->vblank, vblank_min,
>>> +                                      OV05C10_VTS_MAX - mode->height,
>>> +                                      1, vblank_def);
>>> +             __v4l2_ctrl_s_ctrl(ov05c10->vblank, vblank_def);
>>> +             h_blank = mode->hts;
>>> +             __v4l2_ctrl_modify_range(ov05c10->hblank, h_blank,
>>> +                                      h_blank, 1, h_blank);
>>> +     }
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int ov05c10_start_streaming(struct ov05c10 *ov05c10)
>>> +{
>>> +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>>> +     const struct ov05c10_mode *mode = &supported_mode;
>>> +     const struct ov05c10_reg_list *reg_list;
>>> +     int ret = 0;
>>> +
>>> +     /* Apply default values of current mode */
>>> +     reg_list = &mode->reg_list;
>>> +     cci_multi_reg_write(ov05c10->regmap, reg_list->regs,
>>> +                         reg_list->num_of_regs, &ret);
>>> +     if (ret) {
>>> +             dev_err(&client->dev, "fail to set mode, ret: %d\n", ret);
>>> +             return ret;
>>> +     }
>>> +
>>> +     /* Apply customized values from user */
>>> +     ret =  __v4l2_ctrl_handler_setup(ov05c10->sd.ctrl_handler);
>>> +     if (ret) {
>>> +             dev_err(&client->dev, "failed to setup v4l2 handler %d\n", ret);
>>> +             return ret;
>>> +     }
>>> +
>>> +     cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_on_regs,
>>> +                         ARRAY_SIZE(mode_OV05C10_stream_on_regs), &ret);
>>> +     if (ret)
>>> +             dev_err(&client->dev, "fail to start the streaming\n");
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static int ov05c10_stop_streaming(struct ov05c10 *ov05c10)
>>> +{
>>> +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>>> +     int ret = 0;
>>> +
>>> +     cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_off_regs,
>>> +                         ARRAY_SIZE(mode_OV05C10_stream_off_regs), &ret);
>>> +     if (ret)
>>> +             dev_err(&client->dev, "fail to stop the streaming\n");
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static void ov05c10_sensor_power_set(struct ov05c10 *ov05c10, bool on)
>>> +{
>>> +     if (on) {
>>> +             gpiod_set_value(ov05c10->enable_gpio, 0);
>>> +             usleep_range(10, 20);
>>> +
>>> +             gpiod_set_value(ov05c10->enable_gpio, 1);
>>> +             usleep_range(1000, 2000);
>>
>> According to the datasheet, ov05c10 needs at least 8 ms to work after
>> its XSHUTDN pin pulled to high. 1 ms maybe too quick, did you tested it?
>> Or the enable_gpio is actually not the XSHUTDN pin?
>>
>> On Intel platforms, if the sensor driver controls the module power,
>> ususally it requires GPIO "reset", regulator "avdd" and clk "img_clk"
>> assigned by kernel driver intel_skl_int3472_discrete. I'm not sure
>> whether any devices on market using this power control solution, but if
>> any, missing those resources will stop them from powering-up cameras.
>>
>>> +     } else {
>>> +             gpiod_set_value(ov05c10->enable_gpio, 0);
>>> +             usleep_range(10, 20);
>>> +     }
>>> +}
>>> +
>>> +static int ov05c10_enable_streams(struct v4l2_subdev *sd,
>>> +                               struct v4l2_subdev_state *state, u32 pad,
>>> +                               u64 streams_mask)
>>> +{
>>> +     struct i2c_client *client = v4l2_get_subdevdata(sd);
>>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>> +     int ret = 0;
>>> +
>>> +     ret = pm_runtime_resume_and_get(&client->dev);
>>> +     if (ret < 0)
>>> +             return ret;
>>> +
>>> +     ov05c10->cur_page = -1;
>>> +
>>> +     ret = ov05c10_start_streaming(ov05c10);
>>> +     if (ret)
>>> +             goto err_rpm_put;
>>> +
>>> +     return 0;
>>> +
>>> +err_rpm_put:
>>> +     pm_runtime_put(&client->dev);
>>> +     return ret;
>>> +}
>>> +
>>> +static int ov05c10_disable_streams(struct v4l2_subdev *sd,
>>> +                                struct v4l2_subdev_state *state, u32 pad,
>>> +                                u64 streams_mask)
>>> +{
>>> +     struct i2c_client *client = v4l2_get_subdevdata(sd);
>>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>> +
>>> +     ov05c10_stop_streaming(ov05c10);
>>> +     pm_runtime_put(&client->dev);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static const struct v4l2_subdev_video_ops ov05c10_video_ops = {
>>> +     .s_stream = v4l2_subdev_s_stream_helper,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_pad_ops ov05c10_pad_ops = {
>>> +     .enum_mbus_code = ov05c10_enum_mbus_code,
>>> +     .get_fmt = v4l2_subdev_get_fmt,
>>> +     .set_fmt = ov05c10_set_pad_format,
>>> +     .enum_frame_size = ov05c10_enum_frame_size,
>>> +     .enable_streams = ov05c10_enable_streams,
>>> +     .disable_streams = ov05c10_disable_streams,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_ops ov05c10_subdev_ops = {
>>> +     .video = &ov05c10_video_ops,
>>> +     .pad = &ov05c10_pad_ops,
>>> +};
>>> +
>>> +static const struct media_entity_operations ov05c10_subdev_entity_ops = {
>>> +     .link_validate = v4l2_subdev_link_validate,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_internal_ops ov05c10_internal_ops = {
>>> +     .init_state = ov05c10_init_state,
>>> +};
>>> +
>>> +static int ov05c10_init_controls(struct ov05c10 *ov05c10)
>>> +{
>>> +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>>> +     const struct ov05c10_mode *mode = &supported_mode;
>>> +     struct v4l2_fwnode_device_properties props;
>>> +     struct v4l2_ctrl_handler *ctrl_hdlr;
>>> +     s64 pixel_rate_max;
>>> +     s64 exposure_max;
>>> +     s64 vblank_def;
>>> +     s64 vblank_min;
>>> +     u32 max_items;
>>> +     s64 hblank;
>>> +     int ret;
>>> +
>>> +     ret = v4l2_ctrl_handler_init(&ov05c10->ctrl_handler, 10);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     ctrl_hdlr = &ov05c10->ctrl_handler;
>>> +
>>> +     max_items = ARRAY_SIZE(ov05c10_link_freq_menu_items) - 1;
>>> +     ov05c10->link_freq =
>>> +             v4l2_ctrl_new_int_menu(ctrl_hdlr,
>>> +                                    NULL,
>>> +                                    V4L2_CID_LINK_FREQ,
>>> +                                    max_items,
>>> +                                    0,
>>> +                                    ov05c10_link_freq_menu_items);
>>> +     if (ov05c10->link_freq)
>>> +             ov05c10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>> +
>>> +     pixel_rate_max =
>>> +             link_freq_to_pixel_rate(ov05c10_link_freq_menu_items[0],
>>> +                                     supported_mode.lanes);
>>> +     ov05c10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
>>> +                                             V4L2_CID_PIXEL_RATE,
>>> +                                             0, pixel_rate_max,
>>> +                                             1, pixel_rate_max);
>>> +
>>> +     vblank_def = mode->vts_def - mode->height;
>>> +     vblank_min = mode->vts_min - mode->height;
>>> +     ov05c10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
>>> +                                         V4L2_CID_VBLANK,
>>> +                                         vblank_min,
>>> +                                         OV05C10_VTS_MAX - mode->height,
>>> +                                         1, vblank_def);
>>> +
>>> +     hblank = (mode->hts > mode->width) ? (mode->hts - mode->width) : 0;
>>
>> Here your hts uses 640 but width is 2888, which means hblank is set to 0
>> here. This is wrong, please fix your configuration.
>>
>>> +     ov05c10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
>>> +                                         V4L2_CID_HBLANK,
>>> +                                         hblank, hblank, 1, hblank);
>>> +     if (ov05c10->hblank)
>>> +             ov05c10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>>> +
>>> +     exposure_max = mode->vts_def - OV05C10_EXPOSURE_MAX_MARGIN;
>>> +     ov05c10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
>>> +                                           V4L2_CID_EXPOSURE,
>>> +                                           OV05C10_EXPOSURE_MIN,
>>> +                                           exposure_max,
>>> +                                           OV05C10_EXPOSURE_STEP,
>>> +                                           exposure_max);
>>> +
>>> +     v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
>>> +                       OV05C10_ANA_GAIN_MIN, OV05C10_ANA_GAIN_MAX,
>>> +                       OV05C10_ANA_GAIN_STEP, OV05C10_ANA_GAIN_DEFAULT);
>>> +
>>> +     v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
>>> +                       OV05C10_DGTL_GAIN_MIN, OV05C10_DGTL_GAIN_MAX,
>>> +                       OV05C10_DGTL_GAIN_STEP, OV05C10_DGTL_GAIN_DEFAULT);
>>> +
>>> +     v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov05c10_ctrl_ops,
>>> +                                  V4L2_CID_TEST_PATTERN,
>>> +                                  ARRAY_SIZE(ov05c10_test_pattern_menu) - 1,
>>> +                                  0, 0, ov05c10_test_pattern_menu);
>>> +
>>> +     if (ctrl_hdlr->error) {
>>> +             ret = ctrl_hdlr->error;
>>> +             dev_err(&client->dev, "V4L2 control init failed (%d)\n", ret);
>>> +             goto err_hdl_free;
>>> +     }
>>> +
>>> +     ret = v4l2_fwnode_device_parse(&client->dev, &props);
>>> +     if (ret)
>>> +             goto err_hdl_free;
>>> +
>>> +     ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov05c10_ctrl_ops,
>>> +                                           &props);
>>> +     if (ret)
>>> +             goto err_hdl_free;
>>> +
>>> +     ov05c10->sd.ctrl_handler = ctrl_hdlr;
>>> +
>>> +     return 0;
>>> +
>>> +err_hdl_free:
>>> +     v4l2_ctrl_handler_free(ctrl_hdlr);
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static int ov05c10_parse_endpoint(struct device *dev,
>>> +                               struct fwnode_handle *fwnode)
>>> +{
>>> +     struct v4l2_fwnode_endpoint bus_cfg = {
>>> +             .bus_type = V4L2_MBUS_CSI2_DPHY
>>> +     };
>>> +     struct fwnode_handle *ep;
>>> +     unsigned long bitmap;
>>> +     int ret;
>>> +
>>> +     ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
>>> +     if (!ep) {
>>> +             dev_err(dev, "Failed to get next endpoint\n");
>>> +             return -ENXIO;
>>> +     }
>>> +
>>> +     ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
>>> +     fwnode_handle_put(ep);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     if (bus_cfg.bus.mipi_csi2.num_data_lanes != supported_mode.lanes) {
>>> +             dev_err(dev,
>>> +                     "number of CSI2 data lanes %d is not supported\n",
>>> +                     bus_cfg.bus.mipi_csi2.num_data_lanes);
>>> +             ret = -EINVAL;
>>> +             goto err_endpoint_free;
>>> +     }
>>> +
>>> +     ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
>>> +                                    bus_cfg.nr_of_link_frequencies,
>>> +                                    ov05c10_link_frequencies,
>>> +                                    ARRAY_SIZE(ov05c10_link_frequencies),
>>> +                                    &bitmap);
>>> +     if (ret)
>>> +             dev_err(dev, "v4l2_link_freq_to_bitmap fail with %d\n", ret);
>>> +err_endpoint_free:
>>> +     v4l2_fwnode_endpoint_free(&bus_cfg);
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static int ov05c10_probe(struct i2c_client *client)
>>> +{
>>> +     struct ov05c10 *ov05c10;
>>> +     u32 clkfreq;
>>> +     int ret;
>>> +
>>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), GFP_KERNEL);
>>> +     if (!ov05c10)
>>> +             return -ENOMEM;
>>> +
>>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
>>> +
>>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency", &clkfreq);
>>
>> Maybe it's better to separate this part fwnode and GPIO code into a
>> standalone function?
>>
>>> +     if (ret)
>>> +             return  dev_err_probe(&client->dev, -EINVAL,
>>> +                                   "fail to get clock freq\n");
>>> +     if (clkfreq != OV05C10_REF_CLK)
>>> +             return dev_err_probe(&client->dev, -EINVAL,
>>> +                                  "fail invalid clock freq %u, %lu expected\n",
>>> +                                  clkfreq, OV05C10_REF_CLK);
>>> +
>>> +     ret = ov05c10_parse_endpoint(&client->dev, fwnode);
>>> +     if (ret)
>>> +             return dev_err_probe(&client->dev, -EINVAL,
>>> +                                  "fail to parse endpoint\n");
>>> +
>>> +     ov05c10->enable_gpio = devm_gpiod_get(&client->dev, "enable",
>>> +                                           GPIOD_OUT_LOW);
>>> +     if (IS_ERR(ov05c10->enable_gpio))
>>> +             return dev_err_probe(&client->dev,
>>> +                                  PTR_ERR(ov05c10->enable_gpio),
>>> +                                  "fail to get enable gpio\n");
>>> +
>>> +     v4l2_i2c_subdev_init(&ov05c10->sd, client, &ov05c10_subdev_ops);
>>> +
>>> +     ov05c10->regmap = devm_cci_regmap_init_i2c(client, 8);
>>> +     if (IS_ERR(ov05c10->regmap))
>>> +             return dev_err_probe(&client->dev, PTR_ERR(ov05c10->regmap),
>>> +                                  "fail to init cci\n");
>>> +
>>> +     ov05c10->cur_page = -1;
>>> +
>>> +     ret = ov05c10_init_controls(ov05c10);
>>> +     if (ret)
>>> +             return dev_err_probe(&client->dev, ret, "fail to init ctl\n");
>>> +
>>> +     ov05c10->sd.internal_ops = &ov05c10_internal_ops;
>>> +     ov05c10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>> +     ov05c10->sd.entity.ops = &ov05c10_subdev_entity_ops;
>>> +     ov05c10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
>>> +
>>> +     ov05c10->pad.flags = MEDIA_PAD_FL_SOURCE;
>>> +
>>> +     ret = media_entity_pads_init(&ov05c10->sd.entity, NUM_OF_PADS,
>>> +                                  &ov05c10->pad);
>>> +     if (ret)
>>> +             goto err_hdl_free;
>>> +
>>> +     ret = v4l2_subdev_init_finalize(&ov05c10->sd);
>>> +     if (ret < 0)
>>> +             goto err_media_entity_cleanup;
>>> +
>>> +     ret = v4l2_async_register_subdev_sensor(&ov05c10->sd);
>>> +     if (ret)
>>> +             goto err_media_entity_cleanup;
>>> +
>>> +     pm_runtime_set_active(&client->dev);
>>> +     pm_runtime_enable(&client->dev);
>>> +     pm_runtime_idle(&client->dev);
>>> +     pm_runtime_set_autosuspend_delay(&client->dev, 1000);
>>> +     pm_runtime_use_autosuspend(&client->dev);
>>> +     return 0;
>>> +
>>> +err_media_entity_cleanup:
>>> +     media_entity_cleanup(&ov05c10->sd.entity);
>>> +
>>> +err_hdl_free:
>>> +     v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static void ov05c10_remove(struct i2c_client *client)
>>> +{
>>> +     struct v4l2_subdev *sd = i2c_get_clientdata(client);
>>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>> +
>>> +     v4l2_async_unregister_subdev(sd);
>>> +     media_entity_cleanup(&sd->entity);
>>> +     v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
>>> +
>>> +     pm_runtime_disable(&client->dev);
>>> +     pm_runtime_set_suspended(&client->dev);
>>> +}
>>> +
>>> +static int ov05c10_runtime_resume(struct device *dev)
>>> +{
>>> +     struct v4l2_subdev *sd = dev_get_drvdata(dev);
>>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>> +
>>> +     ov05c10_sensor_power_set(ov05c10, true);
>>> +     return 0;
>>> +}
>>> +
>>> +static int ov05c10_runtime_suspend(struct device *dev)
>>> +{
>>> +     struct v4l2_subdev *sd = dev_get_drvdata(dev);
>>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>>> +
>>> +     ov05c10_sensor_power_set(ov05c10, false);
>>> +     return 0;
>>> +}
>>> +
>>> +static DEFINE_RUNTIME_DEV_PM_OPS(ov05c10_pm_ops, ov05c10_runtime_suspend,
>>> +                              ov05c10_runtime_resume, NULL);
>>> +
>>> +static const struct i2c_device_id ov05c10_i2c_ids[] = {
>>> +     {"ov05c10", 0 },
>>> +     { }
>>> +};
>>> +MODULE_DEVICE_TABLE(i2c, ov05c10_i2c_ids);
>>> +
>>> +static struct i2c_driver ov05c10_i2c_driver = {
>>> +     .driver = {
>>> +             .name = DRV_NAME,
>>> +             .pm = pm_ptr(&ov05c10_pm_ops),
>>> +     },
>>> +     .id_table = ov05c10_i2c_ids,
>>> +     .probe = ov05c10_probe,
>>> +     .remove = ov05c10_remove,
>>> +};
>>> +
>>> +module_i2c_driver(ov05c10_i2c_driver);
>>> +
>>> +MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
>>> +MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@amd.com>");
>>> +MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
>>> +MODULE_DESCRIPTION("OmniVision OV05C1010 sensor driver");
>>
>> OV05C10
>>
>>> +MODULE_LICENSE("GPL");
>>
>>
>> Hi Sakari,
>>
>> Seems there are already several camera sensors using page-based
>> registers. Is it a good idea to add page support in CCI interface?
> 
> I think that's a good idea!
> 
> Some how maintaining the generality to keep it easy to access registers
> would be nice. Perhaps the page could be embedded in the CCI_REG()
> macros too to support changing the active page when a write accesses a
> different one?
> 
> --
> Kieran
> 
> 
>>
>>
>> Best Regards,
>> Hao Yao
>>


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-13  4:55 ` Hao Yao
  2025-06-13 11:02   ` Kieran Bingham
  2025-06-13 22:02   ` Sakari Ailus
@ 2025-06-17  0:19   ` Nirujogi, Pratap
  2 siblings, 0 replies; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-17  0:19 UTC (permalink / raw)
  To: Hao Yao, Pratap Nirujogi, sakari.ailus
  Cc: mchehab, laurent.pinchart, hverkuil, bryan.odonoghue, krzk,
	dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen, linux-media,
	linux-kernel, benjamin.chan, bin.du, grosikop, king.li, dantony,
	vengutta, dongcheng.yan, jason.z.chen, jimmy.su

Hi Hao Yao,

On 6/13/2025 12:55 AM, Hao Yao wrote:
> Caution: This message originated from an External Source. Use proper 
> caution when opening attachments, clicking links, or responding.
> 
> 
> Hi Pratap,
> 
> Thanks for your patch.
> 
> This patch is written for your camera sensor module, which seems very
> different from those already applied on Dell laptops (some of "Dell Pro"
> series). Looking into the driver, I think this version will break the
> devices using ov05c10 sensor.
> 
> I think this patch is better to be validated on existing devices, but
> please do some fixes before we can do validation. Please check my
> comments inline.
> 
Thanks for reviewing, sharing your insights and offering the help to 
validate on other targets. Yes, it needs to be validated on the specific 
Dell laptops to confirm the functionality. Unfortunately we don't have 
these platforms inhouse to verify and all the test results we had 
provided are coming from HP ZBook.

By default this patch along with x86/platform driver changes submitted, 
it is expected to bind the sensor device only on supported AMD platforms 
and shouldn't be impacting the functionality of other laptops.

But if the same sensor driver has to be used to bring-up OV05C10 on 
other platforms, then yes it may not work out-of-box and may require 
fixes / enhancements on top of it to make it work on HP ZBook and other 
laptops.
> 
> On 2025/6/10 03:42, Pratap Nirujogi wrote:
>> Add driver for OmniVision 5.2M OV05C10 sensor. This driver
>> supports only the full size normal 2888x1808@30fps 2-lane
>> sensor profile.
>>
>> Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>> Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>> Co-developed-by: Bin Du <bin.du@amd.com>
>> Signed-off-by: Bin Du <bin.du@amd.com>
>> Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
>> ---
>> Changes v2 -> v3:
>>
>> * Update "refclk" property variable as "clock-frequency".
>> * Update sensor GPIO connector id name.
>> * Fix sensor v4l2 compliance issue.
>> * Fix license info.
>> * Address review comments.
>>
>>   MAINTAINERS                 |    8 +
>>   drivers/media/i2c/Kconfig   |   10 +
>>   drivers/media/i2c/Makefile  |    1 +
>>   drivers/media/i2c/ov05c10.c | 1061 +++++++++++++++++++++++++++++++++++
>>   4 files changed, 1080 insertions(+)
>>   create mode 100644 drivers/media/i2c/ov05c10.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index a92290fffa16..caca25d00bf2 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -18303,6 +18303,14 @@ T:   git git://linuxtv.org/media.git
>>   F:  Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml
>>   F:  drivers/media/i2c/ov02e10.c
>>
>> +OMNIVISION OV05C10 SENSOR DRIVER
>> +M:   Nirujogi Pratap <pratap.nirujogi@amd.com>
>> +M:   Bin Du <bin.du@amd.com>
>> +L:   linux-media@vger.kernel.org
>> +S:   Maintained
>> +T:   git git://linuxtv.org/media.git
>> +F:   drivers/media/i2c/ov05c10.c
>> +
>>   OMNIVISION OV08D10 SENSOR DRIVER
>>   M:  Jimmy Su <jimmy.su@intel.com>
>>   L:  linux-media@vger.kernel.org
>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>> index e68202954a8f..1662fb29d75c 100644
>> --- a/drivers/media/i2c/Kconfig
>> +++ b/drivers/media/i2c/Kconfig
>> @@ -377,6 +377,16 @@ config VIDEO_OV02C10
>>         To compile this driver as a module, choose M here: the
>>         module will be called ov02c10.
>>
>> +config VIDEO_OV05C10
>> +     tristate "OmniVision OV05C10 sensor support"
>> +     select V4L2_CCI_I2C
>> +     help
>> +       This is a Video4Linux2 sensor driver for the OmniVision
>> +       OV05C10 camera.
>> +
>> +       To compile this driver as a module, choose M here: the
>> +       module will be called OV05C10.
>> +
>>   config VIDEO_OV08D10
>>           tristate "OmniVision OV08D10 sensor support"
>>           help
>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>> index 5873d29433ee..b4a1d721a7f2 100644
>> --- a/drivers/media/i2c/Makefile
>> +++ b/drivers/media/i2c/Makefile
>> @@ -85,6 +85,7 @@ obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
>>   obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
>>   obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o
>>   obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o
>> +obj-$(CONFIG_VIDEO_OV05C10) += ov05c10.o
>>   obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
>>   obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
>>   obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
>> diff --git a/drivers/media/i2c/ov05c10.c b/drivers/media/i2c/ov05c10.c
>> new file mode 100644
>> index 000000000000..9a1e493c4073
>> --- /dev/null
>> +++ b/drivers/media/i2c/ov05c10.c
>> @@ -0,0 +1,1061 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +// Copyright (C) 2025 Advanced Micro Devices, Inc.
>> +
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/gpio.h>
>> +#include <linux/i2c.h>
>> +#include <linux/module.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/units.h>
>> +#include <media/v4l2-cci.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-fwnode.h>
>> +
>> +#define DRV_NAME                     "ov05c10"
>> +#define OV05C10_REF_CLK                      (24 * HZ_PER_MHZ)
> 
> Seems your module use 24 MHz clock input. The Dell's modules always use
> 19.2MHz, which means your the PLL settings will not work on Dell's.
> 
yes, this PLL configuration is specific to HP Zbook. To support other 
configurations with Dell laptops new PLL configuration is required.

>> +
>> +#define MODE_WIDTH  2888
>> +#define MODE_HEIGHT 1808
>> +
>> +#define PAGE_NUM_MASK                        0xff000000
>> +#define PAGE_NUM_SHIFT                       24
>> +#define REG_ADDR_MASK                        0x00ffffff
>> +
>> +#define OV05C10_SYSCTL_PAGE          (0 << PAGE_NUM_SHIFT)
>> +#define OV05C10_CISCTL_PAGE          (1 << PAGE_NUM_SHIFT)
>> +#define OV05C10_ISPCTL_PAGE          (4 << PAGE_NUM_SHIFT)
>> +
>> +/* Chip ID */
>> +#define OV05C10_REG_CHIP_ID          (CCI_REG24(0x00) | 
>> OV05C10_SYSCTL_PAGE)
>> +#define OV05C10_CHIP_ID                      0x43055610
>> +
>> +/* Control registers */
>> +#define OV05C10_REG_TRIGGER          (CCI_REG8(0x01) | 
>> OV05C10_CISCTL_PAGE)
>> +#define OV05C_REG_TRIGGER_START              BIT(0)
>> +
>> +/* Exposure control */
>> +#define OV05C10_REG_EXPOSURE         (CCI_REG24(0x02) | 
>> OV05C10_CISCTL_PAGE)
>> +#define OV05C10_EXPOSURE_MAX_MARGIN  33
>> +#define OV05C10_EXPOSURE_MIN         4
>> +#define OV05C10_EXPOSURE_STEP                1
>> +#define OV05C10_EXPOSURE_DEFAULT     0x40
>> +
>> +/* V_TIMING internal */
>> +#define OV05C10_REG_VTS                      (CCI_REG16(0x05) | 
>> OV05C10_CISCTL_PAGE)
>> +#define OV05C10_VTS_30FPS            1860
>> +#define OV05C10_VTS_MAX                      0x7fff
>> +
>> +/* Test Pattern Control */
>> +#define OV05C10_REG_TEST_PATTERN     (CCI_REG8(0x12) | 
>> OV05C10_ISPCTL_PAGE)
>> +#define OV05C10_TEST_PATTERN_ENABLE  BIT(0)
>> +#define OV05C10_REG_TEST_PATTERN_CTL (CCI_REG8(0xf3) | 
>> OV05C10_ISPCTL_PAGE)
>> +#define OV05C10_REG_TEST_PATTERN_XXX BIT(0)
>> +
>> +/* Digital gain control */
>> +#define OV05C10_REG_DGTL_GAIN_H              (CCI_REG8(0x21) | 
>> OV05C10_CISCTL_PAGE)
>> +#define OV05C10_REG_DGTL_GAIN_L              (CCI_REG8(0x22) | 
>> OV05C10_CISCTL_PAGE)
>> +
>> +#define OV05C10_DGTL_GAIN_MIN                0x40
>> +#define OV05C10_DGTL_GAIN_MAX                0xff
>> +#define OV05C10_DGTL_GAIN_DEFAULT    0x40
>> +#define OV05C10_DGTL_GAIN_STEP               1
>> +
>> +#define OV05C10_DGTL_GAIN_L_MASK     0xff
>> +#define OV05C10_DGTL_GAIN_H_SHIFT    8
>> +#define OV05C10_DGTL_GAIN_H_MASK     0xff00
>> +
>> +/* Analog gain control */
>> +#define OV05C10_REG_ANALOG_GAIN              (CCI_REG8(0x24) | 
>> OV05C10_CISCTL_PAGE)
>> +#define OV05C10_ANA_GAIN_MIN         0x80
>> +#define OV05C10_ANA_GAIN_MAX         0x07c0
>> +#define OV05C10_ANA_GAIN_STEP                1
>> +#define OV05C10_ANA_GAIN_DEFAULT     0x80
>> +
>> +/* H TIMING internal */
>> +#define OV05C10_REG_HTS                      (CCI_REG16(0x37) | 
>> OV05C10_CISCTL_PAGE)
>> +#define OV05C10_HTS_30FPS            0x0280
>> +
>> +/* Page selection */
>> +#define OV05C10_REG_PAGE_CTL         CCI_REG8(0xfd)
>> +
>> +#define NUM_OF_PADS 1
>> +
>> +#define OV05C10_GET_PAGE_NUM(reg)    (((reg) & PAGE_NUM_MASK) >>\
>> +                                      PAGE_NUM_SHIFT)
>> +#define OV05C10_GET_REG_ADDR(reg)    ((reg) & REG_ADDR_MASK)
>> +
>> +enum {
>> +     OV05C10_LINK_FREQ_900MHZ_INDEX,
>> +};
>> +
>> +struct ov05c10_reg_list {
>> +     u32 num_of_regs;
>> +     const struct cci_reg_sequence *regs;
>> +};
>> +
>> +/* Mode : resolution and related config&values */
>> +struct ov05c10_mode {
>> +     /* Frame width */
>> +     u32 width;
>> +     /* Frame height */
>> +     u32 height;
>> +     /* number of lanes */
>> +     u32 lanes;
>> +
>> +     /* V-timing */
>> +     u32 vts_def;
>> +     u32 vts_min;
>> +
>> +     /* HTS */
>> +     u32 hts;
>> +
>> +     /* Index of Link frequency config to be used */
>> +     u32 link_freq_index;
>> +
>> +     /* Default register values */
>> +     struct ov05c10_reg_list reg_list;
>> +};
>> +
>> +static const s64 ov05c10_link_frequencies[] = {
>> +     925 * HZ_PER_MHZ,
>> +};
> 
> Is it 900 MHz, or 925 MHz?
> 
yes, thanks, it should be 900MHz.

Driver is actually using the link frequency from 
ov05c10_link_freq_menu_items[].

Will fix the confusion between ov05c10_link_frequencies[] and 
ov05c10_link_freq_menu_items[] in the next patch

>> +
>> +/* 2888x1808 30fps, 1800mbps, 2lane, 24mhz */
> 
> Currently Dell's devices with ov05c10 use a CV chip to passthrough MIPI
> CSI signals, but it supports max 750 MHz link frequency. That's why this
> version:
> https://github.com/intel/ipu6-drivers/blob/master/drivers/media/i2c/ 
> ov05c10.c
> uses 480 MHz link frequency and a different resolution setting
> (2800x1576). At least the setting in out-of-tree Github driver should be
> merged into this version.
> 
Looks like this requirement is specific to Dell. We haven't come acorss 
this restriction on HP ZBook. If it is required to support different 
link frequencies, I think submitting incremental patches would be easier 
to extend the support for both products. We can support as required to 
ensure no regressions for the current functionality.

>> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
>> +     { CCI_REG8(0xfd),  0x00 },
>> +     { CCI_REG8(0x20),  0x00 },
>> +     { CCI_REG8(0xfd),  0x00 },
>> +     { CCI_REG8(0x20),  0x0b },
>> +     { CCI_REG8(0xc1),  0x09 },
>> +     { CCI_REG8(0x21),  0x06 },
>> +     { CCI_REG8(0x14),  0x78 },
>> +     { CCI_REG8(0xe7),  0x03 },
>> +     { CCI_REG8(0xe7),  0x00 },
>> +     { CCI_REG8(0x21),  0x00 },
>> +     { CCI_REG8(0xfd),  0x01 },
>> +     { CCI_REG8(0x03),  0x00 },
>> +     { CCI_REG8(0x04),  0x06 },
>> +     { CCI_REG8(0x05),  0x07 },
>> +     { CCI_REG8(0x06),  0x44 },
>> +     { CCI_REG8(0x07),  0x08 },
>> +     { CCI_REG8(0x1b),  0x01 },
>> +     { CCI_REG8(0x24),  0xff },
>> +     { CCI_REG8(0x32),  0x03 },
>> +     { CCI_REG8(0x42),  0x5d },
>> +     { CCI_REG8(0x43),  0x08 },
>> +     { CCI_REG8(0x44),  0x81 },
>> +     { CCI_REG8(0x46),  0x5f },
>> +     { CCI_REG8(0x48),  0x18 },
>> +     { CCI_REG8(0x49),  0x04 },
>> +     { CCI_REG8(0x5c),  0x18 },
>> +     { CCI_REG8(0x5e),  0x13 },
>> +     { CCI_REG8(0x70),  0x15 },
>> +     { CCI_REG8(0x77),  0x35 },
>> +     { CCI_REG8(0x79),  0x00 },
>> +     { CCI_REG8(0x7b),  0x08 },
>> +     { CCI_REG8(0x7d),  0x08 },
>> +     { CCI_REG8(0x7e),  0x08 },
>> +     { CCI_REG8(0x7f),  0x08 },
>> +     { CCI_REG8(0x90),  0x37 },
>> +     { CCI_REG8(0x91),  0x05 },
>> +     { CCI_REG8(0x92),  0x18 },
>> +     { CCI_REG8(0x93),  0x27 },
>> +     { CCI_REG8(0x94),  0x05 },
>> +     { CCI_REG8(0x95),  0x38 },
>> +     { CCI_REG8(0x9b),  0x00 },
>> +     { CCI_REG8(0x9c),  0x06 },
>> +     { CCI_REG8(0x9d),  0x28 },
>> +     { CCI_REG8(0x9e),  0x06 },
>> +     { CCI_REG8(0xb2),  0x0f },
>> +     { CCI_REG8(0xb3),  0x29 },
>> +     { CCI_REG8(0xbf),  0x3c },
>> +     { CCI_REG8(0xc2),  0x04 },
>> +     { CCI_REG8(0xc4),  0x00 },
>> +     { CCI_REG8(0xca),  0x20 },
>> +     { CCI_REG8(0xcb),  0x20 },
>> +     { CCI_REG8(0xcc),  0x28 },
>> +     { CCI_REG8(0xcd),  0x28 },
>> +     { CCI_REG8(0xce),  0x20 },
>> +     { CCI_REG8(0xcf),  0x20 },
>> +     { CCI_REG8(0xd0),  0x2a },
>> +     { CCI_REG8(0xd1),  0x2a },
>> +     { CCI_REG8(0xfd),  0x0f },
>> +     { CCI_REG8(0x00),  0x00 },
>> +     { CCI_REG8(0x01),  0xa0 },
>> +     { CCI_REG8(0x02),  0x48 },
>> +     { CCI_REG8(0x07),  0x8f },
>> +     { CCI_REG8(0x08),  0x70 },
>> +     { CCI_REG8(0x09),  0x01 },
>> +     { CCI_REG8(0x0b),  0x40 },
>> +     { CCI_REG8(0x0d),  0x07 },
>> +     { CCI_REG8(0x11),  0x33 },
>> +     { CCI_REG8(0x12),  0x77 },
>> +     { CCI_REG8(0x13),  0x66 },
>> +     { CCI_REG8(0x14),  0x65 },
>> +     { CCI_REG8(0x15),  0x37 },
>> +     { CCI_REG8(0x16),  0xbf },
>> +     { CCI_REG8(0x17),  0xff },
>> +     { CCI_REG8(0x18),  0xff },
>> +     { CCI_REG8(0x19),  0x12 },
>> +     { CCI_REG8(0x1a),  0x10 },
>> +     { CCI_REG8(0x1c),  0x77 },
>> +     { CCI_REG8(0x1d),  0x77 },
>> +     { CCI_REG8(0x20),  0x0f },
>> +     { CCI_REG8(0x21),  0x0f },
>> +     { CCI_REG8(0x22),  0x0f },
>> +     { CCI_REG8(0x23),  0x0f },
>> +     { CCI_REG8(0x2b),  0x20 },
>> +     { CCI_REG8(0x2c),  0x20 },
>> +     { CCI_REG8(0x2d),  0x04 },
>> +     { CCI_REG8(0xfd),  0x03 },
>> +     { CCI_REG8(0x9d),  0x0f },
>> +     { CCI_REG8(0x9f),  0x40 },
>> +     { CCI_REG8(0xfd),  0x00 },
>> +     { CCI_REG8(0x20),  0x1b },
>> +     { CCI_REG8(0xfd),  0x04 },
>> +     { CCI_REG8(0x19),  0x60 },
>> +     { CCI_REG8(0xfd),  0x02 },
>> +     { CCI_REG8(0x75),  0x05 },
>> +     { CCI_REG8(0x7f),  0x06 },
>> +     { CCI_REG8(0x9a),  0x03 },
>> +     { CCI_REG8(0xa2),  0x07 },
>> +     { CCI_REG8(0xa3),  0x10 },
>> +     { CCI_REG8(0xa5),  0x02 },
>> +     { CCI_REG8(0xa6),  0x0b },
>> +     { CCI_REG8(0xa7),  0x48 },
>> +     { CCI_REG8(0xfd),  0x07 },
>> +     { CCI_REG8(0x42),  0x00 },
>> +     { CCI_REG8(0x43),  0x80 },
>> +     { CCI_REG8(0x44),  0x00 },
>> +     { CCI_REG8(0x45),  0x80 },
>> +     { CCI_REG8(0x46),  0x00 },
>> +     { CCI_REG8(0x47),  0x80 },
>> +     { CCI_REG8(0x48),  0x00 },
>> +     { CCI_REG8(0x49),  0x80 },
>> +     { CCI_REG8(0x00),  0xf7 },
>> +     { CCI_REG8(0xfd),  0x00 },
>> +     { CCI_REG8(0xe7),  0x03 },
>> +     { CCI_REG8(0xe7),  0x00 },
>> +     { CCI_REG8(0xfd),  0x00 },
>> +     { CCI_REG8(0x93),  0x18 },
>> +     { CCI_REG8(0x94),  0xff },
>> +     { CCI_REG8(0x95),  0xbd },
>> +     { CCI_REG8(0x96),  0x1a },
>> +     { CCI_REG8(0x98),  0x04 },
>> +     { CCI_REG8(0x99),  0x08 },
>> +     { CCI_REG8(0x9b),  0x10 },
>> +     { CCI_REG8(0x9c),  0x3f },
>> +     { CCI_REG8(0xa1),  0x05 },
>> +     { CCI_REG8(0xa4),  0x2f },
>> +     { CCI_REG8(0xc0),  0x0c },
>> +     { CCI_REG8(0xc1),  0x08 },
>> +     { CCI_REG8(0xc2),  0x00 },
>> +     { CCI_REG8(0xb6),  0x20 },
>> +     { CCI_REG8(0xbb),  0x80 },
>> +     { CCI_REG8(0xfd),  0x00 },
>> +     { CCI_REG8(0xa0),  0x01 },
>> +     { CCI_REG8(0xfd),  0x01 },
>> +};
>> +
>> +static const struct cci_reg_sequence mode_OV05C10_stream_on_regs[] = {
>> +     { CCI_REG8(0xfd), 0x01 },
>> +     { CCI_REG8(0x33), 0x03 },
>> +     { CCI_REG8(0x01), 0x02 },
>> +     { CCI_REG8(0xfd), 0x00 },
>> +     { CCI_REG8(0x20), 0x1f },
>> +     { CCI_REG8(0xfd), 0x01 },
>> +};
>> +
>> +static const struct cci_reg_sequence mode_OV05C10_stream_off_regs[] = {
>> +     { CCI_REG8(0xfd), 0x00 },
>> +     { CCI_REG8(0x20), 0x5b },
>> +     { CCI_REG8(0xfd), 0x01 },
>> +     { CCI_REG8(0x33), 0x02 },
>> +     { CCI_REG8(0x01), 0x02 },
>> +};
>> +
>> +static const char * const ov05c10_test_pattern_menu[] = {
>> +     "Disabled",
>> +     "Vertical Color Bar Type 1",
>> +     "Vertical Color Bar Type 2",
>> +     "Vertical Color Bar Type 3",
>> +     "Vertical Color Bar Type 4"
>> +};
>> +
>> +/* Configurations for supported link frequencies */
>> +#define OV05C10_LINK_FREQ_900MHZ     (900 * HZ_PER_MHZ)
>> +
>> +/* Number of lanes supported */
>> +#define OV05C10_DATA_LANES           2
>> +
>> +/* Bits per sample of sensor output */
>> +#define OV05C10_BITS_PER_SAMPLE              10
>> +
>> +/*
>> + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
>> + * data rate => double data rate; number of lanes => 2; bits per 
>> pixel => 10
>> + */
>> +static u64 link_freq_to_pixel_rate(u64 f, u32 lane_nr)
>> +{
>> +     f *= 2 * lane_nr;
>> +     do_div(f, OV05C10_BITS_PER_SAMPLE);
>> +
>> +     return f;
>> +}
>> +
>> +/* Menu items for LINK_FREQ V4L2 control */
>> +static const s64 ov05c10_link_freq_menu_items[] = {
>> +     OV05C10_LINK_FREQ_900MHZ,
>> +};
>> +
>> +/* Mode configs, currently, only support 1 mode */
>> +static const struct ov05c10_mode supported_mode = {
>> +     .width = MODE_WIDTH,
>> +     .height = MODE_HEIGHT,
>> +     .vts_def = OV05C10_VTS_30FPS,
>> +     .vts_min = OV05C10_VTS_30FPS,
>> +     .hts = 640,
>> +     .lanes = 2,
>> +     .reg_list = {
>> +             .num_of_regs = ARRAY_SIZE(ov05c10_2888x1808_regs),
>> +             .regs = ov05c10_2888x1808_regs,
>> +     },
>> +     .link_freq_index = OV05C10_LINK_FREQ_900MHZ_INDEX,
>> +};
>> +
>> +struct ov05c10 {
>> +     struct v4l2_subdev sd;
>> +     struct media_pad pad;
>> +
>> +     /* V4L2 control handler */
>> +     struct v4l2_ctrl_handler ctrl_handler;
>> +
>> +     /* V4L2 Controls */
>> +     struct v4l2_ctrl *link_freq;
>> +     struct v4l2_ctrl *pixel_rate;
>> +     struct v4l2_ctrl *vblank;
>> +     struct v4l2_ctrl *hblank;
>> +     struct v4l2_ctrl *exposure;
>> +
>> +     struct regmap *regmap;
>> +
>> +     /* gpio descriptor */
>> +     struct gpio_desc *enable_gpio;
>> +
>> +     /* Current page for sensor register control */
>> +     int cur_page;
>> +};
>> +
>> +#define to_ov05c10(_sd)      container_of(_sd, struct ov05c10, sd)
>> +
>> +static int ov05c10_init_state(struct v4l2_subdev *sd,
>> +                           struct v4l2_subdev_state *sd_state)
>> +{
>> +     struct v4l2_mbus_framefmt *frame_fmt;
>> +     struct v4l2_subdev_format fmt = {
>> +             .which = V4L2_SUBDEV_FORMAT_TRY,
>> +             .format = {
>> +                     .width = MODE_WIDTH,
>> +                     .height = MODE_HEIGHT,
>> +                     .code = MEDIA_BUS_FMT_SGRBG10_1X10,
>> +                     .field = V4L2_FIELD_NONE,
>> +             }
>> +     };
>> +
>> +     frame_fmt = v4l2_subdev_state_get_format(sd_state, 0);
>> +     *frame_fmt = fmt.format;
>> +     return 0;
>> +}
>> +
>> +static int ov05c10_switch_page(struct ov05c10 *ov05c10, u32 page, int 
>> *err)
> 
> Seems nobody cares the return value of ov05c10_switch_page() or
> ov05c10_reg_write(), etc.. It should be better to use void return, or
> use return value instead of int *err.
> 
Thanks, we will update this as Laurent suggested in the next patch.

>> +{
>> +     int ret = 0;
>> +
>> +     if (err && *err)
>> +             return *err;
>> +
>> +     if (page != ov05c10->cur_page) {
>> +             cci_write(ov05c10->regmap, OV05C10_REG_PAGE_CTL, page, 
>> &ret);
>> +             if (!ret)
>> +                     ov05c10->cur_page = page;
>> +     }
>> +
>> +     if (err)
>> +             *err = ret;
>> +
>> +     return ret;
>> +}
>> +
>> +/* refer to the implementation of cci_read */
>> +static int ov05c10_reg_read(struct ov05c10 *ov05c10, u32 reg,
>> +                         u64 *val, int *err)
>> +{
>> +     u32 page;
>> +     u32 addr;
>> +     int ret = 0;
>> +
>> +     if (err && *err)
>> +             return *err;
>> +
>> +     page = OV05C10_GET_PAGE_NUM(reg);
>> +     addr = OV05C10_GET_REG_ADDR(reg);
>> +     ov05c10_switch_page(ov05c10, page, &ret);
>> +     cci_read(ov05c10->regmap, addr, val, &ret);
>> +     if (err)
>> +             *err = ret;
>> +
>> +     return ret;
>> +}
>> +
>> +/* refer to the implementation of cci_write */
>> +static int ov05c10_reg_write(struct ov05c10 *ov05c10, u32 reg,
>> +                          u64 val, int *err)
>> +{
>> +     u32 page;
>> +     u32 addr;
>> +     int ret = 0;
>> +
>> +     if (err && *err)
>> +             return *err;
>> +
>> +     page = OV05C10_GET_PAGE_NUM(reg);
>> +     addr = OV05C10_GET_REG_ADDR(reg);
>> +     ov05c10_switch_page(ov05c10, page, &ret);
>> +     cci_write(ov05c10->regmap, addr, val, &ret);
>> +     if (err)
>> +             *err = ret;
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_update_vblank(struct ov05c10 *ov05c10, u32 vblank)
>> +{
>> +     const struct ov05c10_mode *mode = &supported_mode;
>> +     u64 val;
>> +     int ret = 0;
>> +
>> +     val = mode->height + vblank;
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_VTS, val, &ret);
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>> +                       OV05C_REG_TRIGGER_START, &ret);
>> +
>> +     return ret;
>> +}
> 
> I remembered that the ov05c10 VTS control (P1:0x05~0x06) is a bit weird.
> This register seems take the increment of VTS value, so direct write of
> VTS value will not set it properly. Does this version make AE working on
> your platform?
> 
I’ll need to check on this, will get back to you.

>> +
>> +static int ov05c10_update_exposure(struct ov05c10 *ov05c10, u32 
>> exposure)
>> +{
>> +     int ret = 0;
>> +
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_EXPOSURE, exposure, &ret);
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>> +                       OV05C_REG_TRIGGER_START, &ret);
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_update_analog_gain(struct ov05c10 *ov05c10, u32 
>> a_gain)
>> +{
>> +     int ret = 0;
>> +
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_ANALOG_GAIN, a_gain, &ret);
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>> +                       OV05C_REG_TRIGGER_START, &ret);
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_update_digital_gain(struct ov05c10 *ov05c10, u32 
>> d_gain)
>> +{
>> +     u64 val;
>> +     int ret = 0;
>> +
>> +     val = d_gain & OV05C10_DGTL_GAIN_L_MASK;
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_L, val, &ret);
>> +
>> +     val = (d_gain & OV05C10_DGTL_GAIN_H_MASK) >> 
>> OV05C10_DGTL_GAIN_H_SHIFT;
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_DGTL_GAIN_H, val, &ret);
>> +
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>> +                       OV05C_REG_TRIGGER_START, &ret);
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_enable_test_pattern(struct ov05c10 *ov05c10, u32 
>> pattern)
>> +{
>> +     u64 val;
>> +     int ret = 0;
>> +
>> +     if (pattern) {
>> +             ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
>> +                              &val, &ret);
>> +             ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN_CTL,
>> +                               val | OV05C10_REG_TEST_PATTERN_XXX, 
>> &ret);
>> +             ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, 
>> &val, &ret);
>> +             val |= OV05C10_TEST_PATTERN_ENABLE;
>> +     } else {
>> +             ov05c10_reg_read(ov05c10, OV05C10_REG_TEST_PATTERN, 
>> &val, &ret);
>> +             val &= ~OV05C10_TEST_PATTERN_ENABLE;
>> +     }
>> +
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TEST_PATTERN, val, &ret);
>> +     ov05c10_reg_write(ov05c10, OV05C10_REG_TRIGGER,
>> +                       OV05C_REG_TRIGGER_START, &ret);
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_set_ctrl(struct v4l2_ctrl *ctrl)
>> +{
>> +     struct ov05c10 *ov05c10 = container_of(ctrl->handler,
>> +                                            struct ov05c10, 
>> ctrl_handler);
>> +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>> +     const struct ov05c10_mode *mode = &supported_mode;
>> +     s64 max;
>> +     int ret = 0;
>> +
>> +     /* Propagate change of current control to all related controls */
>> +     if (ctrl->id == V4L2_CID_VBLANK) {
>> +             s64 cur_exp = ov05c10->exposure->cur.val;
>> +
>> +             /* Update max exposure while meeting expected vblanking */
>> +             max = mode->height + ctrl->val - 
>> OV05C10_EXPOSURE_MAX_MARGIN;
>> +             cur_exp = clamp(cur_exp, ov05c10->exposure->minimum, max);
>> +             ret = __v4l2_ctrl_modify_range(ov05c10->exposure,
>> +                                            ov05c10->exposure->minimum,
>> +                                            max, ov05c10->exposure- 
>> >step,
>> +                                            cur_exp);
>> +             if (!ret)
>> +                     return ret;
>> +     }
>> +
>> +     /*
>> +      * Applying V4L2 control value only happens
>> +      * when power is up for streaming
>> +      */
>> +     if (!pm_runtime_get_if_in_use(&client->dev))
>> +             return 0;
>> +
>> +     switch (ctrl->id) {
>> +     case V4L2_CID_ANALOGUE_GAIN:
>> +             ret = ov05c10_update_analog_gain(ov05c10, ctrl->val);
>> +             break;
>> +     case V4L2_CID_DIGITAL_GAIN:
>> +             ret = ov05c10_update_digital_gain(ov05c10, ctrl->val);
>> +             break;
>> +     case V4L2_CID_EXPOSURE:
>> +             ret = ov05c10_update_exposure(ov05c10, ctrl->val);
>> +             break;
>> +     case V4L2_CID_VBLANK:
>> +             ret = ov05c10_update_vblank(ov05c10, ctrl->val);
>> +             break;
>> +     case V4L2_CID_TEST_PATTERN:
>> +             ret = ov05c10_enable_test_pattern(ov05c10, ctrl->val);
>> +             break;
>> +     default:
>> +             ret = -ENOTTY;
>> +             dev_err(&client->dev,
>> +                     "ctrl(id:0x%x,val:0x%x) is not handled\n",
>> +                     ctrl->id, ctrl->val);
>> +             break;
>> +     }
>> +
>> +     pm_runtime_put(&client->dev);
>> +
>> +     return ret;
>> +}
>> +
>> +static const struct v4l2_ctrl_ops ov05c10_ctrl_ops = {
>> +     .s_ctrl = ov05c10_set_ctrl,
>> +};
>> +
>> +static int ov05c10_enum_mbus_code(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *sd_state,
>> +                               struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +     /* Only one bayer order(GRBG) is supported */
>> +     if (code->index > 0)
>> +             return -EINVAL;
>> +
>> +     code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
>> +
>> +     return 0;
>> +}
>> +
>> +static int ov05c10_enum_frame_size(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *sd_state,
>> +                                struct v4l2_subdev_frame_size_enum *fse)
>> +{
>> +     /* ov05c10 driver currently only supports 1 mode*/
>> +     if (fse->index != 0)
>> +             return -EINVAL;
>> +
>> +     if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
>> +             return -EINVAL;
>> +
>> +     fse->min_width = supported_mode.width;
>> +     fse->max_width = fse->min_width;
>> +     fse->min_height = supported_mode.height;
>> +     fse->max_height = fse->min_height;
>> +
>> +     return 0;
>> +}
>> +
>> +static void ov05c10_update_pad_format(const struct ov05c10_mode *mode,
>> +                                   struct v4l2_subdev_format *fmt)
>> +{
>> +     fmt->format.width = mode->width;
>> +     fmt->format.height = mode->height;
>> +     fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
>> +     fmt->format.field = V4L2_FIELD_NONE;
>> +}
>> +
>> +static int ov05c10_set_pad_format(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *sd_state,
>> +                               struct v4l2_subdev_format *fmt)
>> +{
>> +     struct v4l2_mbus_framefmt *framefmt;
>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>> +     const struct ov05c10_mode *mode;
>> +     s32 vblank_def;
>> +     s32 vblank_min;
>> +     s64 pixel_rate;
>> +     s64 link_freq;
>> +     s64 h_blank;
>> +
>> +     /* Only one raw bayer(GRBG) order is supported */
>> +     if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10)
>> +             fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
>> +
>> +     mode = &supported_mode;
>> +     ov05c10_update_pad_format(mode, fmt);
>> +     if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
>> +             framefmt = v4l2_subdev_state_get_format(sd_state, fmt- 
>> >pad);
>> +             *framefmt = fmt->format;
>> +     } else {
>> +             __v4l2_ctrl_s_ctrl(ov05c10->link_freq, mode- 
>> >link_freq_index);
>> +             link_freq = ov05c10_link_freq_menu_items[mode- 
>> >link_freq_index];
>> +             pixel_rate = link_freq_to_pixel_rate(link_freq,
>> +                                                  mode->lanes);
>> +             __v4l2_ctrl_s_ctrl_int64(ov05c10->pixel_rate, pixel_rate);
>> +
>> +             /* Update limits and set FPS to default */
>> +             vblank_def = mode->vts_def - mode->height;
>> +             vblank_min = mode->vts_min - mode->height;
>> +             __v4l2_ctrl_modify_range(ov05c10->vblank, vblank_min,
>> +                                      OV05C10_VTS_MAX - mode->height,
>> +                                      1, vblank_def);
>> +             __v4l2_ctrl_s_ctrl(ov05c10->vblank, vblank_def);
>> +             h_blank = mode->hts;
>> +             __v4l2_ctrl_modify_range(ov05c10->hblank, h_blank,
>> +                                      h_blank, 1, h_blank);
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int ov05c10_start_streaming(struct ov05c10 *ov05c10)
>> +{
>> +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>> +     const struct ov05c10_mode *mode = &supported_mode;
>> +     const struct ov05c10_reg_list *reg_list;
>> +     int ret = 0;
>> +
>> +     /* Apply default values of current mode */
>> +     reg_list = &mode->reg_list;
>> +     cci_multi_reg_write(ov05c10->regmap, reg_list->regs,
>> +                         reg_list->num_of_regs, &ret);
>> +     if (ret) {
>> +             dev_err(&client->dev, "fail to set mode, ret: %d\n", ret);
>> +             return ret;
>> +     }
>> +
>> +     /* Apply customized values from user */
>> +     ret =  __v4l2_ctrl_handler_setup(ov05c10->sd.ctrl_handler);
>> +     if (ret) {
>> +             dev_err(&client->dev, "failed to setup v4l2 handler 
>> %d\n", ret);
>> +             return ret;
>> +     }
>> +
>> +     cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_on_regs,
>> +                         ARRAY_SIZE(mode_OV05C10_stream_on_regs), &ret);
>> +     if (ret)
>> +             dev_err(&client->dev, "fail to start the streaming\n");
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_stop_streaming(struct ov05c10 *ov05c10)
>> +{
>> +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>> +     int ret = 0;
>> +
>> +     cci_multi_reg_write(ov05c10->regmap, mode_OV05C10_stream_off_regs,
>> +                         ARRAY_SIZE(mode_OV05C10_stream_off_regs), 
>> &ret);
>> +     if (ret)
>> +             dev_err(&client->dev, "fail to stop the streaming\n");
>> +
>> +     return ret;
>> +}
>> +
>> +static void ov05c10_sensor_power_set(struct ov05c10 *ov05c10, bool on)
>> +{
>> +     if (on) {
>> +             gpiod_set_value(ov05c10->enable_gpio, 0);
>> +             usleep_range(10, 20);
>> +
>> +             gpiod_set_value(ov05c10->enable_gpio, 1);
>> +             usleep_range(1000, 2000);
> 
> According to the datasheet, ov05c10 needs at least 8 ms to work after
> its XSHUTDN pin pulled to high. 1 ms maybe too quick, did you tested it?
> Or the enable_gpio is actually not the XSHUTDN pin?
> 
yes, 8ms is the delay from XSHUTDN pull up to SCCB start, here 1ms is 
the delay between XSHUTDN pull down and up, these are two different 
things and we confirm this configuration works on HP Zbook.

> On Intel platforms, if the sensor driver controls the module power,
> ususally it requires GPIO "reset", regulator "avdd" and clk "img_clk"
> assigned by kernel driver intel_skl_int3472_discrete. I'm not sure
> whether any devices on market using this power control solution, but if
> any, missing those resources will stop them from powering-up cameras.
> 
I dont think this is relevant for our platform, we can recheck to see if 
it is needed at your end.

>> +     } else {
>> +             gpiod_set_value(ov05c10->enable_gpio, 0);
>> +             usleep_range(10, 20);
>> +     }
>> +}
>> +
>> +static int ov05c10_enable_streams(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *state, u32 pad,
>> +                               u64 streams_mask)
>> +{
>> +     struct i2c_client *client = v4l2_get_subdevdata(sd);
>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>> +     int ret = 0;
>> +
>> +     ret = pm_runtime_resume_and_get(&client->dev);
>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     ov05c10->cur_page = -1;
>> +
>> +     ret = ov05c10_start_streaming(ov05c10);
>> +     if (ret)
>> +             goto err_rpm_put;
>> +
>> +     return 0;
>> +
>> +err_rpm_put:
>> +     pm_runtime_put(&client->dev);
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_disable_streams(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *state, u32 
>> pad,
>> +                                u64 streams_mask)
>> +{
>> +     struct i2c_client *client = v4l2_get_subdevdata(sd);
>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>> +
>> +     ov05c10_stop_streaming(ov05c10);
>> +     pm_runtime_put(&client->dev);
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_video_ops ov05c10_video_ops = {
>> +     .s_stream = v4l2_subdev_s_stream_helper,
>> +};
>> +
>> +static const struct v4l2_subdev_pad_ops ov05c10_pad_ops = {
>> +     .enum_mbus_code = ov05c10_enum_mbus_code,
>> +     .get_fmt = v4l2_subdev_get_fmt,
>> +     .set_fmt = ov05c10_set_pad_format,
>> +     .enum_frame_size = ov05c10_enum_frame_size,
>> +     .enable_streams = ov05c10_enable_streams,
>> +     .disable_streams = ov05c10_disable_streams,
>> +};
>> +
>> +static const struct v4l2_subdev_ops ov05c10_subdev_ops = {
>> +     .video = &ov05c10_video_ops,
>> +     .pad = &ov05c10_pad_ops,
>> +};
>> +
>> +static const struct media_entity_operations ov05c10_subdev_entity_ops 
>> = {
>> +     .link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops ov05c10_internal_ops = {
>> +     .init_state = ov05c10_init_state,
>> +};
>> +
>> +static int ov05c10_init_controls(struct ov05c10 *ov05c10)
>> +{
>> +     struct i2c_client *client = v4l2_get_subdevdata(&ov05c10->sd);
>> +     const struct ov05c10_mode *mode = &supported_mode;
>> +     struct v4l2_fwnode_device_properties props;
>> +     struct v4l2_ctrl_handler *ctrl_hdlr;
>> +     s64 pixel_rate_max;
>> +     s64 exposure_max;
>> +     s64 vblank_def;
>> +     s64 vblank_min;
>> +     u32 max_items;
>> +     s64 hblank;
>> +     int ret;
>> +
>> +     ret = v4l2_ctrl_handler_init(&ov05c10->ctrl_handler, 10);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ctrl_hdlr = &ov05c10->ctrl_handler;
>> +
>> +     max_items = ARRAY_SIZE(ov05c10_link_freq_menu_items) - 1;
>> +     ov05c10->link_freq =
>> +             v4l2_ctrl_new_int_menu(ctrl_hdlr,
>> +                                    NULL,
>> +                                    V4L2_CID_LINK_FREQ,
>> +                                    max_items,
>> +                                    0,
>> +                                    ov05c10_link_freq_menu_items);
>> +     if (ov05c10->link_freq)
>> +             ov05c10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>> +
>> +     pixel_rate_max =
>> +             link_freq_to_pixel_rate(ov05c10_link_freq_menu_items[0],
>> +                                     supported_mode.lanes);
>> +     ov05c10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
>> +                                             V4L2_CID_PIXEL_RATE,
>> +                                             0, pixel_rate_max,
>> +                                             1, pixel_rate_max);
>> +
>> +     vblank_def = mode->vts_def - mode->height;
>> +     vblank_min = mode->vts_min - mode->height;
>> +     ov05c10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
>> +                                         V4L2_CID_VBLANK,
>> +                                         vblank_min,
>> +                                         OV05C10_VTS_MAX - mode->height,
>> +                                         1, vblank_def);
>> +
>> +     hblank = (mode->hts > mode->width) ? (mode->hts - mode->width) : 0;
> 
> Here your hts uses 640 but width is 2888, which means hblank is set to 0
> here. This is wrong, please fix your configuration.
> 
Thanks, this is valid point, we shall check and fix in next version.

>> +     ov05c10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
>> +                                         V4L2_CID_HBLANK,
>> +                                         hblank, hblank, 1, hblank);
>> +     if (ov05c10->hblank)
>> +             ov05c10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>> +
>> +     exposure_max = mode->vts_def - OV05C10_EXPOSURE_MAX_MARGIN;
>> +     ov05c10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops,
>> +                                           V4L2_CID_EXPOSURE,
>> +                                           OV05C10_EXPOSURE_MIN,
>> +                                           exposure_max,
>> +                                           OV05C10_EXPOSURE_STEP,
>> +                                           exposure_max);
>> +
>> +     v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, 
>> V4L2_CID_ANALOGUE_GAIN,
>> +                       OV05C10_ANA_GAIN_MIN, OV05C10_ANA_GAIN_MAX,
>> +                       OV05C10_ANA_GAIN_STEP, OV05C10_ANA_GAIN_DEFAULT);
>> +
>> +     v4l2_ctrl_new_std(ctrl_hdlr, &ov05c10_ctrl_ops, 
>> V4L2_CID_DIGITAL_GAIN,
>> +                       OV05C10_DGTL_GAIN_MIN, OV05C10_DGTL_GAIN_MAX,
>> +                       OV05C10_DGTL_GAIN_STEP, 
>> OV05C10_DGTL_GAIN_DEFAULT);
>> +
>> +     v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov05c10_ctrl_ops,
>> +                                  V4L2_CID_TEST_PATTERN,
>> +                                  
>> ARRAY_SIZE(ov05c10_test_pattern_menu) - 1,
>> +                                  0, 0, ov05c10_test_pattern_menu);
>> +
>> +     if (ctrl_hdlr->error) {
>> +             ret = ctrl_hdlr->error;
>> +             dev_err(&client->dev, "V4L2 control init failed (%d)\n", 
>> ret);
>> +             goto err_hdl_free;
>> +     }
>> +
>> +     ret = v4l2_fwnode_device_parse(&client->dev, &props);
>> +     if (ret)
>> +             goto err_hdl_free;
>> +
>> +     ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov05c10_ctrl_ops,
>> +                                           &props);
>> +     if (ret)
>> +             goto err_hdl_free;
>> +
>> +     ov05c10->sd.ctrl_handler = ctrl_hdlr;
>> +
>> +     return 0;
>> +
>> +err_hdl_free:
>> +     v4l2_ctrl_handler_free(ctrl_hdlr);
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_parse_endpoint(struct device *dev,
>> +                               struct fwnode_handle *fwnode)
>> +{
>> +     struct v4l2_fwnode_endpoint bus_cfg = {
>> +             .bus_type = V4L2_MBUS_CSI2_DPHY
>> +     };
>> +     struct fwnode_handle *ep;
>> +     unsigned long bitmap;
>> +     int ret;
>> +
>> +     ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
>> +     if (!ep) {
>> +             dev_err(dev, "Failed to get next endpoint\n");
>> +             return -ENXIO;
>> +     }
>> +
>> +     ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
>> +     fwnode_handle_put(ep);
>> +     if (ret)
>> +             return ret;
>> +
>> +     if (bus_cfg.bus.mipi_csi2.num_data_lanes != supported_mode.lanes) {
>> +             dev_err(dev,
>> +                     "number of CSI2 data lanes %d is not supported\n",
>> +                     bus_cfg.bus.mipi_csi2.num_data_lanes);
>> +             ret = -EINVAL;
>> +             goto err_endpoint_free;
>> +     }
>> +
>> +     ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
>> +                                    bus_cfg.nr_of_link_frequencies,
>> +                                    ov05c10_link_frequencies,
>> +                                    
>> ARRAY_SIZE(ov05c10_link_frequencies),
>> +                                    &bitmap);
>> +     if (ret)
>> +             dev_err(dev, "v4l2_link_freq_to_bitmap fail with %d\n", 
>> ret);
>> +err_endpoint_free:
>> +     v4l2_fwnode_endpoint_free(&bus_cfg);
>> +
>> +     return ret;
>> +}
>> +
>> +static int ov05c10_probe(struct i2c_client *client)
>> +{
>> +     struct ov05c10 *ov05c10;
>> +     u32 clkfreq;
>> +     int ret;
>> +
>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), GFP_KERNEL);
>> +     if (!ov05c10)
>> +             return -ENOMEM;
>> +
>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
>> +
>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency", 
>> &clkfreq);
> 
> Maybe it's better to separate this part fwnode and GPIO code into a
> standalone function?
> 
We will update using the V4L2 helper function that Laurent suggested in 
the next version.

>> +     if (ret)
>> +             return  dev_err_probe(&client->dev, -EINVAL,
>> +                                   "fail to get clock freq\n");
>> +     if (clkfreq != OV05C10_REF_CLK)
>> +             return dev_err_probe(&client->dev, -EINVAL,
>> +                                  "fail invalid clock freq %u, %lu 
>> expected\n",
>> +                                  clkfreq, OV05C10_REF_CLK);
>> +
>> +     ret = ov05c10_parse_endpoint(&client->dev, fwnode);
>> +     if (ret)
>> +             return dev_err_probe(&client->dev, -EINVAL,
>> +                                  "fail to parse endpoint\n");
>> +
>> +     ov05c10->enable_gpio = devm_gpiod_get(&client->dev, "enable",
>> +                                           GPIOD_OUT_LOW);
>> +     if (IS_ERR(ov05c10->enable_gpio))
>> +             return dev_err_probe(&client->dev,
>> +                                  PTR_ERR(ov05c10->enable_gpio),
>> +                                  "fail to get enable gpio\n");
>> +
>> +     v4l2_i2c_subdev_init(&ov05c10->sd, client, &ov05c10_subdev_ops);
>> +
>> +     ov05c10->regmap = devm_cci_regmap_init_i2c(client, 8);
>> +     if (IS_ERR(ov05c10->regmap))
>> +             return dev_err_probe(&client->dev, PTR_ERR(ov05c10- 
>> >regmap),
>> +                                  "fail to init cci\n");
>> +
>> +     ov05c10->cur_page = -1;
>> +
>> +     ret = ov05c10_init_controls(ov05c10);
>> +     if (ret)
>> +             return dev_err_probe(&client->dev, ret, "fail to init 
>> ctl\n");
>> +
>> +     ov05c10->sd.internal_ops = &ov05c10_internal_ops;
>> +     ov05c10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +     ov05c10->sd.entity.ops = &ov05c10_subdev_entity_ops;
>> +     ov05c10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
>> +
>> +     ov05c10->pad.flags = MEDIA_PAD_FL_SOURCE;
>> +
>> +     ret = media_entity_pads_init(&ov05c10->sd.entity, NUM_OF_PADS,
>> +                                  &ov05c10->pad);
>> +     if (ret)
>> +             goto err_hdl_free;
>> +
>> +     ret = v4l2_subdev_init_finalize(&ov05c10->sd);
>> +     if (ret < 0)
>> +             goto err_media_entity_cleanup;
>> +
>> +     ret = v4l2_async_register_subdev_sensor(&ov05c10->sd);
>> +     if (ret)
>> +             goto err_media_entity_cleanup;
>> +
>> +     pm_runtime_set_active(&client->dev);
>> +     pm_runtime_enable(&client->dev);
>> +     pm_runtime_idle(&client->dev);
>> +     pm_runtime_set_autosuspend_delay(&client->dev, 1000);
>> +     pm_runtime_use_autosuspend(&client->dev);
>> +     return 0;
>> +
>> +err_media_entity_cleanup:
>> +     media_entity_cleanup(&ov05c10->sd.entity);
>> +
>> +err_hdl_free:
>> +     v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
>> +
>> +     return ret;
>> +}
>> +
>> +static void ov05c10_remove(struct i2c_client *client)
>> +{
>> +     struct v4l2_subdev *sd = i2c_get_clientdata(client);
>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>> +
>> +     v4l2_async_unregister_subdev(sd);
>> +     media_entity_cleanup(&sd->entity);
>> +     v4l2_ctrl_handler_free(ov05c10->sd.ctrl_handler);
>> +
>> +     pm_runtime_disable(&client->dev);
>> +     pm_runtime_set_suspended(&client->dev);
>> +}
>> +
>> +static int ov05c10_runtime_resume(struct device *dev)
>> +{
>> +     struct v4l2_subdev *sd = dev_get_drvdata(dev);
>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>> +
>> +     ov05c10_sensor_power_set(ov05c10, true);
>> +     return 0;
>> +}
>> +
>> +static int ov05c10_runtime_suspend(struct device *dev)
>> +{
>> +     struct v4l2_subdev *sd = dev_get_drvdata(dev);
>> +     struct ov05c10 *ov05c10 = to_ov05c10(sd);
>> +
>> +     ov05c10_sensor_power_set(ov05c10, false);
>> +     return 0;
>> +}
>> +
>> +static DEFINE_RUNTIME_DEV_PM_OPS(ov05c10_pm_ops, 
>> ov05c10_runtime_suspend,
>> +                              ov05c10_runtime_resume, NULL);
>> +
>> +static const struct i2c_device_id ov05c10_i2c_ids[] = {
>> +     {"ov05c10", 0 },
>> +     { }
>> +};
>> +MODULE_DEVICE_TABLE(i2c, ov05c10_i2c_ids);
>> +
>> +static struct i2c_driver ov05c10_i2c_driver = {
>> +     .driver = {
>> +             .name = DRV_NAME,
>> +             .pm = pm_ptr(&ov05c10_pm_ops),
>> +     },
>> +     .id_table = ov05c10_i2c_ids,
>> +     .probe = ov05c10_probe,
>> +     .remove = ov05c10_remove,
>> +};
>> +
>> +module_i2c_driver(ov05c10_i2c_driver);
>> +
>> +MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
>> +MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@amd.com>");
>> +MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
>> +MODULE_DESCRIPTION("OmniVision OV05C1010 sensor driver");
> 
> OV05C10
> 
Thanks, will fix it in the next version.

Thanks,
Pratap

>> +MODULE_LICENSE("GPL");
> 
> 
> Hi Sakari,
> 
> Seems there are already several camera sensors using page-based
> registers. Is it a good idea to add page support in CCI interface?
> 
> 
> Best Regards,
> Hao Yao
> 


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-14 22:52     ` Laurent Pinchart
  2025-06-16 22:33       ` Nirujogi, Pratap
@ 2025-06-23 11:27       ` Sakari Ailus
  2025-06-23 12:06         ` Laurent Pinchart
  1 sibling, 1 reply; 60+ messages in thread
From: Sakari Ailus @ 2025-06-23 11:27 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Sakari Ailus, Hao Yao, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su

Hi Laurent,

On Sun, Jun 15, 2025 at 01:52:57AM +0300, Laurent Pinchart wrote:
> > > > +#define OV05C10_REF_CLK			(24 * HZ_PER_MHZ)
> > > 
> > > Seems your module use 24 MHz clock input. The Dell's modules always use
> > > 19.2MHz, which means your the PLL settings will not work on Dell's.
> > 
> > This is ok as further work. Please send a patch. :-)
> 
> The patch should calculate PLL configuration dynamically, perhaps using
> the ccs-pll calculator if applicable.

As much as I do like your suggestion, I don't think it's really feasible to
often do this for Omnivision sensors (most others largely do just work
without much hassle wrt. PLL, as long as a PLL calculator exists). This
sensor's PLL tree is different from CCS and badly documented, as expected.

> > > Seems there are already several camera sensors using page-based registers.
> > > Is it a good idea to add page support in CCI interface?
> > 
> > Sounds like a good idea as such but I'm not sure how common this really is,
> > I think I've seen a few Omnivision sensors doing this. If implemented, I
> > think it would be nice if the page could be encoded in the register address
> > which V4L2 CCI would store and switch page if needed only. This would
> > require serialising accesses, too. There's some room in CCI register raw
> > value space so this could be done without even changing that, say, with
> > 8-bit page and 8-bit register address.
> 
> Ack. I've worked on a driver for the AP1302 external ISP, which also
> uses pages registers. The full address space spans 32 bits though, but
> fortunately the driver doesn't need to access anything above 0x00ffffff.

0xffffff? The current CCI register addresses are limited to 16 bits. To
support that, we'd need to use u64 most likely. For 16-bit register
addresses and 8-bit values which probably are the most common, that starts
to appear a bit wasteful.

-- 
Regards,

Sakari Ailus

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-23 11:27       ` Sakari Ailus
@ 2025-06-23 12:06         ` Laurent Pinchart
  2025-06-23 21:53           ` Nirujogi, Pratap
  0 siblings, 1 reply; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-23 12:06 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Sakari Ailus, Hao Yao, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su

On Mon, Jun 23, 2025 at 11:27:39AM +0000, Sakari Ailus wrote:
> On Sun, Jun 15, 2025 at 01:52:57AM +0300, Laurent Pinchart wrote:
> > > > > +#define OV05C10_REF_CLK			(24 * HZ_PER_MHZ)
> > > > 
> > > > Seems your module use 24 MHz clock input. The Dell's modules always use
> > > > 19.2MHz, which means your the PLL settings will not work on Dell's.
> > > 
> > > This is ok as further work. Please send a patch. :-)
> > 
> > The patch should calculate PLL configuration dynamically, perhaps using
> > the ccs-pll calculator if applicable.
> 
> As much as I do like your suggestion, I don't think it's really feasible to
> often do this for Omnivision sensors (most others largely do just work
> without much hassle wrt. PLL, as long as a PLL calculator exists). This
> sensor's PLL tree is different from CCS and badly documented, as expected.

How much do we know about the PLL structure ?

> > > > Seems there are already several camera sensors using page-based registers.
> > > > Is it a good idea to add page support in CCI interface?
> > > 
> > > Sounds like a good idea as such but I'm not sure how common this really is,
> > > I think I've seen a few Omnivision sensors doing this. If implemented, I
> > > think it would be nice if the page could be encoded in the register address
> > > which V4L2 CCI would store and switch page if needed only. This would
> > > require serialising accesses, too. There's some room in CCI register raw
> > > value space so this could be done without even changing that, say, with
> > > 8-bit page and 8-bit register address.
> > 
> > Ack. I've worked on a driver for the AP1302 external ISP, which also
> > uses pages registers. The full address space spans 32 bits though, but
> > fortunately the driver doesn't need to access anything above 0x00ffffff.
> 
> 0xffffff?

Yes.

> The current CCI register addresses are limited to 16 bits. To
> support that, we'd need to use u64 most likely.

I handled it in the ap1302 driver, by using bits 31:24 of address to
store a 8 bits page value. It's a hack as the CCI helper currently only
allocates 4 bits of the address to driver-specific purpose.

> For 16-bit register
> addresses and 8-bit values which probably are the most common, that starts
> to appear a bit wasteful.

It is wasteful, I don't want to turn the register address to a 64-bit
value.

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-16 23:12     ` Nirujogi, Pratap
@ 2025-06-23 12:09       ` Laurent Pinchart
  2025-06-23 13:22         ` Sakari Ailus
  0 siblings, 1 reply; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-23 12:09 UTC (permalink / raw)
  To: Nirujogi, Pratap
  Cc: Sakari Ailus, Hao Yao, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su

On Mon, Jun 16, 2025 at 07:12:28PM -0400, Nirujogi, Pratap wrote:
> On 6/13/2025 6:02 PM, Sakari Ailus wrote:
> > On Fri, Jun 13, 2025 at 12:55:46PM +0800, Hao Yao wrote:
> >> Hi Pratap,
> >>
> >> Thanks for your patch.
> >>
> >> This patch is written for your camera sensor module, which seems very
> >> different from those already applied on Dell laptops (some of "Dell Pro"
> >> series). Looking into the driver, I think this version will break the
> >> devices using ov05c10 sensor.
> > 
> > There never was such a driver in upstream so nothing breaks. However, in
> > order to support these, could you check what would it take to support them
> > using this driver and post patches, please?
> > 
> >> I think this patch is better to be validated on existing devices, but please
> >> do some fixes before we can do validation. Please check my comments inline.
> >>
> >> On 2025/6/10 03:42, Pratap Nirujogi wrote:
> >>> Add driver for OmniVision 5.2M OV05C10 sensor. This driver
> >>> supports only the full size normal 2888x1808@30fps 2-lane
> >>> sensor profile.
> >>>
> >>> Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> >>> Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> >>> Co-developed-by: Bin Du <bin.du@amd.com>
> >>> Signed-off-by: Bin Du <bin.du@amd.com>
> >>> Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>

[snip]

> >> Hi Sakari,
> >>
> >> Seems there are already several camera sensors using page-based registers.
> >> Is it a good idea to add page support in CCI interface?
> > 
> > Sounds like a good idea as such but I'm not sure how common this really is,
> > I think I've seen a few Omnivision sensors doing this. If implemented, I
> > think it would be nice if the page could be encoded in the register address
> > which V4L2 CCI would store and switch page if needed only. This would
> > require serialising accesses, too. There's some room in CCI register raw
> > value space so this could be done without even changing that, say, with
> > 8-bit page and 8-bit register address.
> 
> Hi Sakari, thank you for sharing your insights and guiding us. Could you 
> please suggest if we should take up this work implementing the helpers 
> in CCI and submit the patch or is it okay to leave it as-is for now and 
> take care of updating in future once the implementation is ready.

I think it can live in the driver for now. Given that the device uses
only 8 bits of register address, I would store the page number in bits
15:8 instead of bits 31:24, as the CCI helpers do not make bits 27:24
available for driver-specific purpose.

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-23 12:09       ` Laurent Pinchart
@ 2025-06-23 13:22         ` Sakari Ailus
  2025-06-23 13:42           ` Laurent Pinchart
  0 siblings, 1 reply; 60+ messages in thread
From: Sakari Ailus @ 2025-06-23 13:22 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Nirujogi, Pratap, Hao Yao, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su

Hi Laurent, Pratap,

On Mon, Jun 23, 2025 at 03:09:29PM +0300, Laurent Pinchart wrote:
> On Mon, Jun 16, 2025 at 07:12:28PM -0400, Nirujogi, Pratap wrote:
> > On 6/13/2025 6:02 PM, Sakari Ailus wrote:
> > > On Fri, Jun 13, 2025 at 12:55:46PM +0800, Hao Yao wrote:
> > >> Hi Pratap,
> > >>
> > >> Thanks for your patch.
> > >>
> > >> This patch is written for your camera sensor module, which seems very
> > >> different from those already applied on Dell laptops (some of "Dell Pro"
> > >> series). Looking into the driver, I think this version will break the
> > >> devices using ov05c10 sensor.
> > > 
> > > There never was such a driver in upstream so nothing breaks. However, in
> > > order to support these, could you check what would it take to support them
> > > using this driver and post patches, please?
> > > 
> > >> I think this patch is better to be validated on existing devices, but please
> > >> do some fixes before we can do validation. Please check my comments inline.
> > >>
> > >> On 2025/6/10 03:42, Pratap Nirujogi wrote:
> > >>> Add driver for OmniVision 5.2M OV05C10 sensor. This driver
> > >>> supports only the full size normal 2888x1808@30fps 2-lane
> > >>> sensor profile.
> > >>>
> > >>> Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> > >>> Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> > >>> Co-developed-by: Bin Du <bin.du@amd.com>
> > >>> Signed-off-by: Bin Du <bin.du@amd.com>
> > >>> Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
> 
> [snip]
> 
> > >> Hi Sakari,
> > >>
> > >> Seems there are already several camera sensors using page-based registers.
> > >> Is it a good idea to add page support in CCI interface?
> > > 
> > > Sounds like a good idea as such but I'm not sure how common this really is,
> > > I think I've seen a few Omnivision sensors doing this. If implemented, I
> > > think it would be nice if the page could be encoded in the register address
> > > which V4L2 CCI would store and switch page if needed only. This would
> > > require serialising accesses, too. There's some room in CCI register raw
> > > value space so this could be done without even changing that, say, with
> > > 8-bit page and 8-bit register address.
> > 
> > Hi Sakari, thank you for sharing your insights and guiding us. Could you 
> > please suggest if we should take up this work implementing the helpers 
> > in CCI and submit the patch or is it okay to leave it as-is for now and 
> > take care of updating in future once the implementation is ready.
> 
> I think it can live in the driver for now. Given that the device uses
> only 8 bits of register address, I would store the page number in bits
> 15:8 instead of bits 31:24, as the CCI helpers do not make bits 27:24
> available for driver-specific purpose.

I'd use the CCI private bits, the driver uses page numbers up to 4 so 4
bits are plenty for that. If we add pages to CCI later, this may be
refactored then.

-- 
Regards,

Sakari Ailus

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-23 13:22         ` Sakari Ailus
@ 2025-06-23 13:42           ` Laurent Pinchart
  2025-06-23 21:55             ` Nirujogi, Pratap
  0 siblings, 1 reply; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-23 13:42 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Nirujogi, Pratap, Hao Yao, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su

On Mon, Jun 23, 2025 at 01:22:00PM +0000, Sakari Ailus wrote:
> On Mon, Jun 23, 2025 at 03:09:29PM +0300, Laurent Pinchart wrote:
> > On Mon, Jun 16, 2025 at 07:12:28PM -0400, Nirujogi, Pratap wrote:
> > > On 6/13/2025 6:02 PM, Sakari Ailus wrote:
> > > > On Fri, Jun 13, 2025 at 12:55:46PM +0800, Hao Yao wrote:
> > > >> Hi Pratap,
> > > >>
> > > >> Thanks for your patch.
> > > >>
> > > >> This patch is written for your camera sensor module, which seems very
> > > >> different from those already applied on Dell laptops (some of "Dell Pro"
> > > >> series). Looking into the driver, I think this version will break the
> > > >> devices using ov05c10 sensor.
> > > > 
> > > > There never was such a driver in upstream so nothing breaks. However, in
> > > > order to support these, could you check what would it take to support them
> > > > using this driver and post patches, please?
> > > > 
> > > >> I think this patch is better to be validated on existing devices, but please
> > > >> do some fixes before we can do validation. Please check my comments inline.
> > > >>
> > > >> On 2025/6/10 03:42, Pratap Nirujogi wrote:
> > > >>> Add driver for OmniVision 5.2M OV05C10 sensor. This driver
> > > >>> supports only the full size normal 2888x1808@30fps 2-lane
> > > >>> sensor profile.
> > > >>>
> > > >>> Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> > > >>> Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> > > >>> Co-developed-by: Bin Du <bin.du@amd.com>
> > > >>> Signed-off-by: Bin Du <bin.du@amd.com>
> > > >>> Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
> > 
> > [snip]
> > 
> > > >> Hi Sakari,
> > > >>
> > > >> Seems there are already several camera sensors using page-based registers.
> > > >> Is it a good idea to add page support in CCI interface?
> > > > 
> > > > Sounds like a good idea as such but I'm not sure how common this really is,
> > > > I think I've seen a few Omnivision sensors doing this. If implemented, I
> > > > think it would be nice if the page could be encoded in the register address
> > > > which V4L2 CCI would store and switch page if needed only. This would
> > > > require serialising accesses, too. There's some room in CCI register raw
> > > > value space so this could be done without even changing that, say, with
> > > > 8-bit page and 8-bit register address.
> > > 
> > > Hi Sakari, thank you for sharing your insights and guiding us. Could you 
> > > please suggest if we should take up this work implementing the helpers 
> > > in CCI and submit the patch or is it okay to leave it as-is for now and 
> > > take care of updating in future once the implementation is ready.
> > 
> > I think it can live in the driver for now. Given that the device uses
> > only 8 bits of register address, I would store the page number in bits
> > 15:8 instead of bits 31:24, as the CCI helpers do not make bits 27:24
> > available for driver-specific purpose.
> 
> I'd use the CCI private bits, the driver uses page numbers up to 4 so 4
> bits are plenty for that. If we add pages to CCI later, this may be
> refactored then.

That works too.

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-16 22:49   ` Nirujogi, Pratap
@ 2025-06-23 21:51     ` Nirujogi, Pratap
  2025-06-23 22:05       ` Laurent Pinchart
  0 siblings, 1 reply; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-23 21:51 UTC (permalink / raw)
  To: Laurent Pinchart, Pratap Nirujogi
  Cc: mchehab, sakari.ailus, hverkuil, bryan.odonoghue, krzk,
	dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen, linux-media,
	linux-kernel, benjamin.chan, bin.du, grosikop, king.li, dantony,
	vengutta, Svetoslav.Stoilov, Yana.Zheleva

Hi Laurent,

On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
>>> +static int ov05c10_probe(struct i2c_client *client)
>>> +{
>>> +     struct ov05c10 *ov05c10;
>>> +     u32 clkfreq;
>>> +     int ret;
>>> +
>>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), 
>>> GFP_KERNEL);
>>> +     if (!ov05c10)
>>> +             return -ENOMEM;
>>> +
>>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
>>> +
>>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency", 
>>> &clkfreq);
>>> +     if (ret)
>>> +             return  dev_err_probe(&client->dev, -EINVAL,
>>> +                                   "fail to get clock freq\n");
>>
>> Let's try to land
>> https://lore.kernel.org/linux-media/20250521104115.176950-1- 
>> mehdi.djait@linux.intel.com/
>> and replace the code above with devm_v4l2_sensor_clk_get().
>>
> Ok, we will verify on our side.
> 
We tried using devm_v4l2_sensor_clk_get() and found its required to add 
support for software_node to make it work with this driver. Please refer 
the changes below and let us know if these should be submitted as a 
separate patch.

---
@@ -645,16 +645,16 @@ struct clk *devm_v4l2_sensor_clk_get(struct device 
*dev, const char *id)
         const char *clk_id __free(kfree) = NULL;
         struct clk_hw *clk_hw;
         struct clk *clk;
-       bool acpi_node;
+       bool is_node;
         u32 rate;
         int ret;

         clk = devm_clk_get_optional(dev, id);
         ret = device_property_read_u32(dev, "clock-frequency", &rate);
-       acpi_node = is_acpi_node(dev_fwnode(dev));
+       is_node = is_acpi_node(dev_fwnode(dev)) || 
is_software_node(dev_fwnode(dev));

         if (clk) {
-               if (!ret && acpi_node) {
+               if (!ret && is_node) {
                         ret = clk_set_rate(clk, rate);
                         if (ret) {
                                 dev_err(dev, "Failed to set clock rate: 
%u\n",
@@ -668,7 +668,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device 
*dev, const char *id)
         if (ret)
                 return ERR_PTR(ret);

-       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
+       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !is_node)
                 return ERR_PTR(-ENOENT);

         if (!id) {
----

Thanks,
Pratap


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-23 12:06         ` Laurent Pinchart
@ 2025-06-23 21:53           ` Nirujogi, Pratap
  2025-06-23 22:11             ` Laurent Pinchart
  0 siblings, 1 reply; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-23 21:53 UTC (permalink / raw)
  To: Laurent Pinchart, Sakari Ailus
  Cc: Sakari Ailus, Hao Yao, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su, Svetoslav.Stoilov, Yana.Zheleva

Hi Laurent, Sakari,

On 6/23/2025 8:06 AM, Laurent Pinchart wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> On Mon, Jun 23, 2025 at 11:27:39AM +0000, Sakari Ailus wrote:
>> On Sun, Jun 15, 2025 at 01:52:57AM +0300, Laurent Pinchart wrote:
>>>>>> +#define OV05C10_REF_CLK                      (24 * HZ_PER_MHZ)
>>>>>
>>>>> Seems your module use 24 MHz clock input. The Dell's modules always use
>>>>> 19.2MHz, which means your the PLL settings will not work on Dell's.
>>>>
>>>> This is ok as further work. Please send a patch. :-)
>>>
>>> The patch should calculate PLL configuration dynamically, perhaps using
>>> the ccs-pll calculator if applicable.
>>
>> As much as I do like your suggestion, I don't think it's really feasible to
>> often do this for Omnivision sensors (most others largely do just work
>> without much hassle wrt. PLL, as long as a PLL calculator exists). This
>> sensor's PLL tree is different from CCS and badly documented, as expected.
> 
> How much do we know about the PLL structure ?
> 
sorry to inform we don't have much details, we've consulted with the 
sensor vendor, but they are not willing to share specifics regarding the 
PLL calculations, register details, or configuration settings. They have 
recommended reaching out to them directly for any PLL configurations 
required for the modes we intend to support.

Thanks,
Pratap

>>>>> Seems there are already several camera sensors using page-based registers.
>>>>> Is it a good idea to add page support in CCI interface?
>>>>
>>>> Sounds like a good idea as such but I'm not sure how common this really is,
>>>> I think I've seen a few Omnivision sensors doing this. If implemented, I
>>>> think it would be nice if the page could be encoded in the register address
>>>> which V4L2 CCI would store and switch page if needed only. This would
>>>> require serialising accesses, too. There's some room in CCI register raw
>>>> value space so this could be done without even changing that, say, with
>>>> 8-bit page and 8-bit register address.
>>>
>>> Ack. I've worked on a driver for the AP1302 external ISP, which also
>>> uses pages registers. The full address space spans 32 bits though, but
>>> fortunately the driver doesn't need to access anything above 0x00ffffff.
>>
>> 0xffffff?
> 
> Yes.
> 
>> The current CCI register addresses are limited to 16 bits. To
>> support that, we'd need to use u64 most likely.
> 
> I handled it in the ap1302 driver, by using bits 31:24 of address to
> store a 8 bits page value. It's a hack as the CCI helper currently only
> allocates 4 bits of the address to driver-specific purpose.
> 
>> For 16-bit register
>> addresses and 8-bit values which probably are the most common, that starts
>> to appear a bit wasteful.
> 
> It is wasteful, I don't want to turn the register address to a 64-bit
> value.
> 
> --
> Regards,
> 
> Laurent Pinchart


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-23 13:42           ` Laurent Pinchart
@ 2025-06-23 21:55             ` Nirujogi, Pratap
  2025-06-23 22:06               ` Laurent Pinchart
  2025-06-25 22:06               ` Nirujogi, Pratap
  0 siblings, 2 replies; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-23 21:55 UTC (permalink / raw)
  To: Laurent Pinchart, Sakari Ailus
  Cc: Hao Yao, Pratap Nirujogi, mchehab, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, dongcheng.yan, jason.z.chen, jimmy.su,
	Svetoslav.Stoilov, Yana.Zheleva

Hi Laurent, Sakari,

On 6/23/2025 9:42 AM, Laurent Pinchart wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> On Mon, Jun 23, 2025 at 01:22:00PM +0000, Sakari Ailus wrote:
>> On Mon, Jun 23, 2025 at 03:09:29PM +0300, Laurent Pinchart wrote:
>>> On Mon, Jun 16, 2025 at 07:12:28PM -0400, Nirujogi, Pratap wrote:
>>>> On 6/13/2025 6:02 PM, Sakari Ailus wrote:
>>>>> On Fri, Jun 13, 2025 at 12:55:46PM +0800, Hao Yao wrote:
>>>>>> Hi Pratap,
>>>>>>
>>>>>> Thanks for your patch.
>>>>>>
>>>>>> This patch is written for your camera sensor module, which seems very
>>>>>> different from those already applied on Dell laptops (some of "Dell Pro"
>>>>>> series). Looking into the driver, I think this version will break the
>>>>>> devices using ov05c10 sensor.
>>>>>
>>>>> There never was such a driver in upstream so nothing breaks. However, in
>>>>> order to support these, could you check what would it take to support them
>>>>> using this driver and post patches, please?
>>>>>
>>>>>> I think this patch is better to be validated on existing devices, but please
>>>>>> do some fixes before we can do validation. Please check my comments inline.
>>>>>>
>>>>>> On 2025/6/10 03:42, Pratap Nirujogi wrote:
>>>>>>> Add driver for OmniVision 5.2M OV05C10 sensor. This driver
>>>>>>> supports only the full size normal 2888x1808@30fps 2-lane
>>>>>>> sensor profile.
>>>>>>>
>>>>>>> Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>>>>>>> Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>>>>>>> Co-developed-by: Bin Du <bin.du@amd.com>
>>>>>>> Signed-off-by: Bin Du <bin.du@amd.com>
>>>>>>> Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
>>>
>>> [snip]
>>>
>>>>>> Hi Sakari,
>>>>>>
>>>>>> Seems there are already several camera sensors using page-based registers.
>>>>>> Is it a good idea to add page support in CCI interface?
>>>>>
>>>>> Sounds like a good idea as such but I'm not sure how common this really is,
>>>>> I think I've seen a few Omnivision sensors doing this. If implemented, I
>>>>> think it would be nice if the page could be encoded in the register address
>>>>> which V4L2 CCI would store and switch page if needed only. This would
>>>>> require serialising accesses, too. There's some room in CCI register raw
>>>>> value space so this could be done without even changing that, say, with
>>>>> 8-bit page and 8-bit register address.
>>>>
>>>> Hi Sakari, thank you for sharing your insights and guiding us. Could you
>>>> please suggest if we should take up this work implementing the helpers
>>>> in CCI and submit the patch or is it okay to leave it as-is for now and
>>>> take care of updating in future once the implementation is ready.
>>>
>>> I think it can live in the driver for now. Given that the device uses
>>> only 8 bits of register address, I would store the page number in bits
>>> 15:8 instead of bits 31:24, as the CCI helpers do not make bits 27:24
>>> available for driver-specific purpose.
>>
>> I'd use the CCI private bits, the driver uses page numbers up to 4 so 4
>> bits are plenty for that. If we add pages to CCI later, this may be
>> refactored then.
> 
> That works too.
> 
Thanks for your support. We will add the page number in the register 
address 15:8 or 11:8 and will update the implementation accordingly in 
the next version.

Thanks,
Pratap

> --
> Regards,
> 
> Laurent Pinchart


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-23 21:51     ` Nirujogi, Pratap
@ 2025-06-23 22:05       ` Laurent Pinchart
  2025-06-23 23:28         ` Nirujogi, Pratap
  2025-06-24  8:35         ` Mehdi Djait
  0 siblings, 2 replies; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-23 22:05 UTC (permalink / raw)
  To: Nirujogi, Pratap
  Cc: Pratap Nirujogi, mchehab, sakari.ailus, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, Svetoslav.Stoilov, Yana.Zheleva,
	Mehdi Djait

(CC'ing Mehdi)

On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
> On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
> >>> +static int ov05c10_probe(struct i2c_client *client)
> >>> +{
> >>> +     struct ov05c10 *ov05c10;
> >>> +     u32 clkfreq;
> >>> +     int ret;
> >>> +
> >>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), 
> >>> GFP_KERNEL);
> >>> +     if (!ov05c10)
> >>> +             return -ENOMEM;
> >>> +
> >>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> >>> +
> >>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency", 
> >>> &clkfreq);
> >>> +     if (ret)
> >>> +             return  dev_err_probe(&client->dev, -EINVAL,
> >>> +                                   "fail to get clock freq\n");
> >>
> >> Let's try to land
> >> https://lore.kernel.org/linux-media/20250521104115.176950-1- 
> >> mehdi.djait@linux.intel.com/
> >> and replace the code above with devm_v4l2_sensor_clk_get().
> >>
> > Ok, we will verify on our side.
> 
> We tried using devm_v4l2_sensor_clk_get() and found its required to add 
> support for software_node to make it work with this driver.

Why is that ?

> Please refer 
> the changes below and let us know if these should be submitted as a 
> separate patch.

Mehdi, do you have any comment ?

> ---
> @@ -645,16 +645,16 @@ struct clk *devm_v4l2_sensor_clk_get(struct device 
> *dev, const char *id)
>          const char *clk_id __free(kfree) = NULL;
>          struct clk_hw *clk_hw;
>          struct clk *clk;
> -       bool acpi_node;
> +       bool is_node;
>          u32 rate;
>          int ret;
> 
>          clk = devm_clk_get_optional(dev, id);
>          ret = device_property_read_u32(dev, "clock-frequency", &rate);
> -       acpi_node = is_acpi_node(dev_fwnode(dev));
> +       is_node = is_acpi_node(dev_fwnode(dev)) || 
> is_software_node(dev_fwnode(dev));
> 
>          if (clk) {
> -               if (!ret && acpi_node) {
> +               if (!ret && is_node) {
>                          ret = clk_set_rate(clk, rate);
>                          if (ret) {
>                                  dev_err(dev, "Failed to set clock rate: 
> %u\n",
> @@ -668,7 +668,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device 
> *dev, const char *id)
>          if (ret)
>                  return ERR_PTR(ret);
> 
> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !is_node)
>                  return ERR_PTR(-ENOENT);
> 
>          if (!id) {
> ----

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-23 21:55             ` Nirujogi, Pratap
@ 2025-06-23 22:06               ` Laurent Pinchart
  2025-06-23 23:21                 ` Nirujogi, Pratap
  2025-06-25 22:06               ` Nirujogi, Pratap
  1 sibling, 1 reply; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-23 22:06 UTC (permalink / raw)
  To: Nirujogi, Pratap
  Cc: Sakari Ailus, Hao Yao, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su, Svetoslav.Stoilov, Yana.Zheleva

On Mon, Jun 23, 2025 at 05:55:14PM -0400, Nirujogi, Pratap wrote:
> On 6/23/2025 9:42 AM, Laurent Pinchart wrote:
> > On Mon, Jun 23, 2025 at 01:22:00PM +0000, Sakari Ailus wrote:
> >> On Mon, Jun 23, 2025 at 03:09:29PM +0300, Laurent Pinchart wrote:
> >>> On Mon, Jun 16, 2025 at 07:12:28PM -0400, Nirujogi, Pratap wrote:
> >>>> On 6/13/2025 6:02 PM, Sakari Ailus wrote:
> >>>>> On Fri, Jun 13, 2025 at 12:55:46PM +0800, Hao Yao wrote:
> >>>>>> Hi Pratap,
> >>>>>>
> >>>>>> Thanks for your patch.
> >>>>>>
> >>>>>> This patch is written for your camera sensor module, which seems very
> >>>>>> different from those already applied on Dell laptops (some of "Dell Pro"
> >>>>>> series). Looking into the driver, I think this version will break the
> >>>>>> devices using ov05c10 sensor.
> >>>>>
> >>>>> There never was such a driver in upstream so nothing breaks. However, in
> >>>>> order to support these, could you check what would it take to support them
> >>>>> using this driver and post patches, please?
> >>>>>
> >>>>>> I think this patch is better to be validated on existing devices, but please
> >>>>>> do some fixes before we can do validation. Please check my comments inline.
> >>>>>>
> >>>>>> On 2025/6/10 03:42, Pratap Nirujogi wrote:
> >>>>>>> Add driver for OmniVision 5.2M OV05C10 sensor. This driver
> >>>>>>> supports only the full size normal 2888x1808@30fps 2-lane
> >>>>>>> sensor profile.
> >>>>>>>
> >>>>>>> Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> >>>>>>> Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
> >>>>>>> Co-developed-by: Bin Du <bin.du@amd.com>
> >>>>>>> Signed-off-by: Bin Du <bin.du@amd.com>
> >>>>>>> Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
> >>>
> >>> [snip]
> >>>
> >>>>>> Hi Sakari,
> >>>>>>
> >>>>>> Seems there are already several camera sensors using page-based registers.
> >>>>>> Is it a good idea to add page support in CCI interface?
> >>>>>
> >>>>> Sounds like a good idea as such but I'm not sure how common this really is,
> >>>>> I think I've seen a few Omnivision sensors doing this. If implemented, I
> >>>>> think it would be nice if the page could be encoded in the register address
> >>>>> which V4L2 CCI would store and switch page if needed only. This would
> >>>>> require serialising accesses, too. There's some room in CCI register raw
> >>>>> value space so this could be done without even changing that, say, with
> >>>>> 8-bit page and 8-bit register address.
> >>>>
> >>>> Hi Sakari, thank you for sharing your insights and guiding us. Could you
> >>>> please suggest if we should take up this work implementing the helpers
> >>>> in CCI and submit the patch or is it okay to leave it as-is for now and
> >>>> take care of updating in future once the implementation is ready.
> >>>
> >>> I think it can live in the driver for now. Given that the device uses
> >>> only 8 bits of register address, I would store the page number in bits
> >>> 15:8 instead of bits 31:24, as the CCI helpers do not make bits 27:24
> >>> available for driver-specific purpose.
> >>
> >> I'd use the CCI private bits, the driver uses page numbers up to 4 so 4
> >> bits are plenty for that. If we add pages to CCI later, this may be
> >> refactored then.
> > 
> > That works too.
> 
> Thanks for your support. We will add the page number in the register 
> address 15:8 or 11:8 and will update the implementation accordingly in 
> the next version.

The CCI private bits are bits 31:28.

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-23 21:53           ` Nirujogi, Pratap
@ 2025-06-23 22:11             ` Laurent Pinchart
  0 siblings, 0 replies; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-23 22:11 UTC (permalink / raw)
  To: Nirujogi, Pratap
  Cc: Sakari Ailus, Sakari Ailus, Hao Yao, Pratap Nirujogi, mchehab,
	hverkuil, bryan.odonoghue, krzk, dave.stevenson, hdegoede,
	jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	dongcheng.yan, jason.z.chen, jimmy.su, Svetoslav.Stoilov,
	Yana.Zheleva

On Mon, Jun 23, 2025 at 05:53:44PM -0400, Nirujogi, Pratap wrote:
> On 6/23/2025 8:06 AM, Laurent Pinchart wrote:
> > On Mon, Jun 23, 2025 at 11:27:39AM +0000, Sakari Ailus wrote:
> >> On Sun, Jun 15, 2025 at 01:52:57AM +0300, Laurent Pinchart wrote:
> >>>>>> +#define OV05C10_REF_CLK                      (24 * HZ_PER_MHZ)
> >>>>>
> >>>>> Seems your module use 24 MHz clock input. The Dell's modules always use
> >>>>> 19.2MHz, which means your the PLL settings will not work on Dell's.
> >>>>
> >>>> This is ok as further work. Please send a patch. :-)
> >>>
> >>> The patch should calculate PLL configuration dynamically, perhaps using
> >>> the ccs-pll calculator if applicable.
> >>
> >> As much as I do like your suggestion, I don't think it's really feasible to
> >> often do this for Omnivision sensors (most others largely do just work
> >> without much hassle wrt. PLL, as long as a PLL calculator exists). This
> >> sensor's PLL tree is different from CCS and badly documented, as expected.
> > 
> > How much do we know about the PLL structure ?
> 
> sorry to inform we don't have much details, we've consulted with the 
> sensor vendor, but they are not willing to share specifics regarding the 
> PLL calculations, register details, or configuration settings. They have 
> recommended reaching out to them directly for any PLL configurations 
> required for the modes we intend to support.

Do you have access to the sensor datasheet ?

> >>>>> Seems there are already several camera sensors using page-based registers.
> >>>>> Is it a good idea to add page support in CCI interface?
> >>>>
> >>>> Sounds like a good idea as such but I'm not sure how common this really is,
> >>>> I think I've seen a few Omnivision sensors doing this. If implemented, I
> >>>> think it would be nice if the page could be encoded in the register address
> >>>> which V4L2 CCI would store and switch page if needed only. This would
> >>>> require serialising accesses, too. There's some room in CCI register raw
> >>>> value space so this could be done without even changing that, say, with
> >>>> 8-bit page and 8-bit register address.
> >>>
> >>> Ack. I've worked on a driver for the AP1302 external ISP, which also
> >>> uses pages registers. The full address space spans 32 bits though, but
> >>> fortunately the driver doesn't need to access anything above 0x00ffffff.
> >>
> >> 0xffffff?
> > 
> > Yes.
> > 
> >> The current CCI register addresses are limited to 16 bits. To
> >> support that, we'd need to use u64 most likely.
> > 
> > I handled it in the ap1302 driver, by using bits 31:24 of address to
> > store a 8 bits page value. It's a hack as the CCI helper currently only
> > allocates 4 bits of the address to driver-specific purpose.
> > 
> >> For 16-bit register
> >> addresses and 8-bit values which probably are the most common, that starts
> >> to appear a bit wasteful.
> > 
> > It is wasteful, I don't want to turn the register address to a 64-bit
> > value.

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-23 22:06               ` Laurent Pinchart
@ 2025-06-23 23:21                 ` Nirujogi, Pratap
  0 siblings, 0 replies; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-23 23:21 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Sakari Ailus, Hao Yao, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su, Svetoslav.Stoilov, Yana.Zheleva

On 6/23/2025 6:06 PM, Laurent Pinchart wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> On Mon, Jun 23, 2025 at 05:55:14PM -0400, Nirujogi, Pratap wrote:
>> On 6/23/2025 9:42 AM, Laurent Pinchart wrote:
>>> On Mon, Jun 23, 2025 at 01:22:00PM +0000, Sakari Ailus wrote:
>>>> On Mon, Jun 23, 2025 at 03:09:29PM +0300, Laurent Pinchart wrote:
>>>>> On Mon, Jun 16, 2025 at 07:12:28PM -0400, Nirujogi, Pratap wrote:
>>>>>> On 6/13/2025 6:02 PM, Sakari Ailus wrote:
>>>>>>> On Fri, Jun 13, 2025 at 12:55:46PM +0800, Hao Yao wrote:
>>>>>>>> Hi Pratap,
>>>>>>>>
>>>>>>>> Thanks for your patch.
>>>>>>>>
>>>>>>>> This patch is written for your camera sensor module, which seems very
>>>>>>>> different from those already applied on Dell laptops (some of "Dell Pro"
>>>>>>>> series). Looking into the driver, I think this version will break the
>>>>>>>> devices using ov05c10 sensor.
>>>>>>>
>>>>>>> There never was such a driver in upstream so nothing breaks. However, in
>>>>>>> order to support these, could you check what would it take to support them
>>>>>>> using this driver and post patches, please?
>>>>>>>
>>>>>>>> I think this patch is better to be validated on existing devices, but please
>>>>>>>> do some fixes before we can do validation. Please check my comments inline.
>>>>>>>>
>>>>>>>> On 2025/6/10 03:42, Pratap Nirujogi wrote:
>>>>>>>>> Add driver for OmniVision 5.2M OV05C10 sensor. This driver
>>>>>>>>> supports only the full size normal 2888x1808@30fps 2-lane
>>>>>>>>> sensor profile.
>>>>>>>>>
>>>>>>>>> Co-developed-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>>>>>>>>> Signed-off-by: Venkata Narendra Kumar Gutta <vengutta@amd.com>
>>>>>>>>> Co-developed-by: Bin Du <bin.du@amd.com>
>>>>>>>>> Signed-off-by: Bin Du <bin.du@amd.com>
>>>>>>>>> Signed-off-by: Pratap Nirujogi <pratap.nirujogi@amd.com>
>>>>>
>>>>> [snip]
>>>>>
>>>>>>>> Hi Sakari,
>>>>>>>>
>>>>>>>> Seems there are already several camera sensors using page-based registers.
>>>>>>>> Is it a good idea to add page support in CCI interface?
>>>>>>>
>>>>>>> Sounds like a good idea as such but I'm not sure how common this really is,
>>>>>>> I think I've seen a few Omnivision sensors doing this. If implemented, I
>>>>>>> think it would be nice if the page could be encoded in the register address
>>>>>>> which V4L2 CCI would store and switch page if needed only. This would
>>>>>>> require serialising accesses, too. There's some room in CCI register raw
>>>>>>> value space so this could be done without even changing that, say, with
>>>>>>> 8-bit page and 8-bit register address.
>>>>>>
>>>>>> Hi Sakari, thank you for sharing your insights and guiding us. Could you
>>>>>> please suggest if we should take up this work implementing the helpers
>>>>>> in CCI and submit the patch or is it okay to leave it as-is for now and
>>>>>> take care of updating in future once the implementation is ready.
>>>>>
>>>>> I think it can live in the driver for now. Given that the device uses
>>>>> only 8 bits of register address, I would store the page number in bits
>>>>> 15:8 instead of bits 31:24, as the CCI helpers do not make bits 27:24
>>>>> available for driver-specific purpose.
>>>>
>>>> I'd use the CCI private bits, the driver uses page numbers up to 4 so 4
>>>> bits are plenty for that. If we add pages to CCI later, this may be
>>>> refactored then.
>>>
>>> That works too.
>>
>> Thanks for your support. We will add the page number in the register
>> address 15:8 or 11:8 and will update the implementation accordingly in
>> the next version.
> 
> The CCI private bits are bits 31:28.
> 
Thanks. Will use the CCI private bits 31:28 instead of bits 15:8 / 11:8

Thanks,
Pratap

> --
> Regards,
> 
> Laurent Pinchart


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-23 22:05       ` Laurent Pinchart
@ 2025-06-23 23:28         ` Nirujogi, Pratap
  2025-06-24  0:19           ` Laurent Pinchart
  2025-06-24  8:35         ` Mehdi Djait
  1 sibling, 1 reply; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-23 23:28 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Pratap Nirujogi, mchehab, sakari.ailus, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, Svetoslav.Stoilov, Yana.Zheleva,
	Mehdi Djait

Hi Laurent,

On 6/23/2025 6:05 PM, Laurent Pinchart wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> (CC'ing Mehdi)
> 
> On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
>> On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
>>>>> +static int ov05c10_probe(struct i2c_client *client)
>>>>> +{
>>>>> +     struct ov05c10 *ov05c10;
>>>>> +     u32 clkfreq;
>>>>> +     int ret;
>>>>> +
>>>>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10),
>>>>> GFP_KERNEL);
>>>>> +     if (!ov05c10)
>>>>> +             return -ENOMEM;
>>>>> +
>>>>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
>>>>> +
>>>>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency",
>>>>> &clkfreq);
>>>>> +     if (ret)
>>>>> +             return  dev_err_probe(&client->dev, -EINVAL,
>>>>> +                                   "fail to get clock freq\n");
>>>>
>>>> Let's try to land
>>>> https://lore.kernel.org/linux-media/20250521104115.176950-1-
>>>> mehdi.djait@linux.intel.com/
>>>> and replace the code above with devm_v4l2_sensor_clk_get().
>>>>
>>> Ok, we will verify on our side.
>>
>> We tried using devm_v4l2_sensor_clk_get() and found its required to add
>> support for software_node to make it work with this driver.
> 
> Why is that ?
> 
Its because the i2c_client device is initialized with swnode in the 
x86/platform driver.

https://github.com/torvalds/linux/blob/master/drivers/platform/x86/amd/amd_isp4.c#L235

Thanks,
Pratap


>> Please refer
>> the changes below and let us know if these should be submitted as a
>> separate patch.
> 
> Mehdi, do you have any comment ?
> 
>> ---
>> @@ -645,16 +645,16 @@ struct clk *devm_v4l2_sensor_clk_get(struct device
>> *dev, const char *id)
>>           const char *clk_id __free(kfree) = NULL;
>>           struct clk_hw *clk_hw;
>>           struct clk *clk;
>> -       bool acpi_node;
>> +       bool is_node;
>>           u32 rate;
>>           int ret;
>>
>>           clk = devm_clk_get_optional(dev, id);
>>           ret = device_property_read_u32(dev, "clock-frequency", &rate);
>> -       acpi_node = is_acpi_node(dev_fwnode(dev));
>> +       is_node = is_acpi_node(dev_fwnode(dev)) ||
>> is_software_node(dev_fwnode(dev));
>>
>>           if (clk) {
>> -               if (!ret && acpi_node) {
>> +               if (!ret && is_node) {
>>                           ret = clk_set_rate(clk, rate);
>>                           if (ret) {
>>                                   dev_err(dev, "Failed to set clock rate:
>> %u\n",
>> @@ -668,7 +668,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device
>> *dev, const char *id)
>>           if (ret)
>>                   return ERR_PTR(ret);
>>
>> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
>> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !is_node)
>>                   return ERR_PTR(-ENOENT);
>>
>>           if (!id) {
>> ----
> 
> --
> Regards,
> 
> Laurent Pinchart


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-23 23:28         ` Nirujogi, Pratap
@ 2025-06-24  0:19           ` Laurent Pinchart
  2025-06-24 18:49             ` Nirujogi, Pratap
  0 siblings, 1 reply; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-24  0:19 UTC (permalink / raw)
  To: Nirujogi, Pratap
  Cc: Pratap Nirujogi, mchehab, sakari.ailus, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, Svetoslav.Stoilov, Yana.Zheleva,
	Mehdi Djait

On Mon, Jun 23, 2025 at 07:28:46PM -0400, Nirujogi, Pratap wrote:
> On 6/23/2025 6:05 PM, Laurent Pinchart wrote:
> > On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
> >> On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
> >>>>> +static int ov05c10_probe(struct i2c_client *client)
> >>>>> +{
> >>>>> +     struct ov05c10 *ov05c10;
> >>>>> +     u32 clkfreq;
> >>>>> +     int ret;
> >>>>> +
> >>>>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10),
> >>>>> GFP_KERNEL);
> >>>>> +     if (!ov05c10)
> >>>>> +             return -ENOMEM;
> >>>>> +
> >>>>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> >>>>> +
> >>>>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency",
> >>>>> &clkfreq);
> >>>>> +     if (ret)
> >>>>> +             return  dev_err_probe(&client->dev, -EINVAL,
> >>>>> +                                   "fail to get clock freq\n");
> >>>>
> >>>> Let's try to land
> >>>> https://lore.kernel.org/linux-media/20250521104115.176950-1-
> >>>> mehdi.djait@linux.intel.com/
> >>>> and replace the code above with devm_v4l2_sensor_clk_get().
> >>>>
> >>> Ok, we will verify on our side.
> >>
> >> We tried using devm_v4l2_sensor_clk_get() and found its required to add
> >> support for software_node to make it work with this driver.
> > 
> > Why is that ?
> 
> Its because the i2c_client device is initialized with swnode in the 
> x86/platform driver.
> 
> https://github.com/torvalds/linux/blob/master/drivers/platform/x86/amd/amd_isp4.c#L235

So there's no information provided in the _DSD for the sensor ?

Looking at that platform driver, it matches the device based on the
sensor ACPI HID only ("OMNI5C10"). That doesn't seem quite right, I
think you need a DMI match as well. You can't assume that OMNI5C10,
which identifies the sensor, will always map to specific platform
integration data (connected to an AMD ISP, using a particular link
frequency, ...), can you ?

> >> Please refer
> >> the changes below and let us know if these should be submitted as a
> >> separate patch.
> > 
> > Mehdi, do you have any comment ?
> > 
> >> ---
> >> @@ -645,16 +645,16 @@ struct clk *devm_v4l2_sensor_clk_get(struct device
> >> *dev, const char *id)
> >>           const char *clk_id __free(kfree) = NULL;
> >>           struct clk_hw *clk_hw;
> >>           struct clk *clk;
> >> -       bool acpi_node;
> >> +       bool is_node;
> >>           u32 rate;
> >>           int ret;
> >>
> >>           clk = devm_clk_get_optional(dev, id);
> >>           ret = device_property_read_u32(dev, "clock-frequency", &rate);
> >> -       acpi_node = is_acpi_node(dev_fwnode(dev));
> >> +       is_node = is_acpi_node(dev_fwnode(dev)) || is_software_node(dev_fwnode(dev));
> >>
> >>           if (clk) {
> >> -               if (!ret && acpi_node) {
> >> +               if (!ret && is_node) {
> >>                           ret = clk_set_rate(clk, rate);
> >>                           if (ret) {
> >>                                   dev_err(dev, "Failed to set clock rate: %u\n",
> >> @@ -668,7 +668,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device
> >> *dev, const char *id)
> >>           if (ret)
> >>                   return ERR_PTR(ret);
> >>
> >> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
> >> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !is_node)
> >>                   return ERR_PTR(-ENOENT);
> >>
> >>           if (!id) {
> >> ----

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-23 22:05       ` Laurent Pinchart
  2025-06-23 23:28         ` Nirujogi, Pratap
@ 2025-06-24  8:35         ` Mehdi Djait
  2025-06-24 10:19           ` Sakari Ailus
  1 sibling, 1 reply; 60+ messages in thread
From: Mehdi Djait @ 2025-06-24  8:35 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Nirujogi, Pratap, Pratap Nirujogi, mchehab, sakari.ailus,
	hverkuil, bryan.odonoghue, krzk, dave.stevenson, hdegoede,
	jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	Svetoslav.Stoilov, Yana.Zheleva

Hi Laurent, Hi Pratap,

Thank you for the patch

On Tue, Jun 24, 2025 at 01:05:03AM +0300, Laurent Pinchart wrote:
> (CC'ing Mehdi)
> 
> On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
> > On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
> > >>> +static int ov05c10_probe(struct i2c_client *client)
> > >>> +{
> > >>> +     struct ov05c10 *ov05c10;
> > >>> +     u32 clkfreq;
> > >>> +     int ret;
> > >>> +
> > >>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), 
> > >>> GFP_KERNEL);
> > >>> +     if (!ov05c10)
> > >>> +             return -ENOMEM;
> > >>> +
> > >>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> > >>> +
> > >>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency", 
> > >>> &clkfreq);
> > >>> +     if (ret)
> > >>> +             return  dev_err_probe(&client->dev, -EINVAL,
> > >>> +                                   "fail to get clock freq\n");
> > >>
> > >> Let's try to land
> > >> https://lore.kernel.org/linux-media/20250521104115.176950-1- 
> > >> mehdi.djait@linux.intel.com/
> > >> and replace the code above with devm_v4l2_sensor_clk_get().
> > >>
> > > Ok, we will verify on our side.
> > 
> > We tried using devm_v4l2_sensor_clk_get() and found its required to add 
> > support for software_node to make it work with this driver.
> 
> Why is that ?
> 
> > Please refer 
> > the changes below and let us know if these should be submitted as a 
> > separate patch.

The helper is still not merged, so no patch is required.

I will see if a change is needed from the helper side or the OV05C10 side.

> 
> Mehdi, do you have any comment ?
> 

No comment for now: I will investigate this.

--
Kind Regards
Mehdi Djait

> > ---
> > @@ -645,16 +645,16 @@ struct clk *devm_v4l2_sensor_clk_get(struct device 
> > *dev, const char *id)
> >          const char *clk_id __free(kfree) = NULL;
> >          struct clk_hw *clk_hw;
> >          struct clk *clk;
> > -       bool acpi_node;
> > +       bool is_node;
> >          u32 rate;
> >          int ret;
> > 
> >          clk = devm_clk_get_optional(dev, id);
> >          ret = device_property_read_u32(dev, "clock-frequency", &rate);
> > -       acpi_node = is_acpi_node(dev_fwnode(dev));
> > +       is_node = is_acpi_node(dev_fwnode(dev)) || 
> > is_software_node(dev_fwnode(dev));
> > 
> >          if (clk) {
> > -               if (!ret && acpi_node) {
> > +               if (!ret && is_node) {
> >                          ret = clk_set_rate(clk, rate);
> >                          if (ret) {
> >                                  dev_err(dev, "Failed to set clock rate: 
> > %u\n",
> > @@ -668,7 +668,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device 
> > *dev, const char *id)
> >          if (ret)
> >                  return ERR_PTR(ret);
> > 
> > -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
> > +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !is_node)
> >                  return ERR_PTR(-ENOENT);
> > 
> >          if (!id) {
> > ----
> 
> -- 
> Regards,
> 
> Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24  8:35         ` Mehdi Djait
@ 2025-06-24 10:19           ` Sakari Ailus
  2025-06-24 10:20             ` Sakari Ailus
  0 siblings, 1 reply; 60+ messages in thread
From: Sakari Ailus @ 2025-06-24 10:19 UTC (permalink / raw)
  To: Mehdi Djait
  Cc: Laurent Pinchart, Nirujogi, Pratap, Pratap Nirujogi, mchehab,
	hverkuil, bryan.odonoghue, krzk, dave.stevenson, hdegoede,
	jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	Svetoslav.Stoilov, Yana.Zheleva

Hi Mehdi,

On Tue, Jun 24, 2025 at 10:35:18AM +0200, Mehdi Djait wrote:
> Hi Laurent, Hi Pratap,
> 
> Thank you for the patch
> 
> On Tue, Jun 24, 2025 at 01:05:03AM +0300, Laurent Pinchart wrote:
> > (CC'ing Mehdi)
> > 
> > On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
> > > On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
> > > >>> +static int ov05c10_probe(struct i2c_client *client)
> > > >>> +{
> > > >>> +     struct ov05c10 *ov05c10;
> > > >>> +     u32 clkfreq;
> > > >>> +     int ret;
> > > >>> +
> > > >>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), 
> > > >>> GFP_KERNEL);
> > > >>> +     if (!ov05c10)
> > > >>> +             return -ENOMEM;
> > > >>> +
> > > >>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> > > >>> +
> > > >>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency", 
> > > >>> &clkfreq);
> > > >>> +     if (ret)
> > > >>> +             return  dev_err_probe(&client->dev, -EINVAL,
> > > >>> +                                   "fail to get clock freq\n");
> > > >>
> > > >> Let's try to land
> > > >> https://lore.kernel.org/linux-media/20250521104115.176950-1- 
> > > >> mehdi.djait@linux.intel.com/
> > > >> and replace the code above with devm_v4l2_sensor_clk_get().
> > > >>
> > > > Ok, we will verify on our side.
> > > 
> > > We tried using devm_v4l2_sensor_clk_get() and found its required to add 
> > > support for software_node to make it work with this driver.
> > 
> > Why is that ?
> > 
> > > Please refer 
> > > the changes below and let us know if these should be submitted as a 
> > > separate patch.
> 
> The helper is still not merged, so no patch is required.
> 
> I will see if a change is needed from the helper side or the OV05C10 side.

I wonder if there's a better way to figure out if you're running on a DT or
ACPI based system than getting the device's parents and checking which one
you find first, DT or ACPI. I think that should work for now at least.

-- 
Regards,

Sakari Ailus

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24 10:19           ` Sakari Ailus
@ 2025-06-24 10:20             ` Sakari Ailus
  2025-06-24 10:27               ` Laurent Pinchart
  0 siblings, 1 reply; 60+ messages in thread
From: Sakari Ailus @ 2025-06-24 10:20 UTC (permalink / raw)
  To: Mehdi Djait
  Cc: Laurent Pinchart, Nirujogi, Pratap, Pratap Nirujogi, mchehab,
	hverkuil, bryan.odonoghue, krzk, dave.stevenson, hdegoede,
	jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	Svetoslav.Stoilov, Yana.Zheleva

On Tue, Jun 24, 2025 at 10:19:35AM +0000, Sakari Ailus wrote:
> Hi Mehdi,
> 
> On Tue, Jun 24, 2025 at 10:35:18AM +0200, Mehdi Djait wrote:
> > Hi Laurent, Hi Pratap,
> > 
> > Thank you for the patch
> > 
> > On Tue, Jun 24, 2025 at 01:05:03AM +0300, Laurent Pinchart wrote:
> > > (CC'ing Mehdi)
> > > 
> > > On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
> > > > On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
> > > > >>> +static int ov05c10_probe(struct i2c_client *client)
> > > > >>> +{
> > > > >>> +     struct ov05c10 *ov05c10;
> > > > >>> +     u32 clkfreq;
> > > > >>> +     int ret;
> > > > >>> +
> > > > >>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), 
> > > > >>> GFP_KERNEL);
> > > > >>> +     if (!ov05c10)
> > > > >>> +             return -ENOMEM;
> > > > >>> +
> > > > >>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> > > > >>> +
> > > > >>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency", 
> > > > >>> &clkfreq);
> > > > >>> +     if (ret)
> > > > >>> +             return  dev_err_probe(&client->dev, -EINVAL,
> > > > >>> +                                   "fail to get clock freq\n");
> > > > >>
> > > > >> Let's try to land
> > > > >> https://lore.kernel.org/linux-media/20250521104115.176950-1- 
> > > > >> mehdi.djait@linux.intel.com/
> > > > >> and replace the code above with devm_v4l2_sensor_clk_get().
> > > > >>
> > > > > Ok, we will verify on our side.
> > > > 
> > > > We tried using devm_v4l2_sensor_clk_get() and found its required to add 
> > > > support for software_node to make it work with this driver.
> > > 
> > > Why is that ?
> > > 
> > > > Please refer 
> > > > the changes below and let us know if these should be submitted as a 
> > > > separate patch.
> > 
> > The helper is still not merged, so no patch is required.
> > 
> > I will see if a change is needed from the helper side or the OV05C10 side.
> 
> I wonder if there's a better way to figure out if you're running on a DT or
> ACPI based system than getting the device's parents and checking which one
> you find first, DT or ACPI. I think that should work for now at least.

Or, rather, checking for non-OF node here would probably work the best. I
wouldn't expect these to be software node based on DT systems ever.

-- 
Regards,

Sakari Ailus

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24 10:20             ` Sakari Ailus
@ 2025-06-24 10:27               ` Laurent Pinchart
  2025-06-24 11:27                 ` Mehdi Djait
  0 siblings, 1 reply; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-24 10:27 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Mehdi Djait, Nirujogi, Pratap, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, Svetoslav.Stoilov,
	Yana.Zheleva

On Tue, Jun 24, 2025 at 10:20:34AM +0000, Sakari Ailus wrote:
> On Tue, Jun 24, 2025 at 10:19:35AM +0000, Sakari Ailus wrote:
> > On Tue, Jun 24, 2025 at 10:35:18AM +0200, Mehdi Djait wrote:
> > > On Tue, Jun 24, 2025 at 01:05:03AM +0300, Laurent Pinchart wrote:
> > > > On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
> > > > > On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
> > > > > >>> +static int ov05c10_probe(struct i2c_client *client)
> > > > > >>> +{
> > > > > >>> +     struct ov05c10 *ov05c10;
> > > > > >>> +     u32 clkfreq;
> > > > > >>> +     int ret;
> > > > > >>> +
> > > > > >>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), 
> > > > > >>> GFP_KERNEL);
> > > > > >>> +     if (!ov05c10)
> > > > > >>> +             return -ENOMEM;
> > > > > >>> +
> > > > > >>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> > > > > >>> +
> > > > > >>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency", 
> > > > > >>> &clkfreq);
> > > > > >>> +     if (ret)
> > > > > >>> +             return  dev_err_probe(&client->dev, -EINVAL,
> > > > > >>> +                                   "fail to get clock freq\n");
> > > > > >>
> > > > > >> Let's try to land
> > > > > >> https://lore.kernel.org/linux-media/20250521104115.176950-1- 
> > > > > >> mehdi.djait@linux.intel.com/
> > > > > >> and replace the code above with devm_v4l2_sensor_clk_get().
> > > > > >>
> > > > > > Ok, we will verify on our side.
> > > > > 
> > > > > We tried using devm_v4l2_sensor_clk_get() and found its required to add 
> > > > > support for software_node to make it work with this driver.
> > > > 
> > > > Why is that ?
> > > > 
> > > > > Please refer 
> > > > > the changes below and let us know if these should be submitted as a 
> > > > > separate patch.
> > > 
> > > The helper is still not merged, so no patch is required.
> > > 
> > > I will see if a change is needed from the helper side or the OV05C10 side.
> > 
> > I wonder if there's a better way to figure out if you're running on a DT or
> > ACPI based system than getting the device's parents and checking which one
> > you find first, DT or ACPI. I think that should work for now at least.
> 
> Or, rather, checking for non-OF node here would probably work the best. I
> wouldn't expect these to be software node based on DT systems ever.

Until it happens :-) And we'll handle it then.

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24 10:27               ` Laurent Pinchart
@ 2025-06-24 11:27                 ` Mehdi Djait
  2025-06-24 11:33                   ` Laurent Pinchart
                                     ` (2 more replies)
  0 siblings, 3 replies; 60+ messages in thread
From: Mehdi Djait @ 2025-06-24 11:27 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Sakari Ailus, Nirujogi, Pratap, Pratap Nirujogi, mchehab,
	hverkuil, bryan.odonoghue, krzk, dave.stevenson, hdegoede,
	jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	Svetoslav.Stoilov, Yana.Zheleva

Hi Laurent, Hi Sakari,

On Tue, Jun 24, 2025 at 01:27:45PM +0300, Laurent Pinchart wrote:
> On Tue, Jun 24, 2025 at 10:20:34AM +0000, Sakari Ailus wrote:
> > On Tue, Jun 24, 2025 at 10:19:35AM +0000, Sakari Ailus wrote:
> > > On Tue, Jun 24, 2025 at 10:35:18AM +0200, Mehdi Djait wrote:
> > > > On Tue, Jun 24, 2025 at 01:05:03AM +0300, Laurent Pinchart wrote:
> > > > > On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
> > > > > > On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
> > > > > > >>> +static int ov05c10_probe(struct i2c_client *client)
> > > > > > >>> +{
> > > > > > >>> +     struct ov05c10 *ov05c10;
> > > > > > >>> +     u32 clkfreq;
> > > > > > >>> +     int ret;
> > > > > > >>> +
> > > > > > >>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), 
> > > > > > >>> GFP_KERNEL);
> > > > > > >>> +     if (!ov05c10)
> > > > > > >>> +             return -ENOMEM;
> > > > > > >>> +
> > > > > > >>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> > > > > > >>> +
> > > > > > >>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency", 
> > > > > > >>> &clkfreq);
> > > > > > >>> +     if (ret)
> > > > > > >>> +             return  dev_err_probe(&client->dev, -EINVAL,
> > > > > > >>> +                                   "fail to get clock freq\n");
> > > > > > >>
> > > > > > >> Let's try to land
> > > > > > >> https://lore.kernel.org/linux-media/20250521104115.176950-1- 
> > > > > > >> mehdi.djait@linux.intel.com/
> > > > > > >> and replace the code above with devm_v4l2_sensor_clk_get().
> > > > > > >>
> > > > > > > Ok, we will verify on our side.
> > > > > > 
> > > > > > We tried using devm_v4l2_sensor_clk_get() and found its required to add 
> > > > > > support for software_node to make it work with this driver.
> > > > > 
> > > > > Why is that ?
> > > > > 
> > > > > > Please refer 
> > > > > > the changes below and let us know if these should be submitted as a 
> > > > > > separate patch.
> > > > 
> > > > The helper is still not merged, so no patch is required.
> > > > 
> > > > I will see if a change is needed from the helper side or the OV05C10 side.
> > > 
> > > I wonder if there's a better way to figure out if you're running on a DT or
> > > ACPI based system than getting the device's parents and checking which one
> > > you find first, DT or ACPI. I think that should work for now at least.
> > 
> > Or, rather, checking for non-OF node here would probably work the best. I
> > wouldn't expect these to be software node based on DT systems ever.
> 
> Until it happens :-) And we'll handle it then.

So we have the following:

- The problem with this driver is due to lack of proper ACPI
  description. HW is already shipping and AMD will work on better ACPI
  description for future models. See [1]

- software_node can also be used on DT systems

[1] https://lore.kernel.org/lkml/0d801367-da24-4596-83d9-08ccd89ca670@redhat.com/

Now going back to the helper. If we want to support this case:

Approach 1: software_node || acpi

--- a/drivers/media/v4l2-core/v4l2-common.c
+++ b/drivers/media/v4l2-core/v4l2-common.c
@@ -682,16 +682,17 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
        const char *clk_id __free(kfree) = NULL;
        struct clk_hw *clk_hw;
        struct clk *clk;
-       bool acpi_node;
+       bool acpi_sw_node;
        u32 rate;
        int ret;
 
        clk = devm_clk_get_optional(dev, id);
        ret = device_property_read_u32(dev, "clock-frequency", &rate);
-       acpi_node = is_acpi_node(dev_fwnode(dev));
+       acpi_sw_node = is_acpi_node(dev_fwnode(dev)) ||
+                      is_software_node(dev_fwnode(dev));
 
        if (clk) {
-               if (!ret && acpi_node) {
+               if (!ret && acpi_sw_node) {
                        ret = clk_set_rate(clk, rate);
                        if (ret) {
                                dev_err(dev, "Failed to set clock rate: %u\n",
@@ -705,7 +706,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
        if (ret)
                return ERR_PTR(ret);
 
-       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
+       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_sw_node)
                return ERR_PTR(-ENOENT);
 
        if (!id) {


Approach 2: of_node

--- a/drivers/media/v4l2-core/v4l2-common.c
+++ b/drivers/media/v4l2-core/v4l2-common.c
@@ -682,16 +682,16 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
        const char *clk_id __free(kfree) = NULL;
        struct clk_hw *clk_hw;
        struct clk *clk;
-       bool acpi_node;
+       bool of_node;
        u32 rate;
        int ret;
 
        clk = devm_clk_get_optional(dev, id);
        ret = device_property_read_u32(dev, "clock-frequency", &rate);
-       acpi_node = is_acpi_node(dev_fwnode(dev));
+       of_node = is_of_node(dev_fwnode(dev));
 
        if (clk) {
-               if (!ret && acpi_node) {
+               if (!ret && !of_node) {
                        ret = clk_set_rate(clk, rate);
                        if (ret) {
                                dev_err(dev, "Failed to set clock rate: %u\n",
@@ -705,7 +705,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
        if (ret)
                return ERR_PTR(ret);
 
-       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
+       if (!IS_ENABLED(CONFIG_COMMON_CLK) || of_node)
                return ERR_PTR(-ENOENT);
 
        if (!id) {


--
Kind Regards
Mehdi Djait

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24 11:27                 ` Mehdi Djait
@ 2025-06-24 11:33                   ` Laurent Pinchart
  2025-06-24 11:46                   ` Sakari Ailus
  2025-06-24 18:26                   ` Nirujogi, Pratap
  2 siblings, 0 replies; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-24 11:33 UTC (permalink / raw)
  To: Mehdi Djait
  Cc: Sakari Ailus, Nirujogi, Pratap, Pratap Nirujogi, mchehab,
	hverkuil, bryan.odonoghue, krzk, dave.stevenson, hdegoede,
	jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	Svetoslav.Stoilov, Yana.Zheleva

On Tue, Jun 24, 2025 at 01:27:03PM +0200, Mehdi Djait wrote:
> On Tue, Jun 24, 2025 at 01:27:45PM +0300, Laurent Pinchart wrote:
> > On Tue, Jun 24, 2025 at 10:20:34AM +0000, Sakari Ailus wrote:
> > > On Tue, Jun 24, 2025 at 10:19:35AM +0000, Sakari Ailus wrote:
> > > > On Tue, Jun 24, 2025 at 10:35:18AM +0200, Mehdi Djait wrote:
> > > > > On Tue, Jun 24, 2025 at 01:05:03AM +0300, Laurent Pinchart wrote:
> > > > > > On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
> > > > > > > On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
> > > > > > > >>> +static int ov05c10_probe(struct i2c_client *client)
> > > > > > > >>> +{
> > > > > > > >>> +     struct ov05c10 *ov05c10;
> > > > > > > >>> +     u32 clkfreq;
> > > > > > > >>> +     int ret;
> > > > > > > >>> +
> > > > > > > >>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), 
> > > > > > > >>> GFP_KERNEL);
> > > > > > > >>> +     if (!ov05c10)
> > > > > > > >>> +             return -ENOMEM;
> > > > > > > >>> +
> > > > > > > >>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> > > > > > > >>> +
> > > > > > > >>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency", 
> > > > > > > >>> &clkfreq);
> > > > > > > >>> +     if (ret)
> > > > > > > >>> +             return  dev_err_probe(&client->dev, -EINVAL,
> > > > > > > >>> +                                   "fail to get clock freq\n");
> > > > > > > >>
> > > > > > > >> Let's try to land
> > > > > > > >> https://lore.kernel.org/linux-media/20250521104115.176950-1- 
> > > > > > > >> mehdi.djait@linux.intel.com/
> > > > > > > >> and replace the code above with devm_v4l2_sensor_clk_get().
> > > > > > > >>
> > > > > > > > Ok, we will verify on our side.
> > > > > > > 
> > > > > > > We tried using devm_v4l2_sensor_clk_get() and found its required to add 
> > > > > > > support for software_node to make it work with this driver.
> > > > > > 
> > > > > > Why is that ?
> > > > > > 
> > > > > > > Please refer 
> > > > > > > the changes below and let us know if these should be submitted as a 
> > > > > > > separate patch.
> > > > > 
> > > > > The helper is still not merged, so no patch is required.
> > > > > 
> > > > > I will see if a change is needed from the helper side or the OV05C10 side.
> > > > 
> > > > I wonder if there's a better way to figure out if you're running on a DT or
> > > > ACPI based system than getting the device's parents and checking which one
> > > > you find first, DT or ACPI. I think that should work for now at least.
> > > 
> > > Or, rather, checking for non-OF node here would probably work the best. I
> > > wouldn't expect these to be software node based on DT systems ever.
> > 
> > Until it happens :-) And we'll handle it then.
> 
> So we have the following:
> 
> - The problem with this driver is due to lack of proper ACPI
>   description. HW is already shipping and AMD will work on better ACPI
>   description for future models. See [1]

If that's the same way that Intel said during the IPU3 days they would
work on better ACPI description for future models, I think the IPU6
shows us how that went :-)

> - software_node can also be used on DT systems

They can, but for this purpose, they really shouldn't. That's why I
prefer approach two, we shouldn't enable a mechanism that nobody should
be using on OF platforms.

> [1] https://lore.kernel.org/lkml/0d801367-da24-4596-83d9-08ccd89ca670@redhat.com/
> 
> Now going back to the helper. If we want to support this case:
> 
> Approach 1: software_node || acpi
> 
> --- a/drivers/media/v4l2-core/v4l2-common.c
> +++ b/drivers/media/v4l2-core/v4l2-common.c
> @@ -682,16 +682,17 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
>         const char *clk_id __free(kfree) = NULL;
>         struct clk_hw *clk_hw;
>         struct clk *clk;
> -       bool acpi_node;
> +       bool acpi_sw_node;
>         u32 rate;
>         int ret;
>  
>         clk = devm_clk_get_optional(dev, id);
>         ret = device_property_read_u32(dev, "clock-frequency", &rate);
> -       acpi_node = is_acpi_node(dev_fwnode(dev));
> +       acpi_sw_node = is_acpi_node(dev_fwnode(dev)) ||
> +                      is_software_node(dev_fwnode(dev));
>  
>         if (clk) {
> -               if (!ret && acpi_node) {
> +               if (!ret && acpi_sw_node) {
>                         ret = clk_set_rate(clk, rate);
>                         if (ret) {
>                                 dev_err(dev, "Failed to set clock rate: %u\n",
> @@ -705,7 +706,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
>         if (ret)
>                 return ERR_PTR(ret);
>  
> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_sw_node)
>                 return ERR_PTR(-ENOENT);
>  
>         if (!id) {
> 
> 
> Approach 2: of_node
> 
> --- a/drivers/media/v4l2-core/v4l2-common.c
> +++ b/drivers/media/v4l2-core/v4l2-common.c
> @@ -682,16 +682,16 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
>         const char *clk_id __free(kfree) = NULL;
>         struct clk_hw *clk_hw;
>         struct clk *clk;
> -       bool acpi_node;
> +       bool of_node;
>         u32 rate;
>         int ret;
>  
>         clk = devm_clk_get_optional(dev, id);
>         ret = device_property_read_u32(dev, "clock-frequency", &rate);
> -       acpi_node = is_acpi_node(dev_fwnode(dev));
> +       of_node = is_of_node(dev_fwnode(dev));
>  
>         if (clk) {
> -               if (!ret && acpi_node) {
> +               if (!ret && !of_node) {
>                         ret = clk_set_rate(clk, rate);
>                         if (ret) {
>                                 dev_err(dev, "Failed to set clock rate: %u\n",
> @@ -705,7 +705,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
>         if (ret)
>                 return ERR_PTR(ret);
>  
> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || of_node)
>                 return ERR_PTR(-ENOENT);
>  
>         if (!id) {

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24 11:27                 ` Mehdi Djait
  2025-06-24 11:33                   ` Laurent Pinchart
@ 2025-06-24 11:46                   ` Sakari Ailus
  2025-06-24 16:34                     ` Mehdi Djait
  2025-06-24 18:26                   ` Nirujogi, Pratap
  2 siblings, 1 reply; 60+ messages in thread
From: Sakari Ailus @ 2025-06-24 11:46 UTC (permalink / raw)
  To: Mehdi Djait
  Cc: Laurent Pinchart, Nirujogi, Pratap, Pratap Nirujogi, mchehab,
	hverkuil, bryan.odonoghue, krzk, dave.stevenson, hdegoede,
	jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	Svetoslav.Stoilov, Yana.Zheleva

Hi Mehdi,

On Tue, Jun 24, 2025 at 01:27:03PM +0200, Mehdi Djait wrote:
> Hi Laurent, Hi Sakari,
> 
> On Tue, Jun 24, 2025 at 01:27:45PM +0300, Laurent Pinchart wrote:
> > On Tue, Jun 24, 2025 at 10:20:34AM +0000, Sakari Ailus wrote:
> > > On Tue, Jun 24, 2025 at 10:19:35AM +0000, Sakari Ailus wrote:
> > > > On Tue, Jun 24, 2025 at 10:35:18AM +0200, Mehdi Djait wrote:
> > > > > On Tue, Jun 24, 2025 at 01:05:03AM +0300, Laurent Pinchart wrote:
> > > > > > On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
> > > > > > > On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
> > > > > > > >>> +static int ov05c10_probe(struct i2c_client *client)
> > > > > > > >>> +{
> > > > > > > >>> +     struct ov05c10 *ov05c10;
> > > > > > > >>> +     u32 clkfreq;
> > > > > > > >>> +     int ret;
> > > > > > > >>> +
> > > > > > > >>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10), 
> > > > > > > >>> GFP_KERNEL);
> > > > > > > >>> +     if (!ov05c10)
> > > > > > > >>> +             return -ENOMEM;
> > > > > > > >>> +
> > > > > > > >>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> > > > > > > >>> +
> > > > > > > >>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency", 
> > > > > > > >>> &clkfreq);
> > > > > > > >>> +     if (ret)
> > > > > > > >>> +             return  dev_err_probe(&client->dev, -EINVAL,
> > > > > > > >>> +                                   "fail to get clock freq\n");
> > > > > > > >>
> > > > > > > >> Let's try to land
> > > > > > > >> https://lore.kernel.org/linux-media/20250521104115.176950-1- 
> > > > > > > >> mehdi.djait@linux.intel.com/
> > > > > > > >> and replace the code above with devm_v4l2_sensor_clk_get().
> > > > > > > >>
> > > > > > > > Ok, we will verify on our side.
> > > > > > > 
> > > > > > > We tried using devm_v4l2_sensor_clk_get() and found its required to add 
> > > > > > > support for software_node to make it work with this driver.
> > > > > > 
> > > > > > Why is that ?
> > > > > > 
> > > > > > > Please refer 
> > > > > > > the changes below and let us know if these should be submitted as a 
> > > > > > > separate patch.
> > > > > 
> > > > > The helper is still not merged, so no patch is required.
> > > > > 
> > > > > I will see if a change is needed from the helper side or the OV05C10 side.
> > > > 
> > > > I wonder if there's a better way to figure out if you're running on a DT or
> > > > ACPI based system than getting the device's parents and checking which one
> > > > you find first, DT or ACPI. I think that should work for now at least.
> > > 
> > > Or, rather, checking for non-OF node here would probably work the best. I
> > > wouldn't expect these to be software node based on DT systems ever.
> > 
> > Until it happens :-) And we'll handle it then.
> 
> So we have the following:
> 
> - The problem with this driver is due to lack of proper ACPI
>   description. HW is already shipping and AMD will work on better ACPI
>   description for future models. See [1]
> 
> - software_node can also be used on DT systems
> 
> [1] https://lore.kernel.org/lkml/0d801367-da24-4596-83d9-08ccd89ca670@redhat.com/
> 
> Now going back to the helper. If we want to support this case:
> 
> Approach 1: software_node || acpi
> 
> --- a/drivers/media/v4l2-core/v4l2-common.c
> +++ b/drivers/media/v4l2-core/v4l2-common.c
> @@ -682,16 +682,17 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
>         const char *clk_id __free(kfree) = NULL;
>         struct clk_hw *clk_hw;
>         struct clk *clk;
> -       bool acpi_node;
> +       bool acpi_sw_node;
>         u32 rate;
>         int ret;
>  
>         clk = devm_clk_get_optional(dev, id);
>         ret = device_property_read_u32(dev, "clock-frequency", &rate);
> -       acpi_node = is_acpi_node(dev_fwnode(dev));
> +       acpi_sw_node = is_acpi_node(dev_fwnode(dev)) ||
> +                      is_software_node(dev_fwnode(dev));
>  
>         if (clk) {
> -               if (!ret && acpi_node) {
> +               if (!ret && acpi_sw_node) {
>                         ret = clk_set_rate(clk, rate);
>                         if (ret) {
>                                 dev_err(dev, "Failed to set clock rate: %u\n",
> @@ -705,7 +706,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
>         if (ret)
>                 return ERR_PTR(ret);
>  
> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_sw_node)
>                 return ERR_PTR(-ENOENT);
>  
>         if (!id) {
> 
> 
> Approach 2: of_node
> 
> --- a/drivers/media/v4l2-core/v4l2-common.c
> +++ b/drivers/media/v4l2-core/v4l2-common.c
> @@ -682,16 +682,16 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
>         const char *clk_id __free(kfree) = NULL;
>         struct clk_hw *clk_hw;
>         struct clk *clk;
> -       bool acpi_node;
> +       bool of_node;
>         u32 rate;
>         int ret;
>  
>         clk = devm_clk_get_optional(dev, id);
>         ret = device_property_read_u32(dev, "clock-frequency", &rate);
> -       acpi_node = is_acpi_node(dev_fwnode(dev));
> +       of_node = is_of_node(dev_fwnode(dev));
>  
>         if (clk) {
> -               if (!ret && acpi_node) {
> +               if (!ret && !of_node) {
>                         ret = clk_set_rate(clk, rate);
>                         if (ret) {
>                                 dev_err(dev, "Failed to set clock rate: %u\n",
> @@ -705,7 +705,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
>         if (ret)
>                 return ERR_PTR(ret);
>  
> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || of_node)
>                 return ERR_PTR(-ENOENT);
>  
>         if (!id) {

I'm in favour of the latter but both should be workable.

Speaking of return values, devm_clk_get_optional() may also return
-EPROBE_DEFER. That needs to be handled.

And further on -EPROBE_DEFER, I think the helper should return
-EPROBE_DEFER if the "clock-frequency" property doesn't exist on non-OF
nodes. That signals the required software nodes required on Intel Windows
definitions/ipu-bridge or AMD systems aren't in place yet so really probing
should be deferred. This would allow removing the hacks that return
-EPROBE_DEFER in sensor drivers when no graph endpoint is found.

-- 
Regards,

Sakari Ailus

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24 11:46                   ` Sakari Ailus
@ 2025-06-24 16:34                     ` Mehdi Djait
  2025-06-24 20:24                       ` Nirujogi, Pratap
  2025-06-25  6:11                       ` Sakari Ailus
  0 siblings, 2 replies; 60+ messages in thread
From: Mehdi Djait @ 2025-06-24 16:34 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Laurent Pinchart, Nirujogi, Pratap, Pratap Nirujogi, mchehab,
	hverkuil, bryan.odonoghue, krzk, dave.stevenson, hdegoede,
	jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	Svetoslav.Stoilov, Yana.Zheleva

Hi Sakari,

On Tue, Jun 24, 2025 at 11:46:27AM +0000, Sakari Ailus wrote:

[...]

> 
> I'm in favour of the latter but both should be workable.
> 
> Speaking of return values, devm_clk_get_optional() may also return
> -EPROBE_DEFER. That needs to be handled.
> 

Ack.

> And further on -EPROBE_DEFER, I think the helper should return
> -EPROBE_DEFER if the "clock-frequency" property doesn't exist on non-OF
> nodes. That signals the required software nodes required on Intel Windows
> definitions/ipu-bridge or AMD systems aren't in place yet so really probing
> should be deferred. This would allow removing the hacks that return
> -EPROBE_DEFER in sensor drivers when no graph endpoint is found.

device_property_read_u32() returns the following:

 * Return: number of values if @val was %NULL,
 *         %0 if the property was found (success),
 *	   %-EINVAL if given arguments are not valid,
 *	   %-ENODATA if the property does not have a value,
 *	   %-EPROTO if the property is not an array of numbers,
 *	   %-EOVERFLOW if the size of the property is not as expected.
 *	   %-ENXIO if no suitable firmware interface is present.


Do you mean something like this in the helper:

if (ret == -ENODATA && !of_node)
	return ERR_PTR(-EPROBE_DEFER);

--
Kind Regards
Mehdi Djait

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24 11:27                 ` Mehdi Djait
  2025-06-24 11:33                   ` Laurent Pinchart
  2025-06-24 11:46                   ` Sakari Ailus
@ 2025-06-24 18:26                   ` Nirujogi, Pratap
  2025-06-24 20:01                     ` Nirujogi, Pratap
  2 siblings, 1 reply; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-24 18:26 UTC (permalink / raw)
  To: Mehdi Djait, Laurent Pinchart
  Cc: Sakari Ailus, Pratap Nirujogi, mchehab, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, Svetoslav.Stoilov, Yana.Zheleva

Hi Mehdi, Sakari, Laurent,

On 6/24/2025 7:27 AM, Mehdi Djait wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> Hi Laurent, Hi Sakari,
> 
> On Tue, Jun 24, 2025 at 01:27:45PM +0300, Laurent Pinchart wrote:
>> On Tue, Jun 24, 2025 at 10:20:34AM +0000, Sakari Ailus wrote:
>>> On Tue, Jun 24, 2025 at 10:19:35AM +0000, Sakari Ailus wrote:
>>>> On Tue, Jun 24, 2025 at 10:35:18AM +0200, Mehdi Djait wrote:
>>>>> On Tue, Jun 24, 2025 at 01:05:03AM +0300, Laurent Pinchart wrote:
>>>>>> On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
>>>>>>> On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
>>>>>>>>>> +static int ov05c10_probe(struct i2c_client *client)
>>>>>>>>>> +{
>>>>>>>>>> +     struct ov05c10 *ov05c10;
>>>>>>>>>> +     u32 clkfreq;
>>>>>>>>>> +     int ret;
>>>>>>>>>> +
>>>>>>>>>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10),
>>>>>>>>>> GFP_KERNEL);
>>>>>>>>>> +     if (!ov05c10)
>>>>>>>>>> +             return -ENOMEM;
>>>>>>>>>> +
>>>>>>>>>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
>>>>>>>>>> +
>>>>>>>>>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency",
>>>>>>>>>> &clkfreq);
>>>>>>>>>> +     if (ret)
>>>>>>>>>> +             return  dev_err_probe(&client->dev, -EINVAL,
>>>>>>>>>> +                                   "fail to get clock freq\n");
>>>>>>>>>
>>>>>>>>> Let's try to land
>>>>>>>>> https://lore.kernel.org/linux-media/20250521104115.176950-1-
>>>>>>>>> mehdi.djait@linux.intel.com/
>>>>>>>>> and replace the code above with devm_v4l2_sensor_clk_get().
>>>>>>>>>
>>>>>>>> Ok, we will verify on our side.
>>>>>>>
>>>>>>> We tried using devm_v4l2_sensor_clk_get() and found its required to add
>>>>>>> support for software_node to make it work with this driver.
>>>>>>
>>>>>> Why is that ?
>>>>>>
>>>>>>> Please refer
>>>>>>> the changes below and let us know if these should be submitted as a
>>>>>>> separate patch.
>>>>>
>>>>> The helper is still not merged, so no patch is required.
>>>>>
>>>>> I will see if a change is needed from the helper side or the OV05C10 side.
>>>>
>>>> I wonder if there's a better way to figure out if you're running on a DT or
>>>> ACPI based system than getting the device's parents and checking which one
>>>> you find first, DT or ACPI. I think that should work for now at least.
>>>
>>> Or, rather, checking for non-OF node here would probably work the best. I
>>> wouldn't expect these to be software node based on DT systems ever.
>>
>> Until it happens :-) And we'll handle it then.
> 
> So we have the following:
> 
> - The problem with this driver is due to lack of proper ACPI
>    description. HW is already shipping and AMD will work on better ACPI
>    description for future models. See [1]
> 
Thanks Mehdi for clarifying and providing the reference from the 
associated x86/platform driver patch.

yes, thats true we have to add software_nodes to mitigate the issue 
caused by incomplete description of camera device in ACPI tables.

For future models we are working on a plan to address this issue 
following the MIPI DisCo Imaging Spec suggested by Sakari to properly 
describe the camera device in ACPI. Please see [A]

Once again thanks everyone for the support!

[A] 
https://lore.kernel.org/lkml/2a9ba94e-7985-4ba9-88c6-45b8cf4d001f@amd.com/

> - software_node can also be used on DT systems
> 
> [1] https://lore.kernel.org/lkml/0d801367-da24-4596-83d9-08ccd89ca670@redhat.com/
> 
> Now going back to the helper. If we want to support this case:
> 
> Approach 1: software_node || acpi
> 
> --- a/drivers/media/v4l2-core/v4l2-common.c
> +++ b/drivers/media/v4l2-core/v4l2-common.c
> @@ -682,16 +682,17 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
>          const char *clk_id __free(kfree) = NULL;
>          struct clk_hw *clk_hw;
>          struct clk *clk;
> -       bool acpi_node;
> +       bool acpi_sw_node;
>          u32 rate;
>          int ret;
> 
>          clk = devm_clk_get_optional(dev, id);
>          ret = device_property_read_u32(dev, "clock-frequency", &rate);
> -       acpi_node = is_acpi_node(dev_fwnode(dev));
> +       acpi_sw_node = is_acpi_node(dev_fwnode(dev)) ||
> +                      is_software_node(dev_fwnode(dev));
> 
>          if (clk) {
> -               if (!ret && acpi_node) {
> +               if (!ret && acpi_sw_node) {
>                          ret = clk_set_rate(clk, rate);
>                          if (ret) {
>                                  dev_err(dev, "Failed to set clock rate: %u\n",
> @@ -705,7 +706,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
>          if (ret)
>                  return ERR_PTR(ret);
> 
> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_sw_node)
>                  return ERR_PTR(-ENOENT);
> 
>          if (!id) {
> 
> 
> Approach 2: of_node
> 
> --- a/drivers/media/v4l2-core/v4l2-common.c
> +++ b/drivers/media/v4l2-core/v4l2-common.c
> @@ -682,16 +682,16 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
>          const char *clk_id __free(kfree) = NULL;
>          struct clk_hw *clk_hw;
>          struct clk *clk;
> -       bool acpi_node;
> +       bool of_node;
>          u32 rate;
>          int ret;
> 
>          clk = devm_clk_get_optional(dev, id);
>          ret = device_property_read_u32(dev, "clock-frequency", &rate);
> -       acpi_node = is_acpi_node(dev_fwnode(dev));
> +       of_node = is_of_node(dev_fwnode(dev));
> 
>          if (clk) {
> -               if (!ret && acpi_node) {
> +               if (!ret && !of_node) {
>                          ret = clk_set_rate(clk, rate);
>                          if (ret) {
>                                  dev_err(dev, "Failed to set clock rate: %u\n",
> @@ -705,7 +705,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device *dev, const char *id)
>          if (ret)
>                  return ERR_PTR(ret);
> 
> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || of_node)
>                  return ERR_PTR(-ENOENT);
> 
>          if (!id) {
> 
Thanks for proposing "approach 2 using !swnode", I verified and confirm 
it works at my end.

Thanks,
Pratap
> 
> --
> Kind Regards
> Mehdi Djait


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24  0:19           ` Laurent Pinchart
@ 2025-06-24 18:49             ` Nirujogi, Pratap
  2025-06-24 19:00               ` Laurent Pinchart
  0 siblings, 1 reply; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-24 18:49 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Pratap Nirujogi, mchehab, sakari.ailus, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, Svetoslav.Stoilov, Yana.Zheleva,
	Mehdi Djait

Hi Laurent,

On 6/23/2025 8:19 PM, Laurent Pinchart wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> On Mon, Jun 23, 2025 at 07:28:46PM -0400, Nirujogi, Pratap wrote:
>> On 6/23/2025 6:05 PM, Laurent Pinchart wrote:
>>> On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
>>>> On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
>>>>>>> +static int ov05c10_probe(struct i2c_client *client)
>>>>>>> +{
>>>>>>> +     struct ov05c10 *ov05c10;
>>>>>>> +     u32 clkfreq;
>>>>>>> +     int ret;
>>>>>>> +
>>>>>>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10),
>>>>>>> GFP_KERNEL);
>>>>>>> +     if (!ov05c10)
>>>>>>> +             return -ENOMEM;
>>>>>>> +
>>>>>>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
>>>>>>> +
>>>>>>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency",
>>>>>>> &clkfreq);
>>>>>>> +     if (ret)
>>>>>>> +             return  dev_err_probe(&client->dev, -EINVAL,
>>>>>>> +                                   "fail to get clock freq\n");
>>>>>>
>>>>>> Let's try to land
>>>>>> https://lore.kernel.org/linux-media/20250521104115.176950-1-
>>>>>> mehdi.djait@linux.intel.com/
>>>>>> and replace the code above with devm_v4l2_sensor_clk_get().
>>>>>>
>>>>> Ok, we will verify on our side.
>>>>
>>>> We tried using devm_v4l2_sensor_clk_get() and found its required to add
>>>> support for software_node to make it work with this driver.
>>>
>>> Why is that ?
>>
>> Its because the i2c_client device is initialized with swnode in the
>> x86/platform driver.
>>
>> https://github.com/torvalds/linux/blob/master/drivers/platform/x86/amd/amd_isp4.c#L235
> 
> So there's no information provided in the _DSD for the sensor ?
> 
yes, camera device was not properly described in the current model, we 
are going to address this issue for future models following the MIPI 
DisCo Imaging spec.

> Looking at that platform driver, it matches the device based on the
> sensor ACPI HID only ("OMNI5C10"). That doesn't seem quite right, I
> think you need a DMI match as well. You can't assume that OMNI5C10,
> which identifies the sensor, will always map to specific platform
> integration data (connected to an AMD ISP, using a particular link
> frequency, ...), can you ?
> 
Initally we had dmi checks, but as the driver matured through review 
iterations, we switched to ACPI driver approach and felt the bus 
traversal to find the matching HID device and dmi checks are no longer 
required. The (_HID, "OMNI5C10") used is specific to this platform and 
shouldn't be impacting other platform even though the dmi checks are 
skipped. Please see [A].

Thanks,
Pratap

[A] 
https://lore.kernel.org/lkml/8d892845-e134-4553-a6af-55d785c1ae98@amd.com/

>>>> Please refer
>>>> the changes below and let us know if these should be submitted as a
>>>> separate patch.
>>>
>>> Mehdi, do you have any comment ?
>>>
>>>> ---
>>>> @@ -645,16 +645,16 @@ struct clk *devm_v4l2_sensor_clk_get(struct device
>>>> *dev, const char *id)
>>>>            const char *clk_id __free(kfree) = NULL;
>>>>            struct clk_hw *clk_hw;
>>>>            struct clk *clk;
>>>> -       bool acpi_node;
>>>> +       bool is_node;
>>>>            u32 rate;
>>>>            int ret;
>>>>
>>>>            clk = devm_clk_get_optional(dev, id);
>>>>            ret = device_property_read_u32(dev, "clock-frequency", &rate);
>>>> -       acpi_node = is_acpi_node(dev_fwnode(dev));
>>>> +       is_node = is_acpi_node(dev_fwnode(dev)) || is_software_node(dev_fwnode(dev));
>>>>
>>>>            if (clk) {
>>>> -               if (!ret && acpi_node) {
>>>> +               if (!ret && is_node) {
>>>>                            ret = clk_set_rate(clk, rate);
>>>>                            if (ret) {
>>>>                                    dev_err(dev, "Failed to set clock rate: %u\n",
>>>> @@ -668,7 +668,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device
>>>> *dev, const char *id)
>>>>            if (ret)
>>>>                    return ERR_PTR(ret);
>>>>
>>>> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
>>>> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !is_node)
>>>>                    return ERR_PTR(-ENOENT);
>>>>
>>>>            if (!id) {
>>>> ----
> 
> --
> Regards,
> 
> Laurent Pinchart


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24 18:49             ` Nirujogi, Pratap
@ 2025-06-24 19:00               ` Laurent Pinchart
  2025-06-24 19:49                 ` Nirujogi, Pratap
  0 siblings, 1 reply; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-24 19:00 UTC (permalink / raw)
  To: Nirujogi, Pratap
  Cc: Pratap Nirujogi, mchehab, sakari.ailus, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, Svetoslav.Stoilov, Yana.Zheleva,
	Mehdi Djait

On Tue, Jun 24, 2025 at 02:49:36PM -0400, Nirujogi, Pratap wrote:
> On 6/23/2025 8:19 PM, Laurent Pinchart wrote:
> > On Mon, Jun 23, 2025 at 07:28:46PM -0400, Nirujogi, Pratap wrote:
> >> On 6/23/2025 6:05 PM, Laurent Pinchart wrote:
> >>> On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
> >>>> On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
> >>>>>>> +static int ov05c10_probe(struct i2c_client *client)
> >>>>>>> +{
> >>>>>>> +     struct ov05c10 *ov05c10;
> >>>>>>> +     u32 clkfreq;
> >>>>>>> +     int ret;
> >>>>>>> +
> >>>>>>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10),
> >>>>>>> GFP_KERNEL);
> >>>>>>> +     if (!ov05c10)
> >>>>>>> +             return -ENOMEM;
> >>>>>>> +
> >>>>>>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
> >>>>>>> +
> >>>>>>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency",
> >>>>>>> &clkfreq);
> >>>>>>> +     if (ret)
> >>>>>>> +             return  dev_err_probe(&client->dev, -EINVAL,
> >>>>>>> +                                   "fail to get clock freq\n");
> >>>>>>
> >>>>>> Let's try to land
> >>>>>> https://lore.kernel.org/linux-media/20250521104115.176950-1-
> >>>>>> mehdi.djait@linux.intel.com/
> >>>>>> and replace the code above with devm_v4l2_sensor_clk_get().
> >>>>>>
> >>>>> Ok, we will verify on our side.
> >>>>
> >>>> We tried using devm_v4l2_sensor_clk_get() and found its required to add
> >>>> support for software_node to make it work with this driver.
> >>>
> >>> Why is that ?
> >>
> >> Its because the i2c_client device is initialized with swnode in the
> >> x86/platform driver.
> >>
> >> https://github.com/torvalds/linux/blob/master/drivers/platform/x86/amd/amd_isp4.c#L235
> > 
> > So there's no information provided in the _DSD for the sensor ?
> 
> yes, camera device was not properly described in the current model, we 
> are going to address this issue for future models following the MIPI 
> DisCo Imaging spec.

Any idea how far in the future that will be ? I suppose it won't be done
overnight, how many new machine models will get to the market with a
need for a platform driver ?

Will the Windows driver team also switch to MIPI DisCo Imaging ?

> > Looking at that platform driver, it matches the device based on the
> > sensor ACPI HID only ("OMNI5C10"). That doesn't seem quite right, I
> > think you need a DMI match as well. You can't assume that OMNI5C10,
> > which identifies the sensor, will always map to specific platform
> > integration data (connected to an AMD ISP, using a particular link
> > frequency, ...), can you ?
> 
> Initally we had dmi checks, but as the driver matured through review 
> iterations, we switched to ACPI driver approach and felt the bus 
> traversal to find the matching HID device and dmi checks are no longer 
> required. The (_HID, "OMNI5C10") used is specific to this platform and 
> shouldn't be impacting other platform even though the dmi checks are 
> skipped. Please see [A].

How do you guarantee that the same sensor will not be used with the
OMNI5C10 ACPI HID on another AMD-based laptop that would require a
different link frequency or use a different number of data lanes ?

> [A] https://lore.kernel.org/lkml/8d892845-e134-4553-a6af-55d785c1ae98@amd.com/
> 
> >>>> Please refer
> >>>> the changes below and let us know if these should be submitted as a
> >>>> separate patch.
> >>>
> >>> Mehdi, do you have any comment ?
> >>>
> >>>> ---
> >>>> @@ -645,16 +645,16 @@ struct clk *devm_v4l2_sensor_clk_get(struct device
> >>>> *dev, const char *id)
> >>>>            const char *clk_id __free(kfree) = NULL;
> >>>>            struct clk_hw *clk_hw;
> >>>>            struct clk *clk;
> >>>> -       bool acpi_node;
> >>>> +       bool is_node;
> >>>>            u32 rate;
> >>>>            int ret;
> >>>>
> >>>>            clk = devm_clk_get_optional(dev, id);
> >>>>            ret = device_property_read_u32(dev, "clock-frequency", &rate);
> >>>> -       acpi_node = is_acpi_node(dev_fwnode(dev));
> >>>> +       is_node = is_acpi_node(dev_fwnode(dev)) || is_software_node(dev_fwnode(dev));
> >>>>
> >>>>            if (clk) {
> >>>> -               if (!ret && acpi_node) {
> >>>> +               if (!ret && is_node) {
> >>>>                            ret = clk_set_rate(clk, rate);
> >>>>                            if (ret) {
> >>>>                                    dev_err(dev, "Failed to set clock rate: %u\n",
> >>>> @@ -668,7 +668,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device
> >>>> *dev, const char *id)
> >>>>            if (ret)
> >>>>                    return ERR_PTR(ret);
> >>>>
> >>>> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
> >>>> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !is_node)
> >>>>                    return ERR_PTR(-ENOENT);
> >>>>
> >>>>            if (!id) {
> >>>> ----

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24 19:00               ` Laurent Pinchart
@ 2025-06-24 19:49                 ` Nirujogi, Pratap
  0 siblings, 0 replies; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-24 19:49 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Pratap Nirujogi, mchehab, sakari.ailus, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, Svetoslav.Stoilov, Yana.Zheleva,
	Mehdi Djait

Hi Laurent,

On 6/24/2025 3:00 PM, Laurent Pinchart wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> On Tue, Jun 24, 2025 at 02:49:36PM -0400, Nirujogi, Pratap wrote:
>> On 6/23/2025 8:19 PM, Laurent Pinchart wrote:
>>> On Mon, Jun 23, 2025 at 07:28:46PM -0400, Nirujogi, Pratap wrote:
>>>> On 6/23/2025 6:05 PM, Laurent Pinchart wrote:
>>>>> On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
>>>>>> On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
>>>>>>>>> +static int ov05c10_probe(struct i2c_client *client)
>>>>>>>>> +{
>>>>>>>>> +     struct ov05c10 *ov05c10;
>>>>>>>>> +     u32 clkfreq;
>>>>>>>>> +     int ret;
>>>>>>>>> +
>>>>>>>>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10),
>>>>>>>>> GFP_KERNEL);
>>>>>>>>> +     if (!ov05c10)
>>>>>>>>> +             return -ENOMEM;
>>>>>>>>> +
>>>>>>>>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
>>>>>>>>> +
>>>>>>>>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency",
>>>>>>>>> &clkfreq);
>>>>>>>>> +     if (ret)
>>>>>>>>> +             return  dev_err_probe(&client->dev, -EINVAL,
>>>>>>>>> +                                   "fail to get clock freq\n");
>>>>>>>>
>>>>>>>> Let's try to land
>>>>>>>> https://lore.kernel.org/linux-media/20250521104115.176950-1-
>>>>>>>> mehdi.djait@linux.intel.com/
>>>>>>>> and replace the code above with devm_v4l2_sensor_clk_get().
>>>>>>>>
>>>>>>> Ok, we will verify on our side.
>>>>>>
>>>>>> We tried using devm_v4l2_sensor_clk_get() and found its required to add
>>>>>> support for software_node to make it work with this driver.
>>>>>
>>>>> Why is that ?
>>>>
>>>> Its because the i2c_client device is initialized with swnode in the
>>>> x86/platform driver.
>>>>
>>>> https://github.com/torvalds/linux/blob/master/drivers/platform/x86/amd/amd_isp4.c#L235
>>>
>>> So there's no information provided in the _DSD for the sensor ?
>>
>> yes, camera device was not properly described in the current model, we
>> are going to address this issue for future models following the MIPI
>> DisCo Imaging spec.
> 
> Any idea how far in the future that will be ? I suppose it won't be done
> overnight, how many new machine models will get to the market with a
> need for a platform driver ?
> 
We are aiming to have MIPI DisCo Imaging ready for models based on 
ISP5.x. However, I don't have specific information on when the ISP5.x 
based APU models will be released at this point of time. We may use the 
current mitigation using the platform driver for the launches that are 
based on ISP4.x. But, to be honest, I don't have visibility into future 
ISP4.x launches as well.

> Will the Windows driver team also switch to MIPI DisCo Imaging ?
> 
yes, even the windows team intends to switch to MIPI DisCo Imaging, they 
shall need to coordinate with MSFT to finalize the plan.

Thanks,
Pratap

>>> Looking at that platform driver, it matches the device based on the
>>> sensor ACPI HID only ("OMNI5C10"). That doesn't seem quite right, I
>>> think you need a DMI match as well. You can't assume that OMNI5C10,
>>> which identifies the sensor, will always map to specific platform
>>> integration data (connected to an AMD ISP, using a particular link
>>> frequency, ...), can you ?
>>
>> Initally we had dmi checks, but as the driver matured through review
>> iterations, we switched to ACPI driver approach and felt the bus
>> traversal to find the matching HID device and dmi checks are no longer
>> required. The (_HID, "OMNI5C10") used is specific to this platform and
>> shouldn't be impacting other platform even though the dmi checks are
>> skipped. Please see [A].
> 
> How do you guarantee that the same sensor will not be used with the
> OMNI5C10 ACPI HID on another AMD-based laptop that would require a
> different link frequency or use a different number of data lanes ?
> 
Currently, no such scenario exists AFAIK. OMNI5C10 is tied to the models 
based on "AMD RYZEN AI MAX PRO 385 w/ Radeon 8050S". We can for sure add 
the DMI checks if really necessary for the current or future models.

Thanks,
Pratap

>> [A] https://lore.kernel.org/lkml/8d892845-e134-4553-a6af-55d785c1ae98@amd.com/
>>
>>>>>> Please refer
>>>>>> the changes below and let us know if these should be submitted as a
>>>>>> separate patch.
>>>>>
>>>>> Mehdi, do you have any comment ?
>>>>>
>>>>>> ---
>>>>>> @@ -645,16 +645,16 @@ struct clk *devm_v4l2_sensor_clk_get(struct device
>>>>>> *dev, const char *id)
>>>>>>             const char *clk_id __free(kfree) = NULL;
>>>>>>             struct clk_hw *clk_hw;
>>>>>>             struct clk *clk;
>>>>>> -       bool acpi_node;
>>>>>> +       bool is_node;
>>>>>>             u32 rate;
>>>>>>             int ret;
>>>>>>
>>>>>>             clk = devm_clk_get_optional(dev, id);
>>>>>>             ret = device_property_read_u32(dev, "clock-frequency", &rate);
>>>>>> -       acpi_node = is_acpi_node(dev_fwnode(dev));
>>>>>> +       is_node = is_acpi_node(dev_fwnode(dev)) || is_software_node(dev_fwnode(dev));
>>>>>>
>>>>>>             if (clk) {
>>>>>> -               if (!ret && acpi_node) {
>>>>>> +               if (!ret && is_node) {
>>>>>>                             ret = clk_set_rate(clk, rate);
>>>>>>                             if (ret) {
>>>>>>                                     dev_err(dev, "Failed to set clock rate: %u\n",
>>>>>> @@ -668,7 +668,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device
>>>>>> *dev, const char *id)
>>>>>>             if (ret)
>>>>>>                     return ERR_PTR(ret);
>>>>>>
>>>>>> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
>>>>>> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !is_node)
>>>>>>                     return ERR_PTR(-ENOENT);
>>>>>>
>>>>>>             if (!id) {
>>>>>> ----
> 
> --
> Regards,
> 
> Laurent Pinchart


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24 18:26                   ` Nirujogi, Pratap
@ 2025-06-24 20:01                     ` Nirujogi, Pratap
  0 siblings, 0 replies; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-24 20:01 UTC (permalink / raw)
  To: Mehdi Djait, Laurent Pinchart
  Cc: Sakari Ailus, Pratap Nirujogi, mchehab, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, Svetoslav.Stoilov, Yana.Zheleva



On 6/24/2025 2:26 PM, Nirujogi, Pratap wrote:
> Hi Mehdi, Sakari, Laurent,
> 
> On 6/24/2025 7:27 AM, Mehdi Djait wrote:
>> Caution: This message originated from an External Source. Use proper 
>> caution when opening attachments, clicking links, or responding.
>>
>>
>> Hi Laurent, Hi Sakari,
>>
>> On Tue, Jun 24, 2025 at 01:27:45PM +0300, Laurent Pinchart wrote:
>>> On Tue, Jun 24, 2025 at 10:20:34AM +0000, Sakari Ailus wrote:
>>>> On Tue, Jun 24, 2025 at 10:19:35AM +0000, Sakari Ailus wrote:
>>>>> On Tue, Jun 24, 2025 at 10:35:18AM +0200, Mehdi Djait wrote:
>>>>>> On Tue, Jun 24, 2025 at 01:05:03AM +0300, Laurent Pinchart wrote:
>>>>>>> On Mon, Jun 23, 2025 at 05:51:48PM -0400, Nirujogi, Pratap wrote:
>>>>>>>> On 6/16/2025 6:49 PM, Nirujogi, Pratap wrote:
>>>>>>>>>>> +static int ov05c10_probe(struct i2c_client *client)
>>>>>>>>>>> +{
>>>>>>>>>>> +     struct ov05c10 *ov05c10;
>>>>>>>>>>> +     u32 clkfreq;
>>>>>>>>>>> +     int ret;
>>>>>>>>>>> +
>>>>>>>>>>> +     ov05c10 = devm_kzalloc(&client->dev, sizeof(*ov05c10),
>>>>>>>>>>> GFP_KERNEL);
>>>>>>>>>>> +     if (!ov05c10)
>>>>>>>>>>> +             return -ENOMEM;
>>>>>>>>>>> +
>>>>>>>>>>> +     struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
>>>>>>>>>>> +
>>>>>>>>>>> +     ret = fwnode_property_read_u32(fwnode, "clock-frequency",
>>>>>>>>>>> &clkfreq);
>>>>>>>>>>> +     if (ret)
>>>>>>>>>>> +             return  dev_err_probe(&client->dev, -EINVAL,
>>>>>>>>>>> +                                   "fail to get clock freq\n");
>>>>>>>>>>
>>>>>>>>>> Let's try to land
>>>>>>>>>> https://lore.kernel.org/linux-media/20250521104115.176950-1-
>>>>>>>>>> mehdi.djait@linux.intel.com/
>>>>>>>>>> and replace the code above with devm_v4l2_sensor_clk_get().
>>>>>>>>>>
>>>>>>>>> Ok, we will verify on our side.
>>>>>>>>
>>>>>>>> We tried using devm_v4l2_sensor_clk_get() and found its required 
>>>>>>>> to add
>>>>>>>> support for software_node to make it work with this driver.
>>>>>>>
>>>>>>> Why is that ?
>>>>>>>
>>>>>>>> Please refer
>>>>>>>> the changes below and let us know if these should be submitted as a
>>>>>>>> separate patch.
>>>>>>
>>>>>> The helper is still not merged, so no patch is required.
>>>>>>
>>>>>> I will see if a change is needed from the helper side or the 
>>>>>> OV05C10 side.
>>>>>
>>>>> I wonder if there's a better way to figure out if you're running on 
>>>>> a DT or
>>>>> ACPI based system than getting the device's parents and checking 
>>>>> which one
>>>>> you find first, DT or ACPI. I think that should work for now at least.
>>>>
>>>> Or, rather, checking for non-OF node here would probably work the 
>>>> best. I
>>>> wouldn't expect these to be software node based on DT systems ever.
>>>
>>> Until it happens :-) And we'll handle it then.
>>
>> So we have the following:
>>
>> - The problem with this driver is due to lack of proper ACPI
>>    description. HW is already shipping and AMD will work on better ACPI
>>    description for future models. See [1]
>>
> Thanks Mehdi for clarifying and providing the reference from the 
> associated x86/platform driver patch.
> 
> yes, thats true we have to add software_nodes to mitigate the issue 
> caused by incomplete description of camera device in ACPI tables.
> 
> For future models we are working on a plan to address this issue 
> following the MIPI DisCo Imaging Spec suggested by Sakari to properly 
> describe the camera device in ACPI. Please see [A]
> 
> Once again thanks everyone for the support!
> 
> [A] https://lore.kernel.org/ 
> lkml/2a9ba94e-7985-4ba9-88c6-45b8cf4d001f@amd.com/
> 
>> - software_node can also be used on DT systems
>>
>> [1] https://lore.kernel.org/lkml/0d801367- 
>> da24-4596-83d9-08ccd89ca670@redhat.com/
>>
>> Now going back to the helper. If we want to support this case:
>>
>> Approach 1: software_node || acpi
>>
>> --- a/drivers/media/v4l2-core/v4l2-common.c
>> +++ b/drivers/media/v4l2-core/v4l2-common.c
>> @@ -682,16 +682,17 @@ struct clk *devm_v4l2_sensor_clk_get(struct 
>> device *dev, const char *id)
>>          const char *clk_id __free(kfree) = NULL;
>>          struct clk_hw *clk_hw;
>>          struct clk *clk;
>> -       bool acpi_node;
>> +       bool acpi_sw_node;
>>          u32 rate;
>>          int ret;
>>
>>          clk = devm_clk_get_optional(dev, id);
>>          ret = device_property_read_u32(dev, "clock-frequency", &rate);
>> -       acpi_node = is_acpi_node(dev_fwnode(dev));
>> +       acpi_sw_node = is_acpi_node(dev_fwnode(dev)) ||
>> +                      is_software_node(dev_fwnode(dev));
>>
>>          if (clk) {
>> -               if (!ret && acpi_node) {
>> +               if (!ret && acpi_sw_node) {
>>                          ret = clk_set_rate(clk, rate);
>>                          if (ret) {
>>                                  dev_err(dev, "Failed to set clock 
>> rate: %u\n",
>> @@ -705,7 +706,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device 
>> *dev, const char *id)
>>          if (ret)
>>                  return ERR_PTR(ret);
>>
>> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
>> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_sw_node)
>>                  return ERR_PTR(-ENOENT);
>>
>>          if (!id) {
>>
>>
>> Approach 2: of_node
>>
>> --- a/drivers/media/v4l2-core/v4l2-common.c
>> +++ b/drivers/media/v4l2-core/v4l2-common.c
>> @@ -682,16 +682,16 @@ struct clk *devm_v4l2_sensor_clk_get(struct 
>> device *dev, const char *id)
>>          const char *clk_id __free(kfree) = NULL;
>>          struct clk_hw *clk_hw;
>>          struct clk *clk;
>> -       bool acpi_node;
>> +       bool of_node;
>>          u32 rate;
>>          int ret;
>>
>>          clk = devm_clk_get_optional(dev, id);
>>          ret = device_property_read_u32(dev, "clock-frequency", &rate);
>> -       acpi_node = is_acpi_node(dev_fwnode(dev));
>> +       of_node = is_of_node(dev_fwnode(dev));
>>
>>          if (clk) {
>> -               if (!ret && acpi_node) {
>> +               if (!ret && !of_node) {
>>                          ret = clk_set_rate(clk, rate);
>>                          if (ret) {
>>                                  dev_err(dev, "Failed to set clock 
>> rate: %u\n",
>> @@ -705,7 +705,7 @@ struct clk *devm_v4l2_sensor_clk_get(struct device 
>> *dev, const char *id)
>>          if (ret)
>>                  return ERR_PTR(ret);
>>
>> -       if (!IS_ENABLED(CONFIG_COMMON_CLK) || !acpi_node)
>> +       if (!IS_ENABLED(CONFIG_COMMON_CLK) || of_node)
>>                  return ERR_PTR(-ENOENT);
>>
>>          if (!id) {
>>
> Thanks for proposing "approach 2 using !swnode", I verified and confirm 
> it works at my end.
> 
sorry,  fixing the typo - I meant "approach 2 using !of_node".

Thanks,
Pratap


> Thanks,
> Pratap
>>
>> -- 
>> Kind Regards
>> Mehdi Djait
> 


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24 16:34                     ` Mehdi Djait
@ 2025-06-24 20:24                       ` Nirujogi, Pratap
  2025-06-25  6:11                       ` Sakari Ailus
  1 sibling, 0 replies; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-24 20:24 UTC (permalink / raw)
  To: Mehdi Djait, Sakari Ailus
  Cc: Laurent Pinchart, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, Svetoslav.Stoilov,
	Yana.Zheleva

Hi Mehdi, Sakari,

On 6/24/2025 12:34 PM, Mehdi Djait wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> Hi Sakari,
> 
> On Tue, Jun 24, 2025 at 11:46:27AM +0000, Sakari Ailus wrote:
> 
> [...]
> 
>>
>> I'm in favour of the latter but both should be workable.
>>
>> Speaking of return values, devm_clk_get_optional() may also return
>> -EPROBE_DEFER. That needs to be handled.
>>
> 
> Ack.
> 
>> And further on -EPROBE_DEFER, I think the helper should return
>> -EPROBE_DEFER if the "clock-frequency" property doesn't exist on non-OF
>> nodes. That signals the required software nodes required on Intel Windows
>> definitions/ipu-bridge or AMD systems aren't in place yet so really probing
>> should be deferred. This would allow removing the hacks that return
>> -EPROBE_DEFER in sensor drivers when no graph endpoint is found.
> 
> device_property_read_u32() returns the following:
> 
>   * Return: number of values if @val was %NULL,
>   *         %0 if the property was found (success),
>   *         %-EINVAL if given arguments are not valid,
>   *         %-ENODATA if the property does not have a value,
>   *         %-EPROTO if the property is not an array of numbers,
>   *         %-EOVERFLOW if the size of the property is not as expected.
>   *         %-ENXIO if no suitable firmware interface is present.
> 
> 
> Do you mean something like this in the helper:
> 
> if (ret == -ENODATA && !of_node)
>          return ERR_PTR(-EPROBE_DEFER);
> 
If I understand correctly, the recommendation is to return -EPROBE_DEFER 
on !of_node systems when the driver fails to read the clock-frequency 
property. Kindly confirm.

if (ret && !of_node)
	return ERR_PTR(-EPROBE_DEFER);

Thanks,
Pratap

> --
> Kind Regards
> Mehdi Djait


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-24 16:34                     ` Mehdi Djait
  2025-06-24 20:24                       ` Nirujogi, Pratap
@ 2025-06-25  6:11                       ` Sakari Ailus
  1 sibling, 0 replies; 60+ messages in thread
From: Sakari Ailus @ 2025-06-25  6:11 UTC (permalink / raw)
  To: Mehdi Djait
  Cc: Laurent Pinchart, Nirujogi, Pratap, Pratap Nirujogi, mchehab,
	hverkuil, bryan.odonoghue, krzk, dave.stevenson, hdegoede,
	jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	Svetoslav.Stoilov, Yana.Zheleva

Hi Mehdi,

On Tue, Jun 24, 2025 at 06:34:51PM +0200, Mehdi Djait wrote:
> Hi Sakari,
> 
> On Tue, Jun 24, 2025 at 11:46:27AM +0000, Sakari Ailus wrote:
> 
> [...]
> 
> > 
> > I'm in favour of the latter but both should be workable.
> > 
> > Speaking of return values, devm_clk_get_optional() may also return
> > -EPROBE_DEFER. That needs to be handled.
> > 
> 
> Ack.
> 
> > And further on -EPROBE_DEFER, I think the helper should return
> > -EPROBE_DEFER if the "clock-frequency" property doesn't exist on non-OF
> > nodes. That signals the required software nodes required on Intel Windows
> > definitions/ipu-bridge or AMD systems aren't in place yet so really probing
> > should be deferred. This would allow removing the hacks that return
> > -EPROBE_DEFER in sensor drivers when no graph endpoint is found.
> 
> device_property_read_u32() returns the following:
> 
>  * Return: number of values if @val was %NULL,
>  *         %0 if the property was found (success),
>  *	   %-EINVAL if given arguments are not valid,
>  *	   %-ENODATA if the property does not have a value,
>  *	   %-EPROTO if the property is not an array of numbers,
>  *	   %-EOVERFLOW if the size of the property is not as expected.
>  *	   %-ENXIO if no suitable firmware interface is present.
> 
> 
> Do you mean something like this in the helper:
> 
> if (ret == -ENODATA && !of_node)
> 	return ERR_PTR(-EPROBE_DEFER);

I think -EINVAL is returned if a property doesn't exist on ACPI, same on
software nodes. On software nodes you could also have -ENODATA if the
property has no value. So test both of these?

I'd also test !of_node first.

-- 
Regards,

Sakari Ailus

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-23 21:55             ` Nirujogi, Pratap
  2025-06-23 22:06               ` Laurent Pinchart
@ 2025-06-25 22:06               ` Nirujogi, Pratap
  2025-06-26 11:11                 ` Kieran Bingham
  1 sibling, 1 reply; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-25 22:06 UTC (permalink / raw)
  To: Laurent Pinchart, Sakari Ailus
  Cc: Hao Yao, Pratap Nirujogi, mchehab, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, dongcheng.yan, jason.z.chen, jimmy.su,
	Svetoslav.Stoilov, Yana.Zheleva

Hi Sakari, Hi Laurent,

On 6/23/2025 5:55 PM, Nirujogi, Pratap wrote:
[...]
>>>> I think it can live in the driver for now. Given that the device uses
>>>> only 8 bits of register address, I would store the page number in bits
>>>> 15:8 instead of bits 31:24, as the CCI helpers do not make bits 27:24
>>>> available for driver-specific purpose.
>>>
>>> I'd use the CCI private bits, the driver uses page numbers up to 4 so 4
>>> bits are plenty for that. If we add pages to CCI later, this may be
>>> refactored then.
>>
>> That works too.
>>
> Thanks for your support. We will add the page number in the register 
> address 15:8 or 11:8 and will update the implementation accordingly in 
> the next version.
> 
I would like to share the approach we are taking to implement the CCI 
helpers that support page value. Could you please review the steps and 
let us know if they make sense or if any adjustments are needed?

1: Add new macros to embed page value into the register address.

Ex:
#define CCI_PAGE_REG8(x, p)		((1 << CCI_REG_WIDTH_SHIFT) | (p << 
CCI_REG_PRIVATE_SHIFT) | (x))
#define CCI_PAGE_REG16(x, p)		((2 << CCI_REG_WIDTH_SHIFT) | (p << 
CCI_REG_PRIVATE_SHIFT) | (x))

2: Create V4L2 CCI context. Initialize page control reg, current_page, 
regmap etc.

Ex:
struct v4l2_cci_ctx {
	struct mutex lock;
	struct regmap *map;
	s16 current_page;
	u8 page_ctrl_reg;
}

3: Introduce new CCI helpers - cci_pwrite() and cci_pread() to handle 
register read-writes updating the page control register as necessary.

int cci_pwrite(void *data, u32 reg, u64 val, int *err)
{
	/* get v4l2_cci_ctx context from data */

	/* get page value from reg */

	/* acquire mutex */

	/* update cci page control reg, save current page value */
	
	/* do cci_write */

	/* release mutex */
}

Similar steps for cci_pread() as well.

Thanks,
Pratap

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-25 22:06               ` Nirujogi, Pratap
@ 2025-06-26 11:11                 ` Kieran Bingham
  2025-06-26 12:23                   ` Laurent Pinchart
  2025-06-26 18:14                   ` Nirujogi, Pratap
  0 siblings, 2 replies; 60+ messages in thread
From: Kieran Bingham @ 2025-06-26 11:11 UTC (permalink / raw)
  To: Nirujogi, Pratap, Laurent Pinchart, Sakari Ailus
  Cc: Hao Yao, Pratap Nirujogi, mchehab, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, dongcheng.yan, jason.z.chen, jimmy.su,
	Svetoslav.Stoilov, Yana.Zheleva

Quoting Nirujogi, Pratap (2025-06-25 23:06:01)
> Hi Sakari, Hi Laurent,
> 
> On 6/23/2025 5:55 PM, Nirujogi, Pratap wrote:
> [...]
> >>>> I think it can live in the driver for now. Given that the device uses
> >>>> only 8 bits of register address, I would store the page number in bits
> >>>> 15:8 instead of bits 31:24, as the CCI helpers do not make bits 27:24
> >>>> available for driver-specific purpose.
> >>>
> >>> I'd use the CCI private bits, the driver uses page numbers up to 4 so 4
> >>> bits are plenty for that. If we add pages to CCI later, this may be
> >>> refactored then.
> >>
> >> That works too.
> >>
> > Thanks for your support. We will add the page number in the register 
> > address 15:8 or 11:8 and will update the implementation accordingly in 
> > the next version.
> > 
> I would like to share the approach we are taking to implement the CCI 
> helpers that support page value. Could you please review the steps and 
> let us know if they make sense or if any adjustments are needed?
> 
> 1: Add new macros to embed page value into the register address.
> 
> Ex:
> #define CCI_PAGE_REG8(x, p)             ((1 << CCI_REG_WIDTH_SHIFT) | (p << 
> CCI_REG_PRIVATE_SHIFT) | (x))
> #define CCI_PAGE_REG16(x, p)            ((2 << CCI_REG_WIDTH_SHIFT) | (p << 
> CCI_REG_PRIVATE_SHIFT) | (x))
> 
> 2: Create V4L2 CCI context. Initialize page control reg, current_page, 
> regmap etc.
> 
> Ex:
> struct v4l2_cci_ctx {
>         struct mutex lock;
>         struct regmap *map;
>         s16 current_page;
>         u8 page_ctrl_reg;
> }
> 
> 3: Introduce new CCI helpers - cci_pwrite() and cci_pread() to handle 
> register read-writes updating the page control register as necessary.

Out of curiosity - but couldn't the existing cci_write and cci_read
already be used by the users - and then the default behaviour is that
the page isn't modified if there is no page_ctrl_reg - and by default
CCI_REG() will simply have (initilised) as page 0 - so the pages will
never change on those calls?

Then the users can indeed define

#define TEST_PATTERN_PAGE 5
#define TEST_PATTERN_COLOUR_BARS BIT(3)

#define MY_TEST_PATTERN_REG CCI_PAGE_REG8(0x33, TEST_PATTERN_PAGE)

and can call 
 cci_write(regmap, MY_TEST_PATTERN_REG, TEST_PATTERN_COLOUR_BARS, &ret);

with everything handled transparently ?


Or do you envisage more complications with the types of pages that might
be supportable ?

(I perfectly understand if I'm wishing for an unreachable utopia -
because I haven't considered something implicit about the page handling
that I haven't yet used :D)

--
Kieran


> int cci_pwrite(void *data, u32 reg, u64 val, int *err)
> {
>         /* get v4l2_cci_ctx context from data */
> 
>         /* get page value from reg */
> 
>         /* acquire mutex */
> 
>         /* update cci page control reg, save current page value */
>         
>         /* do cci_write */
> 
>         /* release mutex */
> }
> 
> Similar steps for cci_pread() as well.
> 
> Thanks,
> Pratap

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-26 11:11                 ` Kieran Bingham
@ 2025-06-26 12:23                   ` Laurent Pinchart
  2025-06-26 18:22                     ` Nirujogi, Pratap
  2025-06-26 18:14                   ` Nirujogi, Pratap
  1 sibling, 1 reply; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-26 12:23 UTC (permalink / raw)
  To: Kieran Bingham
  Cc: Nirujogi, Pratap, Sakari Ailus, Hao Yao, Pratap Nirujogi, mchehab,
	hverkuil, bryan.odonoghue, krzk, dave.stevenson, hdegoede,
	jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	dongcheng.yan, jason.z.chen, jimmy.su, Svetoslav.Stoilov,
	Yana.Zheleva

On Thu, Jun 26, 2025 at 12:11:27PM +0100, Kieran Bingham wrote:
> Quoting Nirujogi, Pratap (2025-06-25 23:06:01)
> > Hi Sakari, Hi Laurent,
> > 
> > On 6/23/2025 5:55 PM, Nirujogi, Pratap wrote:
> > [...]
> > >>>> I think it can live in the driver for now. Given that the device uses
> > >>>> only 8 bits of register address, I would store the page number in bits
> > >>>> 15:8 instead of bits 31:24, as the CCI helpers do not make bits 27:24
> > >>>> available for driver-specific purpose.
> > >>>
> > >>> I'd use the CCI private bits, the driver uses page numbers up to 4 so 4
> > >>> bits are plenty for that. If we add pages to CCI later, this may be
> > >>> refactored then.
> > >>
> > >> That works too.
> > >>
> > > Thanks for your support. We will add the page number in the register 
> > > address 15:8 or 11:8 and will update the implementation accordingly in 
> > > the next version.
> > > 
> > I would like to share the approach we are taking to implement the CCI 
> > helpers that support page value. Could you please review the steps and 
> > let us know if they make sense or if any adjustments are needed?
> > 
> > 1: Add new macros to embed page value into the register address.
> > 
> > Ex:
> > #define CCI_PAGE_REG8(x, p)             ((1 << CCI_REG_WIDTH_SHIFT) | (p << 
> > CCI_REG_PRIVATE_SHIFT) | (x))
> > #define CCI_PAGE_REG16(x, p)            ((2 << CCI_REG_WIDTH_SHIFT) | (p << 
> > CCI_REG_PRIVATE_SHIFT) | (x))
> > 
> > 2: Create V4L2 CCI context. Initialize page control reg, current_page, 
> > regmap etc.
> > 
> > Ex:
> > struct v4l2_cci_ctx {
> >         struct mutex lock;
> >         struct regmap *map;
> >         s16 current_page;
> >         u8 page_ctrl_reg;
> > }
> > 
> > 3: Introduce new CCI helpers - cci_pwrite() and cci_pread() to handle 
> > register read-writes updating the page control register as necessary.
> 
> Out of curiosity - but couldn't the existing cci_write and cci_read
> already be used by the users - and then the default behaviour is that
> the page isn't modified if there is no page_ctrl_reg - and by default
> CCI_REG() will simply have (initilised) as page 0 - so the pages will
> never change on those calls?
> 
> Then the users can indeed define
> 
> #define TEST_PATTERN_PAGE 5
> #define TEST_PATTERN_COLOUR_BARS BIT(3)
> 
> #define MY_TEST_PATTERN_REG CCI_PAGE_REG8(0x33, TEST_PATTERN_PAGE)
> 
> and can call 
>  cci_write(regmap, MY_TEST_PATTERN_REG, TEST_PATTERN_COLOUR_BARS, &ret);
> 
> with everything handled transparently ?
> 
> 
> Or do you envisage more complications with the types of pages that might
> be supportable ?
> 
> (I perfectly understand if I'm wishing for an unreachable utopia -
> because I haven't considered something implicit about the page handling
> that I haven't yet used :D)

I don't think we should implement page support in the CCI helpers
themselves yet. We have too few drivers to look at to understand the
requirements. Instead, I would store the page number in the driver
private bits of the 32-bit address (that's bits 31:28), and add wrappers
around cci_read() and cci_write() to the OV05C10 driver that handles the
page configuration.

Once we'll have multiple drivers doing the same, it will be easier to
see what requirements they share, and move the feature to the CCI
helpers.

> > int cci_pwrite(void *data, u32 reg, u64 val, int *err)
> > {
> >         /* get v4l2_cci_ctx context from data */
> > 
> >         /* get page value from reg */
> > 
> >         /* acquire mutex */
> > 
> >         /* update cci page control reg, save current page value */
> >         
> >         /* do cci_write */
> > 
> >         /* release mutex */
> > }
> > 
> > Similar steps for cci_pread() as well.

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-26 11:11                 ` Kieran Bingham
  2025-06-26 12:23                   ` Laurent Pinchart
@ 2025-06-26 18:14                   ` Nirujogi, Pratap
  1 sibling, 0 replies; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-26 18:14 UTC (permalink / raw)
  To: Kieran Bingham, Laurent Pinchart, Sakari Ailus
  Cc: Hao Yao, Pratap Nirujogi, mchehab, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, dongcheng.yan, jason.z.chen, jimmy.su,
	Svetoslav.Stoilov, Yana.Zheleva

Hi Kieran,

On 6/26/2025 7:11 AM, Kieran Bingham wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> Quoting Nirujogi, Pratap (2025-06-25 23:06:01)
>> Hi Sakari, Hi Laurent,
>>
>> On 6/23/2025 5:55 PM, Nirujogi, Pratap wrote:
>> [...]
>>>>>> I think it can live in the driver for now. Given that the device uses
>>>>>> only 8 bits of register address, I would store the page number in bits
>>>>>> 15:8 instead of bits 31:24, as the CCI helpers do not make bits 27:24
>>>>>> available for driver-specific purpose.
>>>>>
>>>>> I'd use the CCI private bits, the driver uses page numbers up to 4 so 4
>>>>> bits are plenty for that. If we add pages to CCI later, this may be
>>>>> refactored then.
>>>>
>>>> That works too.
>>>>
>>> Thanks for your support. We will add the page number in the register
>>> address 15:8 or 11:8 and will update the implementation accordingly in
>>> the next version.
>>>
>> I would like to share the approach we are taking to implement the CCI
>> helpers that support page value. Could you please review the steps and
>> let us know if they make sense or if any adjustments are needed?
>>
>> 1: Add new macros to embed page value into the register address.
>>
>> Ex:
>> #define CCI_PAGE_REG8(x, p)             ((1 << CCI_REG_WIDTH_SHIFT) | (p <<
>> CCI_REG_PRIVATE_SHIFT) | (x))
>> #define CCI_PAGE_REG16(x, p)            ((2 << CCI_REG_WIDTH_SHIFT) | (p <<
>> CCI_REG_PRIVATE_SHIFT) | (x))
>>
>> 2: Create V4L2 CCI context. Initialize page control reg, current_page,
>> regmap etc.
>>
>> Ex:
>> struct v4l2_cci_ctx {
>>          struct mutex lock;
>>          struct regmap *map;
>>          s16 current_page;
>>          u8 page_ctrl_reg;
>> }
>>
>> 3: Introduce new CCI helpers - cci_pwrite() and cci_pread() to handle
>> register read-writes updating the page control register as necessary.
> 
> Out of curiosity - but couldn't the existing cci_write and cci_read
> already be used by the users - and then the default behaviour is that
> the page isn't modified if there is no page_ctrl_reg - and by default
> CCI_REG() will simply have (initilised) as page 0 - so the pages will
> never change on those calls?
> 
> Then the users can indeed define
> 
> #define TEST_PATTERN_PAGE 5
> #define TEST_PATTERN_COLOUR_BARS BIT(3)
> 
> #define MY_TEST_PATTERN_REG CCI_PAGE_REG8(0x33, TEST_PATTERN_PAGE)
> 
> and can call
>   cci_write(regmap, MY_TEST_PATTERN_REG, TEST_PATTERN_COLOUR_BARS, &ret);
> 
> with everything handled transparently ?
> 
> 
> Or do you envisage more complications with the types of pages that might
> be supportable ?
> 
> (I perfectly understand if I'm wishing for an unreachable utopia -
> because I haven't considered something implicit about the page handling
> that I haven't yet used :D)
> 
Initally we too felt the existing cci_write() / cci_read() should be 
updated to support pages, but worried about the potential regressions 
with other drivers that are based on existing implementation. We felt 
adding new helper functions in V4L2 CCI — while keeping the original 
cci_write() and cci_read() unchanged — would be a safer and more stable 
approach.

Thanks,
Pratap

> --
> Kieran
> 
> 
>> int cci_pwrite(void *data, u32 reg, u64 val, int *err)
>> {
>>          /* get v4l2_cci_ctx context from data */
>>
>>          /* get page value from reg */
>>
>>          /* acquire mutex */
>>
>>          /* update cci page control reg, save current page value */
>>
>>          /* do cci_write */
>>
>>          /* release mutex */
>> }
>>
>> Similar steps for cci_pread() as well.
>>
>> Thanks,
>> Pratap


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-26 12:23                   ` Laurent Pinchart
@ 2025-06-26 18:22                     ` Nirujogi, Pratap
  2025-06-26 18:56                       ` Laurent Pinchart
  0 siblings, 1 reply; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-26 18:22 UTC (permalink / raw)
  To: Laurent Pinchart, Kieran Bingham
  Cc: Sakari Ailus, Hao Yao, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su, Svetoslav.Stoilov, Yana.Zheleva

Hi Laurent,

On 6/26/2025 8:23 AM, Laurent Pinchart wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> On Thu, Jun 26, 2025 at 12:11:27PM +0100, Kieran Bingham wrote:
>> Quoting Nirujogi, Pratap (2025-06-25 23:06:01)
>>> Hi Sakari, Hi Laurent,
>>>
>>> On 6/23/2025 5:55 PM, Nirujogi, Pratap wrote:
>>> [...]
>>>>>>> I think it can live in the driver for now. Given that the device uses
>>>>>>> only 8 bits of register address, I would store the page number in bits
>>>>>>> 15:8 instead of bits 31:24, as the CCI helpers do not make bits 27:24
>>>>>>> available for driver-specific purpose.
>>>>>>
>>>>>> I'd use the CCI private bits, the driver uses page numbers up to 4 so 4
>>>>>> bits are plenty for that. If we add pages to CCI later, this may be
>>>>>> refactored then.
>>>>>
>>>>> That works too.
>>>>>
>>>> Thanks for your support. We will add the page number in the register
>>>> address 15:8 or 11:8 and will update the implementation accordingly in
>>>> the next version.
>>>>
>>> I would like to share the approach we are taking to implement the CCI
>>> helpers that support page value. Could you please review the steps and
>>> let us know if they make sense or if any adjustments are needed?
>>>
>>> 1: Add new macros to embed page value into the register address.
>>>
>>> Ex:
>>> #define CCI_PAGE_REG8(x, p)             ((1 << CCI_REG_WIDTH_SHIFT) | (p <<
>>> CCI_REG_PRIVATE_SHIFT) | (x))
>>> #define CCI_PAGE_REG16(x, p)            ((2 << CCI_REG_WIDTH_SHIFT) | (p <<
>>> CCI_REG_PRIVATE_SHIFT) | (x))
>>>
>>> 2: Create V4L2 CCI context. Initialize page control reg, current_page,
>>> regmap etc.
>>>
>>> Ex:
>>> struct v4l2_cci_ctx {
>>>          struct mutex lock;
>>>          struct regmap *map;
>>>          s16 current_page;
>>>          u8 page_ctrl_reg;
>>> }
>>>
>>> 3: Introduce new CCI helpers - cci_pwrite() and cci_pread() to handle
>>> register read-writes updating the page control register as necessary.
>>
>> Out of curiosity - but couldn't the existing cci_write and cci_read
>> already be used by the users - and then the default behaviour is that
>> the page isn't modified if there is no page_ctrl_reg - and by default
>> CCI_REG() will simply have (initilised) as page 0 - so the pages will
>> never change on those calls?
>>
>> Then the users can indeed define
>>
>> #define TEST_PATTERN_PAGE 5
>> #define TEST_PATTERN_COLOUR_BARS BIT(3)
>>
>> #define MY_TEST_PATTERN_REG CCI_PAGE_REG8(0x33, TEST_PATTERN_PAGE)
>>
>> and can call
>>   cci_write(regmap, MY_TEST_PATTERN_REG, TEST_PATTERN_COLOUR_BARS, &ret);
>>
>> with everything handled transparently ?
>>
>>
>> Or do you envisage more complications with the types of pages that might
>> be supportable ?
>>
>> (I perfectly understand if I'm wishing for an unreachable utopia -
>> because I haven't considered something implicit about the page handling
>> that I haven't yet used :D)
> 
> I don't think we should implement page support in the CCI helpers
> themselves yet. We have too few drivers to look at to understand the
> requirements. Instead, I would store the page number in the driver
> private bits of the 32-bit address (that's bits 31:28), and add wrappers
> around cci_read() and cci_write() to the OV05C10 driver that handles the
> page configuration.
> 
> Once we'll have multiple drivers doing the same, it will be easier to
> see what requirements they share, and move the feature to the CCI
> helpers.
> 
Thanks for clarifying. I agree it would be simple and safer approach too 
to handle this way. We will add the following macros in v4l2-cci.h and 
update the existing wrappers ov05c10_reg_write() / ov05c10_reg_read() in 
the driver to retrieve the page and register values to call cci_write() 
/ cci_read(). We will add new wrappers too wherever necessary in the 
driver (ex: wrapper for cci_multi_reg_write() on replacing CCI_REG8 with 
CCI_PAGE_REG8)

#define CCI_PAGE_REG8(x, p)		((1 << CCI_REG_WIDTH_SHIFT) | (p << 
CCI_REG_PAGE_SHIFT) | (x))
#define CCI_PAGE_REG16(x, p)		((2 << CCI_REG_WIDTH_SHIFT) | (p << 
CCI_REG_PAGE_SHIFT) | (x))
#define CCI_PAGE_REG24(x, p)		((3 << CCI_REG_WIDTH_SHIFT) | (p << 
CCI_REG_PAGE_SHIFT) | (x))
#define CCI_PAGE_REG32(x, p)		((4 << CCI_REG_WIDTH_SHIFT) | (p << 
CCI_REG_PAGE_SHIFT) | (x))
#define CCI_PAGE_REG64(x, p)		((8 << CCI_REG_WIDTH_SHIFT) | (p << 
CCI_REG_PAGE_SHIFT) | (x))
#define CCI_PAGE_REG16_LE(x, p)		(CCI_REG_LE | (2U << 
CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
#define CCI_PAGE_REG24_LE(x, p)		(CCI_REG_LE | (3U << 
CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
#define CCI_PAGE_REG32_LE(x, p)		(CCI_REG_LE | (4U << 
CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
#define CCI_PAGE_REG64_LE(x, p)		(CCI_REG_LE | (8U << 
CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
#define CCI_PAGE_GET(x)			FIELD_GET(CCI_REG_PAGE_MASK, x)

Thanks,
Pratap

>>> int cci_pwrite(void *data, u32 reg, u64 val, int *err)
>>> {
>>>          /* get v4l2_cci_ctx context from data */
>>>
>>>          /* get page value from reg */
>>>
>>>          /* acquire mutex */
>>>
>>>          /* update cci page control reg, save current page value */
>>>
>>>          /* do cci_write */
>>>
>>>          /* release mutex */
>>> }
>>>
>>> Similar steps for cci_pread() as well.
> 
> --
> Regards,
> 
> Laurent Pinchart


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-26 18:22                     ` Nirujogi, Pratap
@ 2025-06-26 18:56                       ` Laurent Pinchart
  2025-06-26 19:21                         ` Nirujogi, Pratap
  0 siblings, 1 reply; 60+ messages in thread
From: Laurent Pinchart @ 2025-06-26 18:56 UTC (permalink / raw)
  To: Nirujogi, Pratap
  Cc: Kieran Bingham, Sakari Ailus, Hao Yao, Pratap Nirujogi, mchehab,
	hverkuil, bryan.odonoghue, krzk, dave.stevenson, hdegoede,
	jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	dongcheng.yan, jason.z.chen, jimmy.su, Svetoslav.Stoilov,
	Yana.Zheleva

On Thu, Jun 26, 2025 at 02:22:00PM -0400, Nirujogi, Pratap wrote:
> On 6/26/2025 8:23 AM, Laurent Pinchart wrote:
> > On Thu, Jun 26, 2025 at 12:11:27PM +0100, Kieran Bingham wrote:
> >> Quoting Nirujogi, Pratap (2025-06-25 23:06:01)
> >>> Hi Sakari, Hi Laurent,
> >>>
> >>> On 6/23/2025 5:55 PM, Nirujogi, Pratap wrote:
> >>> [...]
> >>>>>>> I think it can live in the driver for now. Given that the device uses
> >>>>>>> only 8 bits of register address, I would store the page number in bits
> >>>>>>> 15:8 instead of bits 31:24, as the CCI helpers do not make bits 27:24
> >>>>>>> available for driver-specific purpose.
> >>>>>>
> >>>>>> I'd use the CCI private bits, the driver uses page numbers up to 4 so 4
> >>>>>> bits are plenty for that. If we add pages to CCI later, this may be
> >>>>>> refactored then.
> >>>>>
> >>>>> That works too.
> >>>>>
> >>>> Thanks for your support. We will add the page number in the register
> >>>> address 15:8 or 11:8 and will update the implementation accordingly in
> >>>> the next version.
> >>>>
> >>> I would like to share the approach we are taking to implement the CCI
> >>> helpers that support page value. Could you please review the steps and
> >>> let us know if they make sense or if any adjustments are needed?
> >>>
> >>> 1: Add new macros to embed page value into the register address.
> >>>
> >>> Ex:
> >>> #define CCI_PAGE_REG8(x, p)             ((1 << CCI_REG_WIDTH_SHIFT) | (p <<
> >>> CCI_REG_PRIVATE_SHIFT) | (x))
> >>> #define CCI_PAGE_REG16(x, p)            ((2 << CCI_REG_WIDTH_SHIFT) | (p <<
> >>> CCI_REG_PRIVATE_SHIFT) | (x))
> >>>
> >>> 2: Create V4L2 CCI context. Initialize page control reg, current_page,
> >>> regmap etc.
> >>>
> >>> Ex:
> >>> struct v4l2_cci_ctx {
> >>>          struct mutex lock;
> >>>          struct regmap *map;
> >>>          s16 current_page;
> >>>          u8 page_ctrl_reg;
> >>> }
> >>>
> >>> 3: Introduce new CCI helpers - cci_pwrite() and cci_pread() to handle
> >>> register read-writes updating the page control register as necessary.
> >>
> >> Out of curiosity - but couldn't the existing cci_write and cci_read
> >> already be used by the users - and then the default behaviour is that
> >> the page isn't modified if there is no page_ctrl_reg - and by default
> >> CCI_REG() will simply have (initilised) as page 0 - so the pages will
> >> never change on those calls?
> >>
> >> Then the users can indeed define
> >>
> >> #define TEST_PATTERN_PAGE 5
> >> #define TEST_PATTERN_COLOUR_BARS BIT(3)
> >>
> >> #define MY_TEST_PATTERN_REG CCI_PAGE_REG8(0x33, TEST_PATTERN_PAGE)
> >>
> >> and can call
> >>   cci_write(regmap, MY_TEST_PATTERN_REG, TEST_PATTERN_COLOUR_BARS, &ret);
> >>
> >> with everything handled transparently ?
> >>
> >>
> >> Or do you envisage more complications with the types of pages that might
> >> be supportable ?
> >>
> >> (I perfectly understand if I'm wishing for an unreachable utopia -
> >> because I haven't considered something implicit about the page handling
> >> that I haven't yet used :D)
> > 
> > I don't think we should implement page support in the CCI helpers
> > themselves yet. We have too few drivers to look at to understand the
> > requirements. Instead, I would store the page number in the driver
> > private bits of the 32-bit address (that's bits 31:28), and add wrappers
> > around cci_read() and cci_write() to the OV05C10 driver that handles the
> > page configuration.
> > 
> > Once we'll have multiple drivers doing the same, it will be easier to
> > see what requirements they share, and move the feature to the CCI
> > helpers.
> 
> Thanks for clarifying. I agree it would be simple and safer approach too 
> to handle this way. We will add the following macros in v4l2-cci.h and 

Please add the macros to the driver instead, not to v4l2-cci.h. Once
multiple drivers will implement a similar mechanism we can study how to
generalize it.

> update the existing wrappers ov05c10_reg_write() / ov05c10_reg_read() in 
> the driver to retrieve the page and register values to call cci_write() 
> / cci_read(). We will add new wrappers too wherever necessary in the 
> driver (ex: wrapper for cci_multi_reg_write() on replacing CCI_REG8 with 
> CCI_PAGE_REG8)
> 
> #define CCI_PAGE_REG8(x, p)		((1 << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
> #define CCI_PAGE_REG16(x, p)		((2 << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
> #define CCI_PAGE_REG24(x, p)		((3 << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
> #define CCI_PAGE_REG32(x, p)		((4 << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
> #define CCI_PAGE_REG64(x, p)		((8 << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
> #define CCI_PAGE_REG16_LE(x, p)		(CCI_REG_LE | (2U << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
> #define CCI_PAGE_REG24_LE(x, p)		(CCI_REG_LE | (3U << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
> #define CCI_PAGE_REG32_LE(x, p)		(CCI_REG_LE | (4U << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
> #define CCI_PAGE_REG64_LE(x, p)		(CCI_REG_LE | (8U << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
> #define CCI_PAGE_GET(x)			FIELD_GET(CCI_REG_PAGE_MASK, x)
> 
> >>> int cci_pwrite(void *data, u32 reg, u64 val, int *err)
> >>> {
> >>>          /* get v4l2_cci_ctx context from data */
> >>>
> >>>          /* get page value from reg */
> >>>
> >>>          /* acquire mutex */
> >>>
> >>>          /* update cci page control reg, save current page value */
> >>>
> >>>          /* do cci_write */
> >>>
> >>>          /* release mutex */
> >>> }
> >>>
> >>> Similar steps for cci_pread() as well.

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-26 18:56                       ` Laurent Pinchart
@ 2025-06-26 19:21                         ` Nirujogi, Pratap
  0 siblings, 0 replies; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-26 19:21 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Kieran Bingham, Sakari Ailus, Hao Yao, Pratap Nirujogi, mchehab,
	hverkuil, bryan.odonoghue, krzk, dave.stevenson, hdegoede,
	jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	dongcheng.yan, jason.z.chen, jimmy.su, Svetoslav.Stoilov,
	Yana.Zheleva



On 6/26/2025 2:56 PM, Laurent Pinchart wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> On Thu, Jun 26, 2025 at 02:22:00PM -0400, Nirujogi, Pratap wrote:
>> On 6/26/2025 8:23 AM, Laurent Pinchart wrote:
>>> On Thu, Jun 26, 2025 at 12:11:27PM +0100, Kieran Bingham wrote:
>>>> Quoting Nirujogi, Pratap (2025-06-25 23:06:01)
>>>>> Hi Sakari, Hi Laurent,
>>>>>
>>>>> On 6/23/2025 5:55 PM, Nirujogi, Pratap wrote:
>>>>> [...]
>>>>>>>>> I think it can live in the driver for now. Given that the device uses
>>>>>>>>> only 8 bits of register address, I would store the page number in bits
>>>>>>>>> 15:8 instead of bits 31:24, as the CCI helpers do not make bits 27:24
>>>>>>>>> available for driver-specific purpose.
>>>>>>>>
>>>>>>>> I'd use the CCI private bits, the driver uses page numbers up to 4 so 4
>>>>>>>> bits are plenty for that. If we add pages to CCI later, this may be
>>>>>>>> refactored then.
>>>>>>>
>>>>>>> That works too.
>>>>>>>
>>>>>> Thanks for your support. We will add the page number in the register
>>>>>> address 15:8 or 11:8 and will update the implementation accordingly in
>>>>>> the next version.
>>>>>>
>>>>> I would like to share the approach we are taking to implement the CCI
>>>>> helpers that support page value. Could you please review the steps and
>>>>> let us know if they make sense or if any adjustments are needed?
>>>>>
>>>>> 1: Add new macros to embed page value into the register address.
>>>>>
>>>>> Ex:
>>>>> #define CCI_PAGE_REG8(x, p)             ((1 << CCI_REG_WIDTH_SHIFT) | (p <<
>>>>> CCI_REG_PRIVATE_SHIFT) | (x))
>>>>> #define CCI_PAGE_REG16(x, p)            ((2 << CCI_REG_WIDTH_SHIFT) | (p <<
>>>>> CCI_REG_PRIVATE_SHIFT) | (x))
>>>>>
>>>>> 2: Create V4L2 CCI context. Initialize page control reg, current_page,
>>>>> regmap etc.
>>>>>
>>>>> Ex:
>>>>> struct v4l2_cci_ctx {
>>>>>           struct mutex lock;
>>>>>           struct regmap *map;
>>>>>           s16 current_page;
>>>>>           u8 page_ctrl_reg;
>>>>> }
>>>>>
>>>>> 3: Introduce new CCI helpers - cci_pwrite() and cci_pread() to handle
>>>>> register read-writes updating the page control register as necessary.
>>>>
>>>> Out of curiosity - but couldn't the existing cci_write and cci_read
>>>> already be used by the users - and then the default behaviour is that
>>>> the page isn't modified if there is no page_ctrl_reg - and by default
>>>> CCI_REG() will simply have (initilised) as page 0 - so the pages will
>>>> never change on those calls?
>>>>
>>>> Then the users can indeed define
>>>>
>>>> #define TEST_PATTERN_PAGE 5
>>>> #define TEST_PATTERN_COLOUR_BARS BIT(3)
>>>>
>>>> #define MY_TEST_PATTERN_REG CCI_PAGE_REG8(0x33, TEST_PATTERN_PAGE)
>>>>
>>>> and can call
>>>>    cci_write(regmap, MY_TEST_PATTERN_REG, TEST_PATTERN_COLOUR_BARS, &ret);
>>>>
>>>> with everything handled transparently ?
>>>>
>>>>
>>>> Or do you envisage more complications with the types of pages that might
>>>> be supportable ?
>>>>
>>>> (I perfectly understand if I'm wishing for an unreachable utopia -
>>>> because I haven't considered something implicit about the page handling
>>>> that I haven't yet used :D)
>>>
>>> I don't think we should implement page support in the CCI helpers
>>> themselves yet. We have too few drivers to look at to understand the
>>> requirements. Instead, I would store the page number in the driver
>>> private bits of the 32-bit address (that's bits 31:28), and add wrappers
>>> around cci_read() and cci_write() to the OV05C10 driver that handles the
>>> page configuration.
>>>
>>> Once we'll have multiple drivers doing the same, it will be easier to
>>> see what requirements they share, and move the feature to the CCI
>>> helpers.
>>
>> Thanks for clarifying. I agree it would be simple and safer approach too
>> to handle this way. We will add the following macros in v4l2-cci.h and
> 
> Please add the macros to the driver instead, not to v4l2-cci.h. Once
> multiple drivers will implement a similar mechanism we can study how to
> generalize it.
> 
Thanks Laurent. That makes it even easier - all changes can be included 
in the same patch. Its clear now, we will finalize the changes and work 
toward submitting v4.

Thanks,
Pratap

>> update the existing wrappers ov05c10_reg_write() / ov05c10_reg_read() in
>> the driver to retrieve the page and register values to call cci_write()
>> / cci_read(). We will add new wrappers too wherever necessary in the
>> driver (ex: wrapper for cci_multi_reg_write() on replacing CCI_REG8 with
>> CCI_PAGE_REG8)
>>
>> #define CCI_PAGE_REG8(x, p)           ((1 << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
>> #define CCI_PAGE_REG16(x, p)          ((2 << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
>> #define CCI_PAGE_REG24(x, p)          ((3 << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
>> #define CCI_PAGE_REG32(x, p)          ((4 << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
>> #define CCI_PAGE_REG64(x, p)          ((8 << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
>> #define CCI_PAGE_REG16_LE(x, p)               (CCI_REG_LE | (2U << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
>> #define CCI_PAGE_REG24_LE(x, p)               (CCI_REG_LE | (3U << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
>> #define CCI_PAGE_REG32_LE(x, p)               (CCI_REG_LE | (4U << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
>> #define CCI_PAGE_REG64_LE(x, p)               (CCI_REG_LE | (8U << CCI_REG_WIDTH_SHIFT) | (p << CCI_REG_PAGE_SHIFT) | (x))
>> #define CCI_PAGE_GET(x)                       FIELD_GET(CCI_REG_PAGE_MASK, x)
>>
>>>>> int cci_pwrite(void *data, u32 reg, u64 val, int *err)
>>>>> {
>>>>>           /* get v4l2_cci_ctx context from data */
>>>>>
>>>>>           /* get page value from reg */
>>>>>
>>>>>           /* acquire mutex */
>>>>>
>>>>>           /* update cci page control reg, save current page value */
>>>>>
>>>>>           /* do cci_write */
>>>>>
>>>>>           /* release mutex */
>>>>> }
>>>>>
>>>>> Similar steps for cci_pread() as well.
> 
> --
> Regards,
> 
> Laurent Pinchart


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-16 23:46     ` Nirujogi, Pratap
@ 2025-06-28 19:23       ` Sakari Ailus
  0 siblings, 0 replies; 60+ messages in thread
From: Sakari Ailus @ 2025-06-28 19:23 UTC (permalink / raw)
  To: Nirujogi, Pratap, Kieran Bingham, Hao Yao, Pratap Nirujogi
  Cc: mchehab, laurent.pinchart, hverkuil, bryan.odonoghue, krzk,
	dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen, linux-media,
	linux-kernel, benjamin.chan, bin.du, grosikop, king.li, dantony,
	vengutta, dongcheng.yan, jason.z.chen, jimmy.su

Hi Pratap, Kieran,

On 6/17/25 02:46, Nirujogi, Pratap wrote:
> Hi Kieran,
> 
> Thank you for reviewing and sharing your feedback.
> 
> On 6/13/2025 7:02 AM, Kieran Bingham wrote:
>> Caution: This message originated from an External Source. Use proper 
>> caution when opening attachments, clicking links, or responding.
>>
>>
>> Quoting Hao Yao (2025-06-13 05:55:46)
>> I think it will highlight that werever possible - the code below should
>> be factored out to support the different configuration requirements.
>> Cleaning up the large tables of register addresses and making those
>> configurable functions for example configuring the link rate
>> independently would be really beneficial!
>>
>> That's precisely why we continually push for reducing the large
>> "undocumented register" tables in sensor drivers...
>>
> Yes, I agree, it is best to have documented settings and make 
> calculation for sensor modes instead of array of undocumented settings. 
> However the usual practice is that we send requirements to sensor vendor 
> and they provide us the settings array to be applied. May be we can try 
> to move PLL settings to different array but it is not always just the 
> PLL, there are a lot of undocumented settings which are in sync with PLL 
> and sometimes changing the PLL may result in misbehaviour of the sensor...
> 
> We will try to move PLL settings to separate array but cannot guarantee 
> changing these settings alone will make the sensor functional.

I did discuss this with Laurent as well and based on that I'd suggest 
the following split:

- power-on register list,
- external clock / link frequency specific list (often these are tied)
   and
- sensor mode

Which of these lists a particular register is written in isn't crucial 
at the moment, this can be always changed later on.

I'd leave the possible PLL calculator for later. Having more 
configurations is also useful for validating its function. A different 
PLL configuration from the original doesn't necessarily mean it would be 
wrong.

...

>>>> +     page = OV05C10_GET_PAGE_NUM(reg);
>>>> +     addr = OV05C10_GET_REG_ADDR(reg);
>>>> +     ov05c10_switch_page(ov05c10, page, &ret);
>>>> +     cci_write(ov05c10->regmap, addr, val, &ret);
>>>> +     if (err)
>>>> +             *err = ret;
>>>> +
>>>> +     return ret;
>>>> +}
>>
>> One of the main goals of CCI helpers was to avoid all of the custom
>> device accessors being duplicated in each driver, so I think extending
>> the CCI helpers to support page based accesses in some common way would
>> be beneficial.
>>
> Yes, I agree. We can take on this enhancement either now or in the 
> future, depending on the direction. We'll wait for Sakari's feedback to 
> determine the best way to proceed.

As Laurent suggested, keep the page-based helper macros/functions in 
this driver, we can generalise them later on as needed.

-- 
Kind regards,

Sakari Ailus


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-16 22:33       ` Nirujogi, Pratap
@ 2025-06-29  7:40         ` Sakari Ailus
  2025-06-30 22:31           ` Nirujogi, Pratap
  0 siblings, 1 reply; 60+ messages in thread
From: Sakari Ailus @ 2025-06-29  7:40 UTC (permalink / raw)
  To: Nirujogi, Pratap, Laurent Pinchart
  Cc: Hao Yao, Pratap Nirujogi, mchehab, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, dongcheng.yan, jason.z.chen, jimmy.su

Hi Pratap,

On 6/17/25 01:33, Nirujogi, Pratap wrote:
...
>>>>> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>> + { CCI_REG8(0x20),  0x00 },
>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>> + { CCI_REG8(0x20),  0x0b },
>>>>> + { CCI_REG8(0xc1),  0x09 },
>>>>> + { CCI_REG8(0x21),  0x06 },
>>>>> + { CCI_REG8(0x14),  0x78 },
>>>>> + { CCI_REG8(0xe7),  0x03 },
>>>>> + { CCI_REG8(0xe7),  0x00 },
>>>>> + { CCI_REG8(0x21),  0x00 },
>>>>> + { CCI_REG8(0xfd),  0x01 },
>>>>> + { CCI_REG8(0x03),  0x00 },
>>>>> + { CCI_REG8(0x04),  0x06 },
>>>>> + { CCI_REG8(0x05),  0x07 },
>>>>> + { CCI_REG8(0x06),  0x44 },
>>>>> + { CCI_REG8(0x07),  0x08 },
>>>>> + { CCI_REG8(0x1b),  0x01 },
>>>>> + { CCI_REG8(0x24),  0xff },
>>>>> + { CCI_REG8(0x32),  0x03 },
>>>>> + { CCI_REG8(0x42),  0x5d },
>>>>> + { CCI_REG8(0x43),  0x08 },
>>>>> + { CCI_REG8(0x44),  0x81 },
>>>>> + { CCI_REG8(0x46),  0x5f },
>>>>> + { CCI_REG8(0x48),  0x18 },
>>>>> + { CCI_REG8(0x49),  0x04 },
>>>>> + { CCI_REG8(0x5c),  0x18 },
>>>>> + { CCI_REG8(0x5e),  0x13 },
>>>>> + { CCI_REG8(0x70),  0x15 },
>>>>> + { CCI_REG8(0x77),  0x35 },
>>>>> + { CCI_REG8(0x79),  0x00 },
>>>>> + { CCI_REG8(0x7b),  0x08 },
>>>>> + { CCI_REG8(0x7d),  0x08 },
>>>>> + { CCI_REG8(0x7e),  0x08 },
>>>>> + { CCI_REG8(0x7f),  0x08 },
>>>>> + { CCI_REG8(0x90),  0x37 },
>>>>> + { CCI_REG8(0x91),  0x05 },
>>>>> + { CCI_REG8(0x92),  0x18 },
>>>>> + { CCI_REG8(0x93),  0x27 },
>>>>> + { CCI_REG8(0x94),  0x05 },
>>>>> + { CCI_REG8(0x95),  0x38 },
>>>>> + { CCI_REG8(0x9b),  0x00 },
>>>>> + { CCI_REG8(0x9c),  0x06 },
>>>>> + { CCI_REG8(0x9d),  0x28 },
>>>>> + { CCI_REG8(0x9e),  0x06 },
>>>>> + { CCI_REG8(0xb2),  0x0f },
>>>>> + { CCI_REG8(0xb3),  0x29 },
>>>>> + { CCI_REG8(0xbf),  0x3c },
>>>>> + { CCI_REG8(0xc2),  0x04 },
>>>>> + { CCI_REG8(0xc4),  0x00 },
>>>>> + { CCI_REG8(0xca),  0x20 },
>>>>> + { CCI_REG8(0xcb),  0x20 },
>>>>> + { CCI_REG8(0xcc),  0x28 },
>>>>> + { CCI_REG8(0xcd),  0x28 },
>>>>> + { CCI_REG8(0xce),  0x20 },
>>>>> + { CCI_REG8(0xcf),  0x20 },
>>>>> + { CCI_REG8(0xd0),  0x2a },
>>>>> + { CCI_REG8(0xd1),  0x2a },
>>>>> + { CCI_REG8(0xfd),  0x0f },
>>>>> + { CCI_REG8(0x00),  0x00 },
>>>>> + { CCI_REG8(0x01),  0xa0 },
>>>>> + { CCI_REG8(0x02),  0x48 },
>>>>> + { CCI_REG8(0x07),  0x8f },
>>>>> + { CCI_REG8(0x08),  0x70 },
>>>>> + { CCI_REG8(0x09),  0x01 },
>>>>> + { CCI_REG8(0x0b),  0x40 },
>>>>> + { CCI_REG8(0x0d),  0x07 },
>>>>> + { CCI_REG8(0x11),  0x33 },
>>>>> + { CCI_REG8(0x12),  0x77 },
>>>>> + { CCI_REG8(0x13),  0x66 },
>>>>> + { CCI_REG8(0x14),  0x65 },
>>>>> + { CCI_REG8(0x15),  0x37 },
>>>>> + { CCI_REG8(0x16),  0xbf },
>>>>> + { CCI_REG8(0x17),  0xff },
>>>>> + { CCI_REG8(0x18),  0xff },
>>>>> + { CCI_REG8(0x19),  0x12 },
>>>>> + { CCI_REG8(0x1a),  0x10 },
>>>>> + { CCI_REG8(0x1c),  0x77 },
>>>>> + { CCI_REG8(0x1d),  0x77 },
>>>>> + { CCI_REG8(0x20),  0x0f },
>>>>> + { CCI_REG8(0x21),  0x0f },
>>>>> + { CCI_REG8(0x22),  0x0f },
>>>>> + { CCI_REG8(0x23),  0x0f },
>>>>> + { CCI_REG8(0x2b),  0x20 },
>>>>> + { CCI_REG8(0x2c),  0x20 },
>>>>> + { CCI_REG8(0x2d),  0x04 },
>>>>> + { CCI_REG8(0xfd),  0x03 },
>>>>> + { CCI_REG8(0x9d),  0x0f },
>>>>> + { CCI_REG8(0x9f),  0x40 },
>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>> + { CCI_REG8(0x20),  0x1b },
>>>>> + { CCI_REG8(0xfd),  0x04 },
>>>>> + { CCI_REG8(0x19),  0x60 },
>>>>> + { CCI_REG8(0xfd),  0x02 },
>>>>> + { CCI_REG8(0x75),  0x05 },
>>>>> + { CCI_REG8(0x7f),  0x06 },
>>>>> + { CCI_REG8(0x9a),  0x03 },
>>>>> + { CCI_REG8(0xa2),  0x07 },
>>>>> + { CCI_REG8(0xa3),  0x10 },
>>>>> + { CCI_REG8(0xa5),  0x02 },
>>>>> + { CCI_REG8(0xa6),  0x0b },
>>>>> + { CCI_REG8(0xa7),  0x48 },
>>>>> + { CCI_REG8(0xfd),  0x07 },
>>>>> + { CCI_REG8(0x42),  0x00 },
>>>>> + { CCI_REG8(0x43),  0x80 },
>>>>> + { CCI_REG8(0x44),  0x00 },
>>>>> + { CCI_REG8(0x45),  0x80 },
>>>>> + { CCI_REG8(0x46),  0x00 },
>>>>> + { CCI_REG8(0x47),  0x80 },
>>>>> + { CCI_REG8(0x48),  0x00 },
>>>>> + { CCI_REG8(0x49),  0x80 },
>>>>> + { CCI_REG8(0x00),  0xf7 },
>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>> + { CCI_REG8(0xe7),  0x03 },
>>>>> + { CCI_REG8(0xe7),  0x00 },
>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>> + { CCI_REG8(0x93),  0x18 },
>>>>> + { CCI_REG8(0x94),  0xff },
>>>>> + { CCI_REG8(0x95),  0xbd },
>>>>> + { CCI_REG8(0x96),  0x1a },
>>>>> + { CCI_REG8(0x98),  0x04 },
>>>>> + { CCI_REG8(0x99),  0x08 },
>>>>> + { CCI_REG8(0x9b),  0x10 },
>>>>> + { CCI_REG8(0x9c),  0x3f },
>>>>> + { CCI_REG8(0xa1),  0x05 },
>>>>> + { CCI_REG8(0xa4),  0x2f },
>>>>> + { CCI_REG8(0xc0),  0x0c },
>>>>> + { CCI_REG8(0xc1),  0x08 },
>>>>> + { CCI_REG8(0xc2),  0x00 },
>>>>> + { CCI_REG8(0xb6),  0x20 },
>>>>> + { CCI_REG8(0xbb),  0x80 },
>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>> + { CCI_REG8(0xa0),  0x01 },
>>>>> + { CCI_REG8(0xfd),  0x01 },
>>
>> Please replace these with names macros where possible. I'm sure quite a
>> few of the registers configured here are documented in the datasheet.
>> The registers that configure the mode (analog crop, digital crop,
>> binning, skipping, ...) should be computed dynamically from the subdev
>> pad format and selection rectangles, not hardcoded.
>>
> I agree, but we get the sensor settings based on our requirements from 
> the vendor, i will check if we can get some more info regarding the 
> crop, binning, skipping etc...

Some of this infomation should be available in the datasheet. Use at 
least the register names that can be found, for those that can't there's 
not much that could be done.

-- 
Regards,

Sakari Ailus

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-29  7:40         ` Sakari Ailus
@ 2025-06-30 22:31           ` Nirujogi, Pratap
  2025-07-02  6:08             ` Yan, Dongcheng
  0 siblings, 1 reply; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-06-30 22:31 UTC (permalink / raw)
  To: Sakari Ailus, Laurent Pinchart
  Cc: Hao Yao, Pratap Nirujogi, mchehab, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, dongcheng.yan, jason.z.chen, jimmy.su

Hi Sakari, Hi Laurent,

On 6/29/2025 3:40 AM, Sakari Ailus wrote:
> Caution: This message originated from an External Source. Use proper 
> caution when opening attachments, clicking links, or responding.
> 
> 
> Hi Pratap,
> 
> On 6/17/25 01:33, Nirujogi, Pratap wrote:
> ...
>>>>>> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>> + { CCI_REG8(0x20),  0x00 },
>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>> + { CCI_REG8(0x20),  0x0b },
>>>>>> + { CCI_REG8(0xc1),  0x09 },
>>>>>> + { CCI_REG8(0x21),  0x06 },
>>>>>> + { CCI_REG8(0x14),  0x78 },
>>>>>> + { CCI_REG8(0xe7),  0x03 },
>>>>>> + { CCI_REG8(0xe7),  0x00 },
>>>>>> + { CCI_REG8(0x21),  0x00 },
>>>>>> + { CCI_REG8(0xfd),  0x01 },
>>>>>> + { CCI_REG8(0x03),  0x00 },
>>>>>> + { CCI_REG8(0x04),  0x06 },
>>>>>> + { CCI_REG8(0x05),  0x07 },
>>>>>> + { CCI_REG8(0x06),  0x44 },
>>>>>> + { CCI_REG8(0x07),  0x08 },
>>>>>> + { CCI_REG8(0x1b),  0x01 },
>>>>>> + { CCI_REG8(0x24),  0xff },
>>>>>> + { CCI_REG8(0x32),  0x03 },
>>>>>> + { CCI_REG8(0x42),  0x5d },
>>>>>> + { CCI_REG8(0x43),  0x08 },
>>>>>> + { CCI_REG8(0x44),  0x81 },
>>>>>> + { CCI_REG8(0x46),  0x5f },
>>>>>> + { CCI_REG8(0x48),  0x18 },
>>>>>> + { CCI_REG8(0x49),  0x04 },
>>>>>> + { CCI_REG8(0x5c),  0x18 },
>>>>>> + { CCI_REG8(0x5e),  0x13 },
>>>>>> + { CCI_REG8(0x70),  0x15 },
>>>>>> + { CCI_REG8(0x77),  0x35 },
>>>>>> + { CCI_REG8(0x79),  0x00 },
>>>>>> + { CCI_REG8(0x7b),  0x08 },
>>>>>> + { CCI_REG8(0x7d),  0x08 },
>>>>>> + { CCI_REG8(0x7e),  0x08 },
>>>>>> + { CCI_REG8(0x7f),  0x08 },
>>>>>> + { CCI_REG8(0x90),  0x37 },
>>>>>> + { CCI_REG8(0x91),  0x05 },
>>>>>> + { CCI_REG8(0x92),  0x18 },
>>>>>> + { CCI_REG8(0x93),  0x27 },
>>>>>> + { CCI_REG8(0x94),  0x05 },
>>>>>> + { CCI_REG8(0x95),  0x38 },
>>>>>> + { CCI_REG8(0x9b),  0x00 },
>>>>>> + { CCI_REG8(0x9c),  0x06 },
>>>>>> + { CCI_REG8(0x9d),  0x28 },
>>>>>> + { CCI_REG8(0x9e),  0x06 },
>>>>>> + { CCI_REG8(0xb2),  0x0f },
>>>>>> + { CCI_REG8(0xb3),  0x29 },
>>>>>> + { CCI_REG8(0xbf),  0x3c },
>>>>>> + { CCI_REG8(0xc2),  0x04 },
>>>>>> + { CCI_REG8(0xc4),  0x00 },
>>>>>> + { CCI_REG8(0xca),  0x20 },
>>>>>> + { CCI_REG8(0xcb),  0x20 },
>>>>>> + { CCI_REG8(0xcc),  0x28 },
>>>>>> + { CCI_REG8(0xcd),  0x28 },
>>>>>> + { CCI_REG8(0xce),  0x20 },
>>>>>> + { CCI_REG8(0xcf),  0x20 },
>>>>>> + { CCI_REG8(0xd0),  0x2a },
>>>>>> + { CCI_REG8(0xd1),  0x2a },
>>>>>> + { CCI_REG8(0xfd),  0x0f },
>>>>>> + { CCI_REG8(0x00),  0x00 },
>>>>>> + { CCI_REG8(0x01),  0xa0 },
>>>>>> + { CCI_REG8(0x02),  0x48 },
>>>>>> + { CCI_REG8(0x07),  0x8f },
>>>>>> + { CCI_REG8(0x08),  0x70 },
>>>>>> + { CCI_REG8(0x09),  0x01 },
>>>>>> + { CCI_REG8(0x0b),  0x40 },
>>>>>> + { CCI_REG8(0x0d),  0x07 },
>>>>>> + { CCI_REG8(0x11),  0x33 },
>>>>>> + { CCI_REG8(0x12),  0x77 },
>>>>>> + { CCI_REG8(0x13),  0x66 },
>>>>>> + { CCI_REG8(0x14),  0x65 },
>>>>>> + { CCI_REG8(0x15),  0x37 },
>>>>>> + { CCI_REG8(0x16),  0xbf },
>>>>>> + { CCI_REG8(0x17),  0xff },
>>>>>> + { CCI_REG8(0x18),  0xff },
>>>>>> + { CCI_REG8(0x19),  0x12 },
>>>>>> + { CCI_REG8(0x1a),  0x10 },
>>>>>> + { CCI_REG8(0x1c),  0x77 },
>>>>>> + { CCI_REG8(0x1d),  0x77 },
>>>>>> + { CCI_REG8(0x20),  0x0f },
>>>>>> + { CCI_REG8(0x21),  0x0f },
>>>>>> + { CCI_REG8(0x22),  0x0f },
>>>>>> + { CCI_REG8(0x23),  0x0f },
>>>>>> + { CCI_REG8(0x2b),  0x20 },
>>>>>> + { CCI_REG8(0x2c),  0x20 },
>>>>>> + { CCI_REG8(0x2d),  0x04 },
>>>>>> + { CCI_REG8(0xfd),  0x03 },
>>>>>> + { CCI_REG8(0x9d),  0x0f },
>>>>>> + { CCI_REG8(0x9f),  0x40 },
>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>> + { CCI_REG8(0x20),  0x1b },
>>>>>> + { CCI_REG8(0xfd),  0x04 },
>>>>>> + { CCI_REG8(0x19),  0x60 },
>>>>>> + { CCI_REG8(0xfd),  0x02 },
>>>>>> + { CCI_REG8(0x75),  0x05 },
>>>>>> + { CCI_REG8(0x7f),  0x06 },
>>>>>> + { CCI_REG8(0x9a),  0x03 },
>>>>>> + { CCI_REG8(0xa2),  0x07 },
>>>>>> + { CCI_REG8(0xa3),  0x10 },
>>>>>> + { CCI_REG8(0xa5),  0x02 },
>>>>>> + { CCI_REG8(0xa6),  0x0b },
>>>>>> + { CCI_REG8(0xa7),  0x48 },
>>>>>> + { CCI_REG8(0xfd),  0x07 },
>>>>>> + { CCI_REG8(0x42),  0x00 },
>>>>>> + { CCI_REG8(0x43),  0x80 },
>>>>>> + { CCI_REG8(0x44),  0x00 },
>>>>>> + { CCI_REG8(0x45),  0x80 },
>>>>>> + { CCI_REG8(0x46),  0x00 },
>>>>>> + { CCI_REG8(0x47),  0x80 },
>>>>>> + { CCI_REG8(0x48),  0x00 },
>>>>>> + { CCI_REG8(0x49),  0x80 },
>>>>>> + { CCI_REG8(0x00),  0xf7 },
>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>> + { CCI_REG8(0xe7),  0x03 },
>>>>>> + { CCI_REG8(0xe7),  0x00 },
>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>> + { CCI_REG8(0x93),  0x18 },
>>>>>> + { CCI_REG8(0x94),  0xff },
>>>>>> + { CCI_REG8(0x95),  0xbd },
>>>>>> + { CCI_REG8(0x96),  0x1a },
>>>>>> + { CCI_REG8(0x98),  0x04 },
>>>>>> + { CCI_REG8(0x99),  0x08 },
>>>>>> + { CCI_REG8(0x9b),  0x10 },
>>>>>> + { CCI_REG8(0x9c),  0x3f },
>>>>>> + { CCI_REG8(0xa1),  0x05 },
>>>>>> + { CCI_REG8(0xa4),  0x2f },
>>>>>> + { CCI_REG8(0xc0),  0x0c },
>>>>>> + { CCI_REG8(0xc1),  0x08 },
>>>>>> + { CCI_REG8(0xc2),  0x00 },
>>>>>> + { CCI_REG8(0xb6),  0x20 },
>>>>>> + { CCI_REG8(0xbb),  0x80 },
>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>> + { CCI_REG8(0xa0),  0x01 },
>>>>>> + { CCI_REG8(0xfd),  0x01 },
>>>
>>> Please replace these with names macros where possible. I'm sure quite a
>>> few of the registers configured here are documented in the datasheet.
>>> The registers that configure the mode (analog crop, digital crop,
>>> binning, skipping, ...) should be computed dynamically from the subdev
>>> pad format and selection rectangles, not hardcoded.
>>>
>> I agree, but we get the sensor settings based on our requirements from
>> the vendor, i will check if we can get some more info regarding the
>> crop, binning, skipping etc...
> 
> Some of this infomation should be available in the datasheet. Use at
> least the register names that can be found, for those that can't there's
> not much that could be done.
> 
Sorry to say that I don't have the details in this case. We have 
previously reached out to the sensor vendor, but they are not willing to 
disclose any of these details. We hope for your understanding of the 
constraints we're facing and truly value your support.

Thanks,
Pratap

> -- 
> Regards,
> 
> Sakari Ailus


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-06-30 22:31           ` Nirujogi, Pratap
@ 2025-07-02  6:08             ` Yan, Dongcheng
  2025-07-02  6:12               ` Sakari Ailus
  0 siblings, 1 reply; 60+ messages in thread
From: Yan, Dongcheng @ 2025-07-02  6:08 UTC (permalink / raw)
  To: Nirujogi, Pratap, Sakari Ailus, Laurent Pinchart
  Cc: Hao Yao, Pratap Nirujogi, mchehab, hverkuil, bryan.odonoghue,
	krzk, dave.stevenson, hdegoede, jai.luthra, tomi.valkeinen,
	linux-media, linux-kernel, benjamin.chan, bin.du, grosikop,
	king.li, dantony, vengutta, dongcheng.yan, jason.z.chen, jimmy.su

Hi Pratap,

On 7/1/2025 6:31 AM, Nirujogi, Pratap wrote:
> Hi Sakari, Hi Laurent,
> 
> On 6/29/2025 3:40 AM, Sakari Ailus wrote:
>> Caution: This message originated from an External Source. Use proper
>> caution when opening attachments, clicking links, or responding.
>>
>>
>> Hi Pratap,
>>
>> On 6/17/25 01:33, Nirujogi, Pratap wrote:
>> ...
>>>>>>> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>> + { CCI_REG8(0x20),  0x00 },
>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>> + { CCI_REG8(0x20),  0x0b },
>>>>>>> + { CCI_REG8(0xc1),  0x09 },
>>>>>>> + { CCI_REG8(0x21),  0x06 },
>>>>>>> + { CCI_REG8(0x14),  0x78 },
>>>>>>> + { CCI_REG8(0xe7),  0x03 },
>>>>>>> + { CCI_REG8(0xe7),  0x00 },
>>>>>>> + { CCI_REG8(0x21),  0x00 },
>>>>>>> + { CCI_REG8(0xfd),  0x01 },
>>>>>>> + { CCI_REG8(0x03),  0x00 },
>>>>>>> + { CCI_REG8(0x04),  0x06 },
>>>>>>> + { CCI_REG8(0x05),  0x07 },
>>>>>>> + { CCI_REG8(0x06),  0x44 },
>>>>>>> + { CCI_REG8(0x07),  0x08 },
>>>>>>> + { CCI_REG8(0x1b),  0x01 },
>>>>>>> + { CCI_REG8(0x24),  0xff },
>>>>>>> + { CCI_REG8(0x32),  0x03 },
>>>>>>> + { CCI_REG8(0x42),  0x5d },
>>>>>>> + { CCI_REG8(0x43),  0x08 },
>>>>>>> + { CCI_REG8(0x44),  0x81 },
>>>>>>> + { CCI_REG8(0x46),  0x5f },
>>>>>>> + { CCI_REG8(0x48),  0x18 },
>>>>>>> + { CCI_REG8(0x49),  0x04 },
>>>>>>> + { CCI_REG8(0x5c),  0x18 },
>>>>>>> + { CCI_REG8(0x5e),  0x13 },
>>>>>>> + { CCI_REG8(0x70),  0x15 },
>>>>>>> + { CCI_REG8(0x77),  0x35 },
>>>>>>> + { CCI_REG8(0x79),  0x00 },
>>>>>>> + { CCI_REG8(0x7b),  0x08 },
>>>>>>> + { CCI_REG8(0x7d),  0x08 },
>>>>>>> + { CCI_REG8(0x7e),  0x08 },
>>>>>>> + { CCI_REG8(0x7f),  0x08 },
>>>>>>> + { CCI_REG8(0x90),  0x37 },
>>>>>>> + { CCI_REG8(0x91),  0x05 },
>>>>>>> + { CCI_REG8(0x92),  0x18 },
>>>>>>> + { CCI_REG8(0x93),  0x27 },
>>>>>>> + { CCI_REG8(0x94),  0x05 },
>>>>>>> + { CCI_REG8(0x95),  0x38 },
>>>>>>> + { CCI_REG8(0x9b),  0x00 },
>>>>>>> + { CCI_REG8(0x9c),  0x06 },
>>>>>>> + { CCI_REG8(0x9d),  0x28 },
>>>>>>> + { CCI_REG8(0x9e),  0x06 },
>>>>>>> + { CCI_REG8(0xb2),  0x0f },
>>>>>>> + { CCI_REG8(0xb3),  0x29 },
>>>>>>> + { CCI_REG8(0xbf),  0x3c },
>>>>>>> + { CCI_REG8(0xc2),  0x04 },
>>>>>>> + { CCI_REG8(0xc4),  0x00 },
>>>>>>> + { CCI_REG8(0xca),  0x20 },
>>>>>>> + { CCI_REG8(0xcb),  0x20 },
>>>>>>> + { CCI_REG8(0xcc),  0x28 },
>>>>>>> + { CCI_REG8(0xcd),  0x28 },
>>>>>>> + { CCI_REG8(0xce),  0x20 },
>>>>>>> + { CCI_REG8(0xcf),  0x20 },
>>>>>>> + { CCI_REG8(0xd0),  0x2a },
>>>>>>> + { CCI_REG8(0xd1),  0x2a },
>>>>>>> + { CCI_REG8(0xfd),  0x0f },
>>>>>>> + { CCI_REG8(0x00),  0x00 },
>>>>>>> + { CCI_REG8(0x01),  0xa0 },
>>>>>>> + { CCI_REG8(0x02),  0x48 },
>>>>>>> + { CCI_REG8(0x07),  0x8f },
>>>>>>> + { CCI_REG8(0x08),  0x70 },
>>>>>>> + { CCI_REG8(0x09),  0x01 },
>>>>>>> + { CCI_REG8(0x0b),  0x40 },
>>>>>>> + { CCI_REG8(0x0d),  0x07 },
>>>>>>> + { CCI_REG8(0x11),  0x33 },
>>>>>>> + { CCI_REG8(0x12),  0x77 },
>>>>>>> + { CCI_REG8(0x13),  0x66 },
>>>>>>> + { CCI_REG8(0x14),  0x65 },
>>>>>>> + { CCI_REG8(0x15),  0x37 },
>>>>>>> + { CCI_REG8(0x16),  0xbf },
>>>>>>> + { CCI_REG8(0x17),  0xff },
>>>>>>> + { CCI_REG8(0x18),  0xff },
>>>>>>> + { CCI_REG8(0x19),  0x12 },
>>>>>>> + { CCI_REG8(0x1a),  0x10 },
>>>>>>> + { CCI_REG8(0x1c),  0x77 },
>>>>>>> + { CCI_REG8(0x1d),  0x77 },
>>>>>>> + { CCI_REG8(0x20),  0x0f },
>>>>>>> + { CCI_REG8(0x21),  0x0f },
>>>>>>> + { CCI_REG8(0x22),  0x0f },
>>>>>>> + { CCI_REG8(0x23),  0x0f },
>>>>>>> + { CCI_REG8(0x2b),  0x20 },
>>>>>>> + { CCI_REG8(0x2c),  0x20 },
>>>>>>> + { CCI_REG8(0x2d),  0x04 },
>>>>>>> + { CCI_REG8(0xfd),  0x03 },
>>>>>>> + { CCI_REG8(0x9d),  0x0f },
>>>>>>> + { CCI_REG8(0x9f),  0x40 },
>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>> + { CCI_REG8(0x20),  0x1b },
>>>>>>> + { CCI_REG8(0xfd),  0x04 },
>>>>>>> + { CCI_REG8(0x19),  0x60 },
>>>>>>> + { CCI_REG8(0xfd),  0x02 },
>>>>>>> + { CCI_REG8(0x75),  0x05 },
>>>>>>> + { CCI_REG8(0x7f),  0x06 },
>>>>>>> + { CCI_REG8(0x9a),  0x03 },
>>>>>>> + { CCI_REG8(0xa2),  0x07 },
>>>>>>> + { CCI_REG8(0xa3),  0x10 },
>>>>>>> + { CCI_REG8(0xa5),  0x02 },
>>>>>>> + { CCI_REG8(0xa6),  0x0b },
>>>>>>> + { CCI_REG8(0xa7),  0x48 },
>>>>>>> + { CCI_REG8(0xfd),  0x07 },
>>>>>>> + { CCI_REG8(0x42),  0x00 },
>>>>>>> + { CCI_REG8(0x43),  0x80 },
>>>>>>> + { CCI_REG8(0x44),  0x00 },
>>>>>>> + { CCI_REG8(0x45),  0x80 },
>>>>>>> + { CCI_REG8(0x46),  0x00 },
>>>>>>> + { CCI_REG8(0x47),  0x80 },
>>>>>>> + { CCI_REG8(0x48),  0x00 },
>>>>>>> + { CCI_REG8(0x49),  0x80 },
>>>>>>> + { CCI_REG8(0x00),  0xf7 },
>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>> + { CCI_REG8(0xe7),  0x03 },
>>>>>>> + { CCI_REG8(0xe7),  0x00 },
>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>> + { CCI_REG8(0x93),  0x18 },
>>>>>>> + { CCI_REG8(0x94),  0xff },
>>>>>>> + { CCI_REG8(0x95),  0xbd },
>>>>>>> + { CCI_REG8(0x96),  0x1a },
>>>>>>> + { CCI_REG8(0x98),  0x04 },
>>>>>>> + { CCI_REG8(0x99),  0x08 },
>>>>>>> + { CCI_REG8(0x9b),  0x10 },
>>>>>>> + { CCI_REG8(0x9c),  0x3f },
>>>>>>> + { CCI_REG8(0xa1),  0x05 },
>>>>>>> + { CCI_REG8(0xa4),  0x2f },
>>>>>>> + { CCI_REG8(0xc0),  0x0c },
>>>>>>> + { CCI_REG8(0xc1),  0x08 },
>>>>>>> + { CCI_REG8(0xc2),  0x00 },
>>>>>>> + { CCI_REG8(0xb6),  0x20 },
>>>>>>> + { CCI_REG8(0xbb),  0x80 },
>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>> + { CCI_REG8(0xa0),  0x01 },
>>>>>>> + { CCI_REG8(0xfd),  0x01 },
>>>>
>>>> Please replace these with names macros where possible. I'm sure quite a
>>>> few of the registers configured here are documented in the datasheet.
>>>> The registers that configure the mode (analog crop, digital crop,
>>>> binning, skipping, ...) should be computed dynamically from the subdev
>>>> pad format and selection rectangles, not hardcoded.
>>>>
>>> I agree, but we get the sensor settings based on our requirements from
>>> the vendor, i will check if we can get some more info regarding the
>>> crop, binning, skipping etc...
>>
>> Some of this infomation should be available in the datasheet. Use at
>> least the register names that can be found, for those that can't there's
>> not much that could be done.
>>
> Sorry to say that I don't have the details in this case. We have
> previously reached out to the sensor vendor, but they are not willing to
> disclose any of these details. We hope for your understanding of the
> constraints we're facing and truly value your support.
> 

If you have a spec of OV05C10 (I assume you do, as the developer of this
driver), it is not a issue.
Take P0:0x14 as an example, it's named as DPLL_NC_SEL in spec and set to
0x78 in your reglist ov05c10_2888x1808_regs. If define all named
registers rather than the confusing magic hardcode, the driver code will
be more readable and easy to review.
I think this is what Sakari thought.

Thanks,
Dongcheng

> Thanks,
> Pratap
> 
>> -- 
>> Regards,
>>
>> Sakari Ailus
> 
> 


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-07-02  6:08             ` Yan, Dongcheng
@ 2025-07-02  6:12               ` Sakari Ailus
  2025-07-02 16:47                 ` Nirujogi, Pratap
  0 siblings, 1 reply; 60+ messages in thread
From: Sakari Ailus @ 2025-07-02  6:12 UTC (permalink / raw)
  To: Yan, Dongcheng
  Cc: Nirujogi, Pratap, Laurent Pinchart, Hao Yao, Pratap Nirujogi,
	mchehab, hverkuil, bryan.odonoghue, krzk, dave.stevenson,
	hdegoede, jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	dongcheng.yan, jason.z.chen, jimmy.su

Hi Dongcheng, Pratap,

On Wed, Jul 02, 2025 at 02:08:26PM +0800, Yan, Dongcheng wrote:
> Hi Pratap,
> 
> On 7/1/2025 6:31 AM, Nirujogi, Pratap wrote:
> > Hi Sakari, Hi Laurent,
> > 
> > On 6/29/2025 3:40 AM, Sakari Ailus wrote:
> >> Caution: This message originated from an External Source. Use proper
> >> caution when opening attachments, clicking links, or responding.
> >>
> >>
> >> Hi Pratap,
> >>
> >> On 6/17/25 01:33, Nirujogi, Pratap wrote:
> >> ...
> >>>>>>> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
> >>>>>>> + { CCI_REG8(0xfd),  0x00 },
> >>>>>>> + { CCI_REG8(0x20),  0x00 },
> >>>>>>> + { CCI_REG8(0xfd),  0x00 },
> >>>>>>> + { CCI_REG8(0x20),  0x0b },
> >>>>>>> + { CCI_REG8(0xc1),  0x09 },
> >>>>>>> + { CCI_REG8(0x21),  0x06 },
> >>>>>>> + { CCI_REG8(0x14),  0x78 },
> >>>>>>> + { CCI_REG8(0xe7),  0x03 },
> >>>>>>> + { CCI_REG8(0xe7),  0x00 },
> >>>>>>> + { CCI_REG8(0x21),  0x00 },
> >>>>>>> + { CCI_REG8(0xfd),  0x01 },
> >>>>>>> + { CCI_REG8(0x03),  0x00 },
> >>>>>>> + { CCI_REG8(0x04),  0x06 },
> >>>>>>> + { CCI_REG8(0x05),  0x07 },
> >>>>>>> + { CCI_REG8(0x06),  0x44 },
> >>>>>>> + { CCI_REG8(0x07),  0x08 },
> >>>>>>> + { CCI_REG8(0x1b),  0x01 },
> >>>>>>> + { CCI_REG8(0x24),  0xff },
> >>>>>>> + { CCI_REG8(0x32),  0x03 },
> >>>>>>> + { CCI_REG8(0x42),  0x5d },
> >>>>>>> + { CCI_REG8(0x43),  0x08 },
> >>>>>>> + { CCI_REG8(0x44),  0x81 },
> >>>>>>> + { CCI_REG8(0x46),  0x5f },
> >>>>>>> + { CCI_REG8(0x48),  0x18 },
> >>>>>>> + { CCI_REG8(0x49),  0x04 },
> >>>>>>> + { CCI_REG8(0x5c),  0x18 },
> >>>>>>> + { CCI_REG8(0x5e),  0x13 },
> >>>>>>> + { CCI_REG8(0x70),  0x15 },
> >>>>>>> + { CCI_REG8(0x77),  0x35 },
> >>>>>>> + { CCI_REG8(0x79),  0x00 },
> >>>>>>> + { CCI_REG8(0x7b),  0x08 },
> >>>>>>> + { CCI_REG8(0x7d),  0x08 },
> >>>>>>> + { CCI_REG8(0x7e),  0x08 },
> >>>>>>> + { CCI_REG8(0x7f),  0x08 },
> >>>>>>> + { CCI_REG8(0x90),  0x37 },
> >>>>>>> + { CCI_REG8(0x91),  0x05 },
> >>>>>>> + { CCI_REG8(0x92),  0x18 },
> >>>>>>> + { CCI_REG8(0x93),  0x27 },
> >>>>>>> + { CCI_REG8(0x94),  0x05 },
> >>>>>>> + { CCI_REG8(0x95),  0x38 },
> >>>>>>> + { CCI_REG8(0x9b),  0x00 },
> >>>>>>> + { CCI_REG8(0x9c),  0x06 },
> >>>>>>> + { CCI_REG8(0x9d),  0x28 },
> >>>>>>> + { CCI_REG8(0x9e),  0x06 },
> >>>>>>> + { CCI_REG8(0xb2),  0x0f },
> >>>>>>> + { CCI_REG8(0xb3),  0x29 },
> >>>>>>> + { CCI_REG8(0xbf),  0x3c },
> >>>>>>> + { CCI_REG8(0xc2),  0x04 },
> >>>>>>> + { CCI_REG8(0xc4),  0x00 },
> >>>>>>> + { CCI_REG8(0xca),  0x20 },
> >>>>>>> + { CCI_REG8(0xcb),  0x20 },
> >>>>>>> + { CCI_REG8(0xcc),  0x28 },
> >>>>>>> + { CCI_REG8(0xcd),  0x28 },
> >>>>>>> + { CCI_REG8(0xce),  0x20 },
> >>>>>>> + { CCI_REG8(0xcf),  0x20 },
> >>>>>>> + { CCI_REG8(0xd0),  0x2a },
> >>>>>>> + { CCI_REG8(0xd1),  0x2a },
> >>>>>>> + { CCI_REG8(0xfd),  0x0f },
> >>>>>>> + { CCI_REG8(0x00),  0x00 },
> >>>>>>> + { CCI_REG8(0x01),  0xa0 },
> >>>>>>> + { CCI_REG8(0x02),  0x48 },
> >>>>>>> + { CCI_REG8(0x07),  0x8f },
> >>>>>>> + { CCI_REG8(0x08),  0x70 },
> >>>>>>> + { CCI_REG8(0x09),  0x01 },
> >>>>>>> + { CCI_REG8(0x0b),  0x40 },
> >>>>>>> + { CCI_REG8(0x0d),  0x07 },
> >>>>>>> + { CCI_REG8(0x11),  0x33 },
> >>>>>>> + { CCI_REG8(0x12),  0x77 },
> >>>>>>> + { CCI_REG8(0x13),  0x66 },
> >>>>>>> + { CCI_REG8(0x14),  0x65 },
> >>>>>>> + { CCI_REG8(0x15),  0x37 },
> >>>>>>> + { CCI_REG8(0x16),  0xbf },
> >>>>>>> + { CCI_REG8(0x17),  0xff },
> >>>>>>> + { CCI_REG8(0x18),  0xff },
> >>>>>>> + { CCI_REG8(0x19),  0x12 },
> >>>>>>> + { CCI_REG8(0x1a),  0x10 },
> >>>>>>> + { CCI_REG8(0x1c),  0x77 },
> >>>>>>> + { CCI_REG8(0x1d),  0x77 },
> >>>>>>> + { CCI_REG8(0x20),  0x0f },
> >>>>>>> + { CCI_REG8(0x21),  0x0f },
> >>>>>>> + { CCI_REG8(0x22),  0x0f },
> >>>>>>> + { CCI_REG8(0x23),  0x0f },
> >>>>>>> + { CCI_REG8(0x2b),  0x20 },
> >>>>>>> + { CCI_REG8(0x2c),  0x20 },
> >>>>>>> + { CCI_REG8(0x2d),  0x04 },
> >>>>>>> + { CCI_REG8(0xfd),  0x03 },
> >>>>>>> + { CCI_REG8(0x9d),  0x0f },
> >>>>>>> + { CCI_REG8(0x9f),  0x40 },
> >>>>>>> + { CCI_REG8(0xfd),  0x00 },
> >>>>>>> + { CCI_REG8(0x20),  0x1b },
> >>>>>>> + { CCI_REG8(0xfd),  0x04 },
> >>>>>>> + { CCI_REG8(0x19),  0x60 },
> >>>>>>> + { CCI_REG8(0xfd),  0x02 },
> >>>>>>> + { CCI_REG8(0x75),  0x05 },
> >>>>>>> + { CCI_REG8(0x7f),  0x06 },
> >>>>>>> + { CCI_REG8(0x9a),  0x03 },
> >>>>>>> + { CCI_REG8(0xa2),  0x07 },
> >>>>>>> + { CCI_REG8(0xa3),  0x10 },
> >>>>>>> + { CCI_REG8(0xa5),  0x02 },
> >>>>>>> + { CCI_REG8(0xa6),  0x0b },
> >>>>>>> + { CCI_REG8(0xa7),  0x48 },
> >>>>>>> + { CCI_REG8(0xfd),  0x07 },
> >>>>>>> + { CCI_REG8(0x42),  0x00 },
> >>>>>>> + { CCI_REG8(0x43),  0x80 },
> >>>>>>> + { CCI_REG8(0x44),  0x00 },
> >>>>>>> + { CCI_REG8(0x45),  0x80 },
> >>>>>>> + { CCI_REG8(0x46),  0x00 },
> >>>>>>> + { CCI_REG8(0x47),  0x80 },
> >>>>>>> + { CCI_REG8(0x48),  0x00 },
> >>>>>>> + { CCI_REG8(0x49),  0x80 },
> >>>>>>> + { CCI_REG8(0x00),  0xf7 },
> >>>>>>> + { CCI_REG8(0xfd),  0x00 },
> >>>>>>> + { CCI_REG8(0xe7),  0x03 },
> >>>>>>> + { CCI_REG8(0xe7),  0x00 },
> >>>>>>> + { CCI_REG8(0xfd),  0x00 },
> >>>>>>> + { CCI_REG8(0x93),  0x18 },
> >>>>>>> + { CCI_REG8(0x94),  0xff },
> >>>>>>> + { CCI_REG8(0x95),  0xbd },
> >>>>>>> + { CCI_REG8(0x96),  0x1a },
> >>>>>>> + { CCI_REG8(0x98),  0x04 },
> >>>>>>> + { CCI_REG8(0x99),  0x08 },
> >>>>>>> + { CCI_REG8(0x9b),  0x10 },
> >>>>>>> + { CCI_REG8(0x9c),  0x3f },
> >>>>>>> + { CCI_REG8(0xa1),  0x05 },
> >>>>>>> + { CCI_REG8(0xa4),  0x2f },
> >>>>>>> + { CCI_REG8(0xc0),  0x0c },
> >>>>>>> + { CCI_REG8(0xc1),  0x08 },
> >>>>>>> + { CCI_REG8(0xc2),  0x00 },
> >>>>>>> + { CCI_REG8(0xb6),  0x20 },
> >>>>>>> + { CCI_REG8(0xbb),  0x80 },
> >>>>>>> + { CCI_REG8(0xfd),  0x00 },
> >>>>>>> + { CCI_REG8(0xa0),  0x01 },
> >>>>>>> + { CCI_REG8(0xfd),  0x01 },
> >>>>
> >>>> Please replace these with names macros where possible. I'm sure quite a
> >>>> few of the registers configured here are documented in the datasheet.
> >>>> The registers that configure the mode (analog crop, digital crop,
> >>>> binning, skipping, ...) should be computed dynamically from the subdev
> >>>> pad format and selection rectangles, not hardcoded.
> >>>>
> >>> I agree, but we get the sensor settings based on our requirements from
> >>> the vendor, i will check if we can get some more info regarding the
> >>> crop, binning, skipping etc...
> >>
> >> Some of this infomation should be available in the datasheet. Use at
> >> least the register names that can be found, for those that can't there's
> >> not much that could be done.
> >>
> > Sorry to say that I don't have the details in this case. We have
> > previously reached out to the sensor vendor, but they are not willing to
> > disclose any of these details. We hope for your understanding of the
> > constraints we're facing and truly value your support.
> > 
> 
> If you have a spec of OV05C10 (I assume you do, as the developer of this
> driver), it is not a issue.
> Take P0:0x14 as an example, it's named as DPLL_NC_SEL in spec and set to
> 0x78 in your reglist ov05c10_2888x1808_regs. If define all named
> registers rather than the confusing magic hardcode, the driver code will
> be more readable and easy to review.
> I think this is what Sakari thought.

Yes. And even if it happens that a register write slips to a wrong list,
we can fix it later.

-- 
Kind regards,

Sakari Ailus

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-07-02  6:12               ` Sakari Ailus
@ 2025-07-02 16:47                 ` Nirujogi, Pratap
  2025-07-03  6:24                   ` Yan, Dongcheng
  2025-07-06  9:11                   ` Sakari Ailus
  0 siblings, 2 replies; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-07-02 16:47 UTC (permalink / raw)
  To: Sakari Ailus, Yan, Dongcheng
  Cc: Laurent Pinchart, Hao Yao, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su

Hi Sakari, Dongcheng,

On 7/2/2025 2:12 AM, Sakari Ailus wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> Hi Dongcheng, Pratap,
> 
> On Wed, Jul 02, 2025 at 02:08:26PM +0800, Yan, Dongcheng wrote:
>> Hi Pratap,
>>
>> On 7/1/2025 6:31 AM, Nirujogi, Pratap wrote:
>>> Hi Sakari, Hi Laurent,
>>>
>>> On 6/29/2025 3:40 AM, Sakari Ailus wrote:
>>>> Caution: This message originated from an External Source. Use proper
>>>> caution when opening attachments, clicking links, or responding.
>>>>
>>>>
>>>> Hi Pratap,
>>>>
>>>> On 6/17/25 01:33, Nirujogi, Pratap wrote:
>>>> ...
>>>>>>>>> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[] = {
>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>> + { CCI_REG8(0x20),  0x00 },
>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>> + { CCI_REG8(0x20),  0x0b },
>>>>>>>>> + { CCI_REG8(0xc1),  0x09 },
>>>>>>>>> + { CCI_REG8(0x21),  0x06 },
>>>>>>>>> + { CCI_REG8(0x14),  0x78 },
>>>>>>>>> + { CCI_REG8(0xe7),  0x03 },
>>>>>>>>> + { CCI_REG8(0xe7),  0x00 },
>>>>>>>>> + { CCI_REG8(0x21),  0x00 },
>>>>>>>>> + { CCI_REG8(0xfd),  0x01 },
>>>>>>>>> + { CCI_REG8(0x03),  0x00 },
>>>>>>>>> + { CCI_REG8(0x04),  0x06 },
>>>>>>>>> + { CCI_REG8(0x05),  0x07 },
>>>>>>>>> + { CCI_REG8(0x06),  0x44 },
>>>>>>>>> + { CCI_REG8(0x07),  0x08 },
>>>>>>>>> + { CCI_REG8(0x1b),  0x01 },
>>>>>>>>> + { CCI_REG8(0x24),  0xff },
>>>>>>>>> + { CCI_REG8(0x32),  0x03 },
>>>>>>>>> + { CCI_REG8(0x42),  0x5d },
>>>>>>>>> + { CCI_REG8(0x43),  0x08 },
>>>>>>>>> + { CCI_REG8(0x44),  0x81 },
>>>>>>>>> + { CCI_REG8(0x46),  0x5f },
>>>>>>>>> + { CCI_REG8(0x48),  0x18 },
>>>>>>>>> + { CCI_REG8(0x49),  0x04 },
>>>>>>>>> + { CCI_REG8(0x5c),  0x18 },
>>>>>>>>> + { CCI_REG8(0x5e),  0x13 },
>>>>>>>>> + { CCI_REG8(0x70),  0x15 },
>>>>>>>>> + { CCI_REG8(0x77),  0x35 },
>>>>>>>>> + { CCI_REG8(0x79),  0x00 },
>>>>>>>>> + { CCI_REG8(0x7b),  0x08 },
>>>>>>>>> + { CCI_REG8(0x7d),  0x08 },
>>>>>>>>> + { CCI_REG8(0x7e),  0x08 },
>>>>>>>>> + { CCI_REG8(0x7f),  0x08 },
>>>>>>>>> + { CCI_REG8(0x90),  0x37 },
>>>>>>>>> + { CCI_REG8(0x91),  0x05 },
>>>>>>>>> + { CCI_REG8(0x92),  0x18 },
>>>>>>>>> + { CCI_REG8(0x93),  0x27 },
>>>>>>>>> + { CCI_REG8(0x94),  0x05 },
>>>>>>>>> + { CCI_REG8(0x95),  0x38 },
>>>>>>>>> + { CCI_REG8(0x9b),  0x00 },
>>>>>>>>> + { CCI_REG8(0x9c),  0x06 },
>>>>>>>>> + { CCI_REG8(0x9d),  0x28 },
>>>>>>>>> + { CCI_REG8(0x9e),  0x06 },
>>>>>>>>> + { CCI_REG8(0xb2),  0x0f },
>>>>>>>>> + { CCI_REG8(0xb3),  0x29 },
>>>>>>>>> + { CCI_REG8(0xbf),  0x3c },
>>>>>>>>> + { CCI_REG8(0xc2),  0x04 },
>>>>>>>>> + { CCI_REG8(0xc4),  0x00 },
>>>>>>>>> + { CCI_REG8(0xca),  0x20 },
>>>>>>>>> + { CCI_REG8(0xcb),  0x20 },
>>>>>>>>> + { CCI_REG8(0xcc),  0x28 },
>>>>>>>>> + { CCI_REG8(0xcd),  0x28 },
>>>>>>>>> + { CCI_REG8(0xce),  0x20 },
>>>>>>>>> + { CCI_REG8(0xcf),  0x20 },
>>>>>>>>> + { CCI_REG8(0xd0),  0x2a },
>>>>>>>>> + { CCI_REG8(0xd1),  0x2a },
>>>>>>>>> + { CCI_REG8(0xfd),  0x0f },
>>>>>>>>> + { CCI_REG8(0x00),  0x00 },
>>>>>>>>> + { CCI_REG8(0x01),  0xa0 },
>>>>>>>>> + { CCI_REG8(0x02),  0x48 },
>>>>>>>>> + { CCI_REG8(0x07),  0x8f },
>>>>>>>>> + { CCI_REG8(0x08),  0x70 },
>>>>>>>>> + { CCI_REG8(0x09),  0x01 },
>>>>>>>>> + { CCI_REG8(0x0b),  0x40 },
>>>>>>>>> + { CCI_REG8(0x0d),  0x07 },
>>>>>>>>> + { CCI_REG8(0x11),  0x33 },
>>>>>>>>> + { CCI_REG8(0x12),  0x77 },
>>>>>>>>> + { CCI_REG8(0x13),  0x66 },
>>>>>>>>> + { CCI_REG8(0x14),  0x65 },
>>>>>>>>> + { CCI_REG8(0x15),  0x37 },
>>>>>>>>> + { CCI_REG8(0x16),  0xbf },
>>>>>>>>> + { CCI_REG8(0x17),  0xff },
>>>>>>>>> + { CCI_REG8(0x18),  0xff },
>>>>>>>>> + { CCI_REG8(0x19),  0x12 },
>>>>>>>>> + { CCI_REG8(0x1a),  0x10 },
>>>>>>>>> + { CCI_REG8(0x1c),  0x77 },
>>>>>>>>> + { CCI_REG8(0x1d),  0x77 },
>>>>>>>>> + { CCI_REG8(0x20),  0x0f },
>>>>>>>>> + { CCI_REG8(0x21),  0x0f },
>>>>>>>>> + { CCI_REG8(0x22),  0x0f },
>>>>>>>>> + { CCI_REG8(0x23),  0x0f },
>>>>>>>>> + { CCI_REG8(0x2b),  0x20 },
>>>>>>>>> + { CCI_REG8(0x2c),  0x20 },
>>>>>>>>> + { CCI_REG8(0x2d),  0x04 },
>>>>>>>>> + { CCI_REG8(0xfd),  0x03 },
>>>>>>>>> + { CCI_REG8(0x9d),  0x0f },
>>>>>>>>> + { CCI_REG8(0x9f),  0x40 },
>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>> + { CCI_REG8(0x20),  0x1b },
>>>>>>>>> + { CCI_REG8(0xfd),  0x04 },
>>>>>>>>> + { CCI_REG8(0x19),  0x60 },
>>>>>>>>> + { CCI_REG8(0xfd),  0x02 },
>>>>>>>>> + { CCI_REG8(0x75),  0x05 },
>>>>>>>>> + { CCI_REG8(0x7f),  0x06 },
>>>>>>>>> + { CCI_REG8(0x9a),  0x03 },
>>>>>>>>> + { CCI_REG8(0xa2),  0x07 },
>>>>>>>>> + { CCI_REG8(0xa3),  0x10 },
>>>>>>>>> + { CCI_REG8(0xa5),  0x02 },
>>>>>>>>> + { CCI_REG8(0xa6),  0x0b },
>>>>>>>>> + { CCI_REG8(0xa7),  0x48 },
>>>>>>>>> + { CCI_REG8(0xfd),  0x07 },
>>>>>>>>> + { CCI_REG8(0x42),  0x00 },
>>>>>>>>> + { CCI_REG8(0x43),  0x80 },
>>>>>>>>> + { CCI_REG8(0x44),  0x00 },
>>>>>>>>> + { CCI_REG8(0x45),  0x80 },
>>>>>>>>> + { CCI_REG8(0x46),  0x00 },
>>>>>>>>> + { CCI_REG8(0x47),  0x80 },
>>>>>>>>> + { CCI_REG8(0x48),  0x00 },
>>>>>>>>> + { CCI_REG8(0x49),  0x80 },
>>>>>>>>> + { CCI_REG8(0x00),  0xf7 },
>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>> + { CCI_REG8(0xe7),  0x03 },
>>>>>>>>> + { CCI_REG8(0xe7),  0x00 },
>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>> + { CCI_REG8(0x93),  0x18 },
>>>>>>>>> + { CCI_REG8(0x94),  0xff },
>>>>>>>>> + { CCI_REG8(0x95),  0xbd },
>>>>>>>>> + { CCI_REG8(0x96),  0x1a },
>>>>>>>>> + { CCI_REG8(0x98),  0x04 },
>>>>>>>>> + { CCI_REG8(0x99),  0x08 },
>>>>>>>>> + { CCI_REG8(0x9b),  0x10 },
>>>>>>>>> + { CCI_REG8(0x9c),  0x3f },
>>>>>>>>> + { CCI_REG8(0xa1),  0x05 },
>>>>>>>>> + { CCI_REG8(0xa4),  0x2f },
>>>>>>>>> + { CCI_REG8(0xc0),  0x0c },
>>>>>>>>> + { CCI_REG8(0xc1),  0x08 },
>>>>>>>>> + { CCI_REG8(0xc2),  0x00 },
>>>>>>>>> + { CCI_REG8(0xb6),  0x20 },
>>>>>>>>> + { CCI_REG8(0xbb),  0x80 },
>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>> + { CCI_REG8(0xa0),  0x01 },
>>>>>>>>> + { CCI_REG8(0xfd),  0x01 },
>>>>>>
>>>>>> Please replace these with names macros where possible. I'm sure quite a
>>>>>> few of the registers configured here are documented in the datasheet.
>>>>>> The registers that configure the mode (analog crop, digital crop,
>>>>>> binning, skipping, ...) should be computed dynamically from the subdev
>>>>>> pad format and selection rectangles, not hardcoded.
>>>>>>
>>>>> I agree, but we get the sensor settings based on our requirements from
>>>>> the vendor, i will check if we can get some more info regarding the
>>>>> crop, binning, skipping etc...
>>>>
>>>> Some of this infomation should be available in the datasheet. Use at
>>>> least the register names that can be found, for those that can't there's
>>>> not much that could be done.
>>>>
>>> Sorry to say that I don't have the details in this case. We have
>>> previously reached out to the sensor vendor, but they are not willing to
>>> disclose any of these details. We hope for your understanding of the
>>> constraints we're facing and truly value your support.
>>>
>>
>> If you have a spec of OV05C10 (I assume you do, as the developer of this
>> driver), it is not a issue.
>> Take P0:0x14 as an example, it's named as DPLL_NC_SEL in spec and set to
>> 0x78 in your reglist ov05c10_2888x1808_regs. If define all named
>> registers rather than the confusing magic hardcode, the driver code will
>> be more readable and easy to review.
>> I think this is what Sakari thought.
> 
> Yes. And even if it happens that a register write slips to a wrong list,
> we can fix it later.
> 
I agree with the suggestion on proper naming of register offsets, but 
unfortunately we lack access to the spec. We are completely relying on 
the sensor vendor for these sequences, which they are not willing to 
share the details.

Thanks,
Pratap

> --
> Kind regards,
> 
> Sakari Ailus


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-07-02 16:47                 ` Nirujogi, Pratap
@ 2025-07-03  6:24                   ` Yan, Dongcheng
  2025-07-05 11:45                     ` Nirujogi, Pratap
  2025-07-06  9:11                   ` Sakari Ailus
  1 sibling, 1 reply; 60+ messages in thread
From: Yan, Dongcheng @ 2025-07-03  6:24 UTC (permalink / raw)
  To: Nirujogi, Pratap, Sakari Ailus
  Cc: Laurent Pinchart, Hao Yao, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su



On 7/3/2025 12:47 AM, Nirujogi, Pratap wrote:
> Hi Sakari, Dongcheng,
> 
> On 7/2/2025 2:12 AM, Sakari Ailus wrote:
>> Caution: This message originated from an External Source. Use proper
>> caution when opening attachments, clicking links, or responding.
>>
>>
>> Hi Dongcheng, Pratap,
>>
>> On Wed, Jul 02, 2025 at 02:08:26PM +0800, Yan, Dongcheng wrote:
>>> Hi Pratap,
>>>
>>> On 7/1/2025 6:31 AM, Nirujogi, Pratap wrote:
>>>> Hi Sakari, Hi Laurent,
>>>>
>>>> On 6/29/2025 3:40 AM, Sakari Ailus wrote:
>>>>> Caution: This message originated from an External Source. Use proper
>>>>> caution when opening attachments, clicking links, or responding.
>>>>>
>>>>>
>>>>> Hi Pratap,
>>>>>
>>>>> On 6/17/25 01:33, Nirujogi, Pratap wrote:
>>>>> ...
>>>>>>>>>> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[]
>>>>>>>>>> = {
>>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>>> + { CCI_REG8(0x20),  0x00 },
>>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>>> + { CCI_REG8(0x20),  0x0b },
>>>>>>>>>> + { CCI_REG8(0xc1),  0x09 },
>>>>>>>>>> + { CCI_REG8(0x21),  0x06 },
>>>>>>>>>> + { CCI_REG8(0x14),  0x78 },
>>>>>>>>>> + { CCI_REG8(0xe7),  0x03 },
>>>>>>>>>> + { CCI_REG8(0xe7),  0x00 },
>>>>>>>>>> + { CCI_REG8(0x21),  0x00 },
>>>>>>>>>> + { CCI_REG8(0xfd),  0x01 },
>>>>>>>>>> + { CCI_REG8(0x03),  0x00 },
>>>>>>>>>> + { CCI_REG8(0x04),  0x06 },
>>>>>>>>>> + { CCI_REG8(0x05),  0x07 },
>>>>>>>>>> + { CCI_REG8(0x06),  0x44 },
>>>>>>>>>> + { CCI_REG8(0x07),  0x08 },
>>>>>>>>>> + { CCI_REG8(0x1b),  0x01 },
>>>>>>>>>> + { CCI_REG8(0x24),  0xff },
>>>>>>>>>> + { CCI_REG8(0x32),  0x03 },
>>>>>>>>>> + { CCI_REG8(0x42),  0x5d },
>>>>>>>>>> + { CCI_REG8(0x43),  0x08 },
>>>>>>>>>> + { CCI_REG8(0x44),  0x81 },
>>>>>>>>>> + { CCI_REG8(0x46),  0x5f },
>>>>>>>>>> + { CCI_REG8(0x48),  0x18 },
>>>>>>>>>> + { CCI_REG8(0x49),  0x04 },
>>>>>>>>>> + { CCI_REG8(0x5c),  0x18 },
>>>>>>>>>> + { CCI_REG8(0x5e),  0x13 },
>>>>>>>>>> + { CCI_REG8(0x70),  0x15 },
>>>>>>>>>> + { CCI_REG8(0x77),  0x35 },
>>>>>>>>>> + { CCI_REG8(0x79),  0x00 },
>>>>>>>>>> + { CCI_REG8(0x7b),  0x08 },
>>>>>>>>>> + { CCI_REG8(0x7d),  0x08 },
>>>>>>>>>> + { CCI_REG8(0x7e),  0x08 },
>>>>>>>>>> + { CCI_REG8(0x7f),  0x08 },
>>>>>>>>>> + { CCI_REG8(0x90),  0x37 },
>>>>>>>>>> + { CCI_REG8(0x91),  0x05 },
>>>>>>>>>> + { CCI_REG8(0x92),  0x18 },
>>>>>>>>>> + { CCI_REG8(0x93),  0x27 },
>>>>>>>>>> + { CCI_REG8(0x94),  0x05 },
>>>>>>>>>> + { CCI_REG8(0x95),  0x38 },
>>>>>>>>>> + { CCI_REG8(0x9b),  0x00 },
>>>>>>>>>> + { CCI_REG8(0x9c),  0x06 },
>>>>>>>>>> + { CCI_REG8(0x9d),  0x28 },
>>>>>>>>>> + { CCI_REG8(0x9e),  0x06 },
>>>>>>>>>> + { CCI_REG8(0xb2),  0x0f },
>>>>>>>>>> + { CCI_REG8(0xb3),  0x29 },
>>>>>>>>>> + { CCI_REG8(0xbf),  0x3c },
>>>>>>>>>> + { CCI_REG8(0xc2),  0x04 },
>>>>>>>>>> + { CCI_REG8(0xc4),  0x00 },
>>>>>>>>>> + { CCI_REG8(0xca),  0x20 },
>>>>>>>>>> + { CCI_REG8(0xcb),  0x20 },
>>>>>>>>>> + { CCI_REG8(0xcc),  0x28 },
>>>>>>>>>> + { CCI_REG8(0xcd),  0x28 },
>>>>>>>>>> + { CCI_REG8(0xce),  0x20 },
>>>>>>>>>> + { CCI_REG8(0xcf),  0x20 },
>>>>>>>>>> + { CCI_REG8(0xd0),  0x2a },
>>>>>>>>>> + { CCI_REG8(0xd1),  0x2a },
>>>>>>>>>> + { CCI_REG8(0xfd),  0x0f },
>>>>>>>>>> + { CCI_REG8(0x00),  0x00 },
>>>>>>>>>> + { CCI_REG8(0x01),  0xa0 },
>>>>>>>>>> + { CCI_REG8(0x02),  0x48 },
>>>>>>>>>> + { CCI_REG8(0x07),  0x8f },
>>>>>>>>>> + { CCI_REG8(0x08),  0x70 },
>>>>>>>>>> + { CCI_REG8(0x09),  0x01 },
>>>>>>>>>> + { CCI_REG8(0x0b),  0x40 },
>>>>>>>>>> + { CCI_REG8(0x0d),  0x07 },
>>>>>>>>>> + { CCI_REG8(0x11),  0x33 },
>>>>>>>>>> + { CCI_REG8(0x12),  0x77 },
>>>>>>>>>> + { CCI_REG8(0x13),  0x66 },
>>>>>>>>>> + { CCI_REG8(0x14),  0x65 },
>>>>>>>>>> + { CCI_REG8(0x15),  0x37 },
>>>>>>>>>> + { CCI_REG8(0x16),  0xbf },
>>>>>>>>>> + { CCI_REG8(0x17),  0xff },
>>>>>>>>>> + { CCI_REG8(0x18),  0xff },
>>>>>>>>>> + { CCI_REG8(0x19),  0x12 },
>>>>>>>>>> + { CCI_REG8(0x1a),  0x10 },
>>>>>>>>>> + { CCI_REG8(0x1c),  0x77 },
>>>>>>>>>> + { CCI_REG8(0x1d),  0x77 },
>>>>>>>>>> + { CCI_REG8(0x20),  0x0f },
>>>>>>>>>> + { CCI_REG8(0x21),  0x0f },
>>>>>>>>>> + { CCI_REG8(0x22),  0x0f },
>>>>>>>>>> + { CCI_REG8(0x23),  0x0f },
>>>>>>>>>> + { CCI_REG8(0x2b),  0x20 },
>>>>>>>>>> + { CCI_REG8(0x2c),  0x20 },
>>>>>>>>>> + { CCI_REG8(0x2d),  0x04 },
>>>>>>>>>> + { CCI_REG8(0xfd),  0x03 },
>>>>>>>>>> + { CCI_REG8(0x9d),  0x0f },
>>>>>>>>>> + { CCI_REG8(0x9f),  0x40 },
>>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>>> + { CCI_REG8(0x20),  0x1b },
>>>>>>>>>> + { CCI_REG8(0xfd),  0x04 },
>>>>>>>>>> + { CCI_REG8(0x19),  0x60 },
>>>>>>>>>> + { CCI_REG8(0xfd),  0x02 },
>>>>>>>>>> + { CCI_REG8(0x75),  0x05 },
>>>>>>>>>> + { CCI_REG8(0x7f),  0x06 },
>>>>>>>>>> + { CCI_REG8(0x9a),  0x03 },
>>>>>>>>>> + { CCI_REG8(0xa2),  0x07 },
>>>>>>>>>> + { CCI_REG8(0xa3),  0x10 },
>>>>>>>>>> + { CCI_REG8(0xa5),  0x02 },
>>>>>>>>>> + { CCI_REG8(0xa6),  0x0b },
>>>>>>>>>> + { CCI_REG8(0xa7),  0x48 },
>>>>>>>>>> + { CCI_REG8(0xfd),  0x07 },
>>>>>>>>>> + { CCI_REG8(0x42),  0x00 },
>>>>>>>>>> + { CCI_REG8(0x43),  0x80 },
>>>>>>>>>> + { CCI_REG8(0x44),  0x00 },
>>>>>>>>>> + { CCI_REG8(0x45),  0x80 },
>>>>>>>>>> + { CCI_REG8(0x46),  0x00 },
>>>>>>>>>> + { CCI_REG8(0x47),  0x80 },
>>>>>>>>>> + { CCI_REG8(0x48),  0x00 },
>>>>>>>>>> + { CCI_REG8(0x49),  0x80 },
>>>>>>>>>> + { CCI_REG8(0x00),  0xf7 },
>>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>>> + { CCI_REG8(0xe7),  0x03 },
>>>>>>>>>> + { CCI_REG8(0xe7),  0x00 },
>>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>>> + { CCI_REG8(0x93),  0x18 },
>>>>>>>>>> + { CCI_REG8(0x94),  0xff },
>>>>>>>>>> + { CCI_REG8(0x95),  0xbd },
>>>>>>>>>> + { CCI_REG8(0x96),  0x1a },
>>>>>>>>>> + { CCI_REG8(0x98),  0x04 },
>>>>>>>>>> + { CCI_REG8(0x99),  0x08 },
>>>>>>>>>> + { CCI_REG8(0x9b),  0x10 },
>>>>>>>>>> + { CCI_REG8(0x9c),  0x3f },
>>>>>>>>>> + { CCI_REG8(0xa1),  0x05 },
>>>>>>>>>> + { CCI_REG8(0xa4),  0x2f },
>>>>>>>>>> + { CCI_REG8(0xc0),  0x0c },
>>>>>>>>>> + { CCI_REG8(0xc1),  0x08 },
>>>>>>>>>> + { CCI_REG8(0xc2),  0x00 },
>>>>>>>>>> + { CCI_REG8(0xb6),  0x20 },
>>>>>>>>>> + { CCI_REG8(0xbb),  0x80 },
>>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>>> + { CCI_REG8(0xa0),  0x01 },
>>>>>>>>>> + { CCI_REG8(0xfd),  0x01 },
>>>>>>>
>>>>>>> Please replace these with names macros where possible. I'm sure
>>>>>>> quite a
>>>>>>> few of the registers configured here are documented in the
>>>>>>> datasheet.
>>>>>>> The registers that configure the mode (analog crop, digital crop,
>>>>>>> binning, skipping, ...) should be computed dynamically from the
>>>>>>> subdev
>>>>>>> pad format and selection rectangles, not hardcoded.
>>>>>>>
>>>>>> I agree, but we get the sensor settings based on our requirements
>>>>>> from
>>>>>> the vendor, i will check if we can get some more info regarding the
>>>>>> crop, binning, skipping etc...
>>>>>
>>>>> Some of this infomation should be available in the datasheet. Use at
>>>>> least the register names that can be found, for those that can't
>>>>> there's
>>>>> not much that could be done.
>>>>>
>>>> Sorry to say that I don't have the details in this case. We have
>>>> previously reached out to the sensor vendor, but they are not
>>>> willing to
>>>> disclose any of these details. We hope for your understanding of the
>>>> constraints we're facing and truly value your support.
>>>>
>>>
>>> If you have a spec of OV05C10 (I assume you do, as the developer of this
>>> driver), it is not a issue.
>>> Take P0:0x14 as an example, it's named as DPLL_NC_SEL in spec and set to
>>> 0x78 in your reglist ov05c10_2888x1808_regs. If define all named
>>> registers rather than the confusing magic hardcode, the driver code will
>>> be more readable and easy to review.
>>> I think this is what Sakari thought.
>>
>> Yes. And even if it happens that a register write slips to a wrong list,
>> we can fix it later.
>>
> I agree with the suggestion on proper naming of register offsets, but
> unfortunately we lack access to the spec.

Do you mean this driver is developed without spec? Noticing that
OV05C10_*CTL_PAGE and OV05C10_REG_* are defined quite standard, I never
doubt it. Excuse me for being a little straightforward, I even doubt
whether this driver can work properly.

 We are completely relying on
> the sensor vendor for these sequences, which they are not willing to
> share the details.
> 
> Thanks,
> Pratap
> 
>> -- 
>> Kind regards,
>>
>> Sakari Ailus
> 


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-07-03  6:24                   ` Yan, Dongcheng
@ 2025-07-05 11:45                     ` Nirujogi, Pratap
  0 siblings, 0 replies; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-07-05 11:45 UTC (permalink / raw)
  To: Yan, Dongcheng, Sakari Ailus
  Cc: Laurent Pinchart, Hao Yao, Pratap Nirujogi, mchehab, hverkuil,
	bryan.odonoghue, krzk, dave.stevenson, hdegoede, jai.luthra,
	tomi.valkeinen, linux-media, linux-kernel, benjamin.chan, bin.du,
	grosikop, king.li, dantony, vengutta, dongcheng.yan, jason.z.chen,
	jimmy.su

Hi Dongcheng,

On 7/3/2025 2:24 AM, Yan, Dongcheng wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> On 7/3/2025 12:47 AM, Nirujogi, Pratap wrote:
>> Hi Sakari, Dongcheng,
>>
>> On 7/2/2025 2:12 AM, Sakari Ailus wrote:
>>> Caution: This message originated from an External Source. Use proper
>>> caution when opening attachments, clicking links, or responding.
>>>
>>>
>>> Hi Dongcheng, Pratap,
>>>
>>> On Wed, Jul 02, 2025 at 02:08:26PM +0800, Yan, Dongcheng wrote:
>>>> Hi Pratap,
>>>>
>>>> On 7/1/2025 6:31 AM, Nirujogi, Pratap wrote:
>>>>> Hi Sakari, Hi Laurent,
>>>>>
>>>>> On 6/29/2025 3:40 AM, Sakari Ailus wrote:
>>>>>> Caution: This message originated from an External Source. Use proper
>>>>>> caution when opening attachments, clicking links, or responding.
>>>>>>
>>>>>>
>>>>>> Hi Pratap,
>>>>>>
>>>>>> On 6/17/25 01:33, Nirujogi, Pratap wrote:
>>>>>> ...
>>>>>>>>>>> +static const struct cci_reg_sequence ov05c10_2888x1808_regs[]
>>>>>>>>>>> = {
>>>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0x20),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0x20),  0x0b },
>>>>>>>>>>> + { CCI_REG8(0xc1),  0x09 },
>>>>>>>>>>> + { CCI_REG8(0x21),  0x06 },
>>>>>>>>>>> + { CCI_REG8(0x14),  0x78 },
>>>>>>>>>>> + { CCI_REG8(0xe7),  0x03 },
>>>>>>>>>>> + { CCI_REG8(0xe7),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0x21),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0xfd),  0x01 },
>>>>>>>>>>> + { CCI_REG8(0x03),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0x04),  0x06 },
>>>>>>>>>>> + { CCI_REG8(0x05),  0x07 },
>>>>>>>>>>> + { CCI_REG8(0x06),  0x44 },
>>>>>>>>>>> + { CCI_REG8(0x07),  0x08 },
>>>>>>>>>>> + { CCI_REG8(0x1b),  0x01 },
>>>>>>>>>>> + { CCI_REG8(0x24),  0xff },
>>>>>>>>>>> + { CCI_REG8(0x32),  0x03 },
>>>>>>>>>>> + { CCI_REG8(0x42),  0x5d },
>>>>>>>>>>> + { CCI_REG8(0x43),  0x08 },
>>>>>>>>>>> + { CCI_REG8(0x44),  0x81 },
>>>>>>>>>>> + { CCI_REG8(0x46),  0x5f },
>>>>>>>>>>> + { CCI_REG8(0x48),  0x18 },
>>>>>>>>>>> + { CCI_REG8(0x49),  0x04 },
>>>>>>>>>>> + { CCI_REG8(0x5c),  0x18 },
>>>>>>>>>>> + { CCI_REG8(0x5e),  0x13 },
>>>>>>>>>>> + { CCI_REG8(0x70),  0x15 },
>>>>>>>>>>> + { CCI_REG8(0x77),  0x35 },
>>>>>>>>>>> + { CCI_REG8(0x79),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0x7b),  0x08 },
>>>>>>>>>>> + { CCI_REG8(0x7d),  0x08 },
>>>>>>>>>>> + { CCI_REG8(0x7e),  0x08 },
>>>>>>>>>>> + { CCI_REG8(0x7f),  0x08 },
>>>>>>>>>>> + { CCI_REG8(0x90),  0x37 },
>>>>>>>>>>> + { CCI_REG8(0x91),  0x05 },
>>>>>>>>>>> + { CCI_REG8(0x92),  0x18 },
>>>>>>>>>>> + { CCI_REG8(0x93),  0x27 },
>>>>>>>>>>> + { CCI_REG8(0x94),  0x05 },
>>>>>>>>>>> + { CCI_REG8(0x95),  0x38 },
>>>>>>>>>>> + { CCI_REG8(0x9b),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0x9c),  0x06 },
>>>>>>>>>>> + { CCI_REG8(0x9d),  0x28 },
>>>>>>>>>>> + { CCI_REG8(0x9e),  0x06 },
>>>>>>>>>>> + { CCI_REG8(0xb2),  0x0f },
>>>>>>>>>>> + { CCI_REG8(0xb3),  0x29 },
>>>>>>>>>>> + { CCI_REG8(0xbf),  0x3c },
>>>>>>>>>>> + { CCI_REG8(0xc2),  0x04 },
>>>>>>>>>>> + { CCI_REG8(0xc4),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0xca),  0x20 },
>>>>>>>>>>> + { CCI_REG8(0xcb),  0x20 },
>>>>>>>>>>> + { CCI_REG8(0xcc),  0x28 },
>>>>>>>>>>> + { CCI_REG8(0xcd),  0x28 },
>>>>>>>>>>> + { CCI_REG8(0xce),  0x20 },
>>>>>>>>>>> + { CCI_REG8(0xcf),  0x20 },
>>>>>>>>>>> + { CCI_REG8(0xd0),  0x2a },
>>>>>>>>>>> + { CCI_REG8(0xd1),  0x2a },
>>>>>>>>>>> + { CCI_REG8(0xfd),  0x0f },
>>>>>>>>>>> + { CCI_REG8(0x00),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0x01),  0xa0 },
>>>>>>>>>>> + { CCI_REG8(0x02),  0x48 },
>>>>>>>>>>> + { CCI_REG8(0x07),  0x8f },
>>>>>>>>>>> + { CCI_REG8(0x08),  0x70 },
>>>>>>>>>>> + { CCI_REG8(0x09),  0x01 },
>>>>>>>>>>> + { CCI_REG8(0x0b),  0x40 },
>>>>>>>>>>> + { CCI_REG8(0x0d),  0x07 },
>>>>>>>>>>> + { CCI_REG8(0x11),  0x33 },
>>>>>>>>>>> + { CCI_REG8(0x12),  0x77 },
>>>>>>>>>>> + { CCI_REG8(0x13),  0x66 },
>>>>>>>>>>> + { CCI_REG8(0x14),  0x65 },
>>>>>>>>>>> + { CCI_REG8(0x15),  0x37 },
>>>>>>>>>>> + { CCI_REG8(0x16),  0xbf },
>>>>>>>>>>> + { CCI_REG8(0x17),  0xff },
>>>>>>>>>>> + { CCI_REG8(0x18),  0xff },
>>>>>>>>>>> + { CCI_REG8(0x19),  0x12 },
>>>>>>>>>>> + { CCI_REG8(0x1a),  0x10 },
>>>>>>>>>>> + { CCI_REG8(0x1c),  0x77 },
>>>>>>>>>>> + { CCI_REG8(0x1d),  0x77 },
>>>>>>>>>>> + { CCI_REG8(0x20),  0x0f },
>>>>>>>>>>> + { CCI_REG8(0x21),  0x0f },
>>>>>>>>>>> + { CCI_REG8(0x22),  0x0f },
>>>>>>>>>>> + { CCI_REG8(0x23),  0x0f },
>>>>>>>>>>> + { CCI_REG8(0x2b),  0x20 },
>>>>>>>>>>> + { CCI_REG8(0x2c),  0x20 },
>>>>>>>>>>> + { CCI_REG8(0x2d),  0x04 },
>>>>>>>>>>> + { CCI_REG8(0xfd),  0x03 },
>>>>>>>>>>> + { CCI_REG8(0x9d),  0x0f },
>>>>>>>>>>> + { CCI_REG8(0x9f),  0x40 },
>>>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0x20),  0x1b },
>>>>>>>>>>> + { CCI_REG8(0xfd),  0x04 },
>>>>>>>>>>> + { CCI_REG8(0x19),  0x60 },
>>>>>>>>>>> + { CCI_REG8(0xfd),  0x02 },
>>>>>>>>>>> + { CCI_REG8(0x75),  0x05 },
>>>>>>>>>>> + { CCI_REG8(0x7f),  0x06 },
>>>>>>>>>>> + { CCI_REG8(0x9a),  0x03 },
>>>>>>>>>>> + { CCI_REG8(0xa2),  0x07 },
>>>>>>>>>>> + { CCI_REG8(0xa3),  0x10 },
>>>>>>>>>>> + { CCI_REG8(0xa5),  0x02 },
>>>>>>>>>>> + { CCI_REG8(0xa6),  0x0b },
>>>>>>>>>>> + { CCI_REG8(0xa7),  0x48 },
>>>>>>>>>>> + { CCI_REG8(0xfd),  0x07 },
>>>>>>>>>>> + { CCI_REG8(0x42),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0x43),  0x80 },
>>>>>>>>>>> + { CCI_REG8(0x44),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0x45),  0x80 },
>>>>>>>>>>> + { CCI_REG8(0x46),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0x47),  0x80 },
>>>>>>>>>>> + { CCI_REG8(0x48),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0x49),  0x80 },
>>>>>>>>>>> + { CCI_REG8(0x00),  0xf7 },
>>>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0xe7),  0x03 },
>>>>>>>>>>> + { CCI_REG8(0xe7),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0x93),  0x18 },
>>>>>>>>>>> + { CCI_REG8(0x94),  0xff },
>>>>>>>>>>> + { CCI_REG8(0x95),  0xbd },
>>>>>>>>>>> + { CCI_REG8(0x96),  0x1a },
>>>>>>>>>>> + { CCI_REG8(0x98),  0x04 },
>>>>>>>>>>> + { CCI_REG8(0x99),  0x08 },
>>>>>>>>>>> + { CCI_REG8(0x9b),  0x10 },
>>>>>>>>>>> + { CCI_REG8(0x9c),  0x3f },
>>>>>>>>>>> + { CCI_REG8(0xa1),  0x05 },
>>>>>>>>>>> + { CCI_REG8(0xa4),  0x2f },
>>>>>>>>>>> + { CCI_REG8(0xc0),  0x0c },
>>>>>>>>>>> + { CCI_REG8(0xc1),  0x08 },
>>>>>>>>>>> + { CCI_REG8(0xc2),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0xb6),  0x20 },
>>>>>>>>>>> + { CCI_REG8(0xbb),  0x80 },
>>>>>>>>>>> + { CCI_REG8(0xfd),  0x00 },
>>>>>>>>>>> + { CCI_REG8(0xa0),  0x01 },
>>>>>>>>>>> + { CCI_REG8(0xfd),  0x01 },
>>>>>>>>
>>>>>>>> Please replace these with names macros where possible. I'm sure
>>>>>>>> quite a
>>>>>>>> few of the registers configured here are documented in the
>>>>>>>> datasheet.
>>>>>>>> The registers that configure the mode (analog crop, digital crop,
>>>>>>>> binning, skipping, ...) should be computed dynamically from the
>>>>>>>> subdev
>>>>>>>> pad format and selection rectangles, not hardcoded.
>>>>>>>>
>>>>>>> I agree, but we get the sensor settings based on our requirements
>>>>>>> from
>>>>>>> the vendor, i will check if we can get some more info regarding the
>>>>>>> crop, binning, skipping etc...
>>>>>>
>>>>>> Some of this infomation should be available in the datasheet. Use at
>>>>>> least the register names that can be found, for those that can't
>>>>>> there's
>>>>>> not much that could be done.
>>>>>>
>>>>> Sorry to say that I don't have the details in this case. We have
>>>>> previously reached out to the sensor vendor, but they are not
>>>>> willing to
>>>>> disclose any of these details. We hope for your understanding of the
>>>>> constraints we're facing and truly value your support.
>>>>>
>>>>
>>>> If you have a spec of OV05C10 (I assume you do, as the developer of this
>>>> driver), it is not a issue.
>>>> Take P0:0x14 as an example, it's named as DPLL_NC_SEL in spec and set to
>>>> 0x78 in your reglist ov05c10_2888x1808_regs. If define all named
>>>> registers rather than the confusing magic hardcode, the driver code will
>>>> be more readable and easy to review.
>>>> I think this is what Sakari thought.
>>>
>>> Yes. And even if it happens that a register write slips to a wrong list,
>>> we can fix it later.
>>>
>> I agree with the suggestion on proper naming of register offsets, but
>> unfortunately we lack access to the spec.
> 
> Do you mean this driver is developed without spec? Noticing that
> OV05C10_*CTL_PAGE and OV05C10_REG_* are defined quite standard, I never
> doubt it. Excuse me for being a little straightforward, I even doubt
> whether this driver can work properly.
> 
I can confirm that the driver works reliably. This was verified with the 
ISP patch series submitted for review. Please refer 
https://lore.kernel.org/lkml/20250618091959.68293-1-Bin.Du@amd.com/

We have incorporated all register names provided by the sensor vendor. 
Unfortunately, for the remaining registers, we don't have the details 
from the vendor. Maybe in future when the sensor vendor choose to share 
the details, we will be happy to submit an updated patch accordingly.

Thanks,
Pratap

>   We are completely relying on
>> the sensor vendor for these sequences, which they are not willing to
>> share the details.
>>
>> Thanks,
>> Pratap
>>
>>> --
>>> Kind regards,
>>>
>>> Sakari Ailus
>>
> 


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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-07-02 16:47                 ` Nirujogi, Pratap
  2025-07-03  6:24                   ` Yan, Dongcheng
@ 2025-07-06  9:11                   ` Sakari Ailus
  2025-07-08 15:31                     ` Nirujogi, Pratap
  1 sibling, 1 reply; 60+ messages in thread
From: Sakari Ailus @ 2025-07-06  9:11 UTC (permalink / raw)
  To: Nirujogi, Pratap
  Cc: Yan, Dongcheng, Laurent Pinchart, Hao Yao, Pratap Nirujogi,
	mchehab, hverkuil, bryan.odonoghue, krzk, dave.stevenson,
	hdegoede, jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	dongcheng.yan, jason.z.chen, jimmy.su

Hi Pratap,

On Wed, Jul 02, 2025 at 12:47:40PM -0400, Nirujogi, Pratap wrote:
> > > If you have a spec of OV05C10 (I assume you do, as the developer of this
> > > driver), it is not a issue.
> > > Take P0:0x14 as an example, it's named as DPLL_NC_SEL in spec and set to
> > > 0x78 in your reglist ov05c10_2888x1808_regs. If define all named
> > > registers rather than the confusing magic hardcode, the driver code will
> > > be more readable and easy to review.
> > > I think this is what Sakari thought.
> > 
> > Yes. And even if it happens that a register write slips to a wrong list,
> > we can fix it later.
> > 
> I agree with the suggestion on proper naming of register offsets, but
> unfortunately we lack access to the spec. We are completely relying on the
> sensor vendor for these sequences, which they are not willing to share the
> details.

I find it difficult to believe a company such as AMD would use a camera
sensor without having documentation on it. It is essentially required for
hardware design, too.

Have you asked around?

-- 
Kind regards,

Sakari Ailus

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

* Re: [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver
  2025-07-06  9:11                   ` Sakari Ailus
@ 2025-07-08 15:31                     ` Nirujogi, Pratap
  0 siblings, 0 replies; 60+ messages in thread
From: Nirujogi, Pratap @ 2025-07-08 15:31 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Yan, Dongcheng, Laurent Pinchart, Hao Yao, Pratap Nirujogi,
	mchehab, hverkuil, bryan.odonoghue, krzk, dave.stevenson,
	hdegoede, jai.luthra, tomi.valkeinen, linux-media, linux-kernel,
	benjamin.chan, bin.du, grosikop, king.li, dantony, vengutta,
	dongcheng.yan, jason.z.chen, jimmy.su

Hi Sakari,

On 7/6/2025 5:11 AM, Sakari Ailus wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> Hi Pratap,
> 
> On Wed, Jul 02, 2025 at 12:47:40PM -0400, Nirujogi, Pratap wrote:
>>>> If you have a spec of OV05C10 (I assume you do, as the developer of this
>>>> driver), it is not a issue.
>>>> Take P0:0x14 as an example, it's named as DPLL_NC_SEL in spec and set to
>>>> 0x78 in your reglist ov05c10_2888x1808_regs. If define all named
>>>> registers rather than the confusing magic hardcode, the driver code will
>>>> be more readable and easy to review.
>>>> I think this is what Sakari thought.
>>>
>>> Yes. And even if it happens that a register write slips to a wrong list,
>>> we can fix it later.
>>>
>> I agree with the suggestion on proper naming of register offsets, but
>> unfortunately we lack access to the spec. We are completely relying on the
>> sensor vendor for these sequences, which they are not willing to share the
>> details.
> 
> I find it difficult to believe a company such as AMD would use a camera
> sensor without having documentation on it. It is essentially required for
> hardware design, too.
> 
> Have you asked around?
> 
Yes, I checked internally too and sorry to say that we don't have 
register programming guide (aka the register list definition). All 
programming sequence are a result of FAE engineer from OV providing to 
AMD based on the usecase requirements.

Thanks,
Pratap

> --
> Kind regards,
> 
> Sakari Ailus


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

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

Thread overview: 60+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-09 19:42 [PATCH v3 RESEND] media: i2c: Add OV05C10 camera sensor driver Pratap Nirujogi
2025-06-13  4:55 ` Hao Yao
2025-06-13 11:02   ` Kieran Bingham
2025-06-13 12:05     ` Bryan O'Donoghue
2025-06-16 23:15       ` Nirujogi, Pratap
2025-06-16 23:46     ` Nirujogi, Pratap
2025-06-28 19:23       ` Sakari Ailus
2025-06-13 22:02   ` Sakari Ailus
2025-06-14 22:52     ` Laurent Pinchart
2025-06-16 22:33       ` Nirujogi, Pratap
2025-06-29  7:40         ` Sakari Ailus
2025-06-30 22:31           ` Nirujogi, Pratap
2025-07-02  6:08             ` Yan, Dongcheng
2025-07-02  6:12               ` Sakari Ailus
2025-07-02 16:47                 ` Nirujogi, Pratap
2025-07-03  6:24                   ` Yan, Dongcheng
2025-07-05 11:45                     ` Nirujogi, Pratap
2025-07-06  9:11                   ` Sakari Ailus
2025-07-08 15:31                     ` Nirujogi, Pratap
2025-06-23 11:27       ` Sakari Ailus
2025-06-23 12:06         ` Laurent Pinchart
2025-06-23 21:53           ` Nirujogi, Pratap
2025-06-23 22:11             ` Laurent Pinchart
2025-06-16 23:12     ` Nirujogi, Pratap
2025-06-23 12:09       ` Laurent Pinchart
2025-06-23 13:22         ` Sakari Ailus
2025-06-23 13:42           ` Laurent Pinchart
2025-06-23 21:55             ` Nirujogi, Pratap
2025-06-23 22:06               ` Laurent Pinchart
2025-06-23 23:21                 ` Nirujogi, Pratap
2025-06-25 22:06               ` Nirujogi, Pratap
2025-06-26 11:11                 ` Kieran Bingham
2025-06-26 12:23                   ` Laurent Pinchart
2025-06-26 18:22                     ` Nirujogi, Pratap
2025-06-26 18:56                       ` Laurent Pinchart
2025-06-26 19:21                         ` Nirujogi, Pratap
2025-06-26 18:14                   ` Nirujogi, Pratap
2025-06-17  0:19   ` Nirujogi, Pratap
2025-06-15  0:09 ` Laurent Pinchart
2025-06-16 22:49   ` Nirujogi, Pratap
2025-06-23 21:51     ` Nirujogi, Pratap
2025-06-23 22:05       ` Laurent Pinchart
2025-06-23 23:28         ` Nirujogi, Pratap
2025-06-24  0:19           ` Laurent Pinchart
2025-06-24 18:49             ` Nirujogi, Pratap
2025-06-24 19:00               ` Laurent Pinchart
2025-06-24 19:49                 ` Nirujogi, Pratap
2025-06-24  8:35         ` Mehdi Djait
2025-06-24 10:19           ` Sakari Ailus
2025-06-24 10:20             ` Sakari Ailus
2025-06-24 10:27               ` Laurent Pinchart
2025-06-24 11:27                 ` Mehdi Djait
2025-06-24 11:33                   ` Laurent Pinchart
2025-06-24 11:46                   ` Sakari Ailus
2025-06-24 16:34                     ` Mehdi Djait
2025-06-24 20:24                       ` Nirujogi, Pratap
2025-06-25  6:11                       ` Sakari Ailus
2025-06-24 18:26                   ` Nirujogi, Pratap
2025-06-24 20:01                     ` Nirujogi, Pratap
  -- strict thread matches above, loose matches on Subject: below --
2025-05-15 22:41 Pratap Nirujogi

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