mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Rishikesh Donadkar <r-donadkar@ti.com>
To: <linux-media@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
	<devicetree@vger.kernel.org>,
	<linux-arm-kernel@lists.infradead.org>
Cc: <tomi.valkeinen@ideasonboard.com>, <mchehab@kernel.org>,
	<robh@kernel.org>, <krzk+dt@kernel.org>, <conor+dt@kernel.org>,
	<nm@ti.com>, <vigneshr@ti.com>, <kristo@kernel.org>,
	<sakari.ailus@linux.intel.com>, <mripard@kernel.org>,
	<jai.luthra@linux.dev>, <jai.luthra@ideasonboard.com>,
	<devarsht@ti.com>, <y-abhilashchandra@ti.com>,
	<mirela.rabulea@nxp.com>, <laurent.pinchart@ideasonboard.com>
Subject: [RFC PATCH 6/8] media: i2c: ov2312: add Omnivison OV2312 driver
Date: Fri, 25 Sep 2026 18:59:59 +0530	[thread overview]
Message-ID: <20260925133001.2780868-7-r-donadkar@ti.com> (raw)
In-Reply-To: <20260925133001.2780868-1-r-donadkar@ti.com>

From: Jai Luthra <j-luthra@ti.com>

Omnivision OV2312 is an RGB-IR sensor, i.e. it uses a 4x4 R,G,B,Ir bayer
pattern to capture both visible and near-infrared light. Every alternate
frame, the sensor changes the exposure and IR flash strobe registers to
stream an -
A. IR-dominant frame on CSI-2 virtual channel 0
B. RGB-dominant frame on CSI-2 virtual channel 1

These A/B frames are routed as separate v4l2 streams, which may be
mapped to two separate /dev/videoX nodes by the CSI-RX DMA driver.

Both of these streams are captured at a resolution of 1600x1301, 30 fps
each (60fps total). The extra row (1301 vs 1300) is an embedded line
prepended to each frame by the sensor, containing the following register
values:
  0x4813 - VC (Virtual Channel)
  0x321A - Group ID
  0x3920 - Strobe
  0x3501 - Exposure HI
  0x3502 - Exposure LO
  0x3508 - Gain HI
  0x3509 - Gain LO
  0x350e - Current Exposure HI
  0x350f - Current Exposure LO

This driver also supports a few v4l2 controls like horizontal/vertical
flip, multi exposure and multi gain controls.

Signed-off-by: Jai Luthra <j-luthra@ti.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 drivers/media/i2c/Kconfig  |  12 +
 drivers/media/i2c/Makefile |   1 +
 drivers/media/i2c/ov2312.c | 939 +++++++++++++++++++++++++++++++++++++
 drivers/media/i2c/ov2312.h | 285 +++++++++++
 4 files changed, 1237 insertions(+)
 create mode 100644 drivers/media/i2c/ov2312.c
 create mode 100644 drivers/media/i2c/ov2312.h

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 4d9946479160..8d9d8d491b2e 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -496,6 +496,18 @@ config VIDEO_OV13B10
 	  This is a Video4Linux2 sensor driver for the OmniVision
 	  OV13B10 camera.
 
+config VIDEO_OV2312
+	tristate "OmniVision OV2312 sensor support"
+	depends on I2C && VIDEO_DEV
+	select MEDIA_CONTROLLER
+	select VIDEO_V4L2_SUBDEV_API
+	help
+	  This is a Video4Linux2 sensor driver for the OmniVision
+	  OV2312 camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ov2312.
+
 config VIDEO_OV2640
 	tristate "OmniVision OV2640 sensor support"
 	help
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index fd1cb25718c0..e019f174c5c0 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -98,6 +98,7 @@ obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
 obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
 obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
 obj-$(CONFIG_VIDEO_OV13B10) += ov13b10.o
+obj-$(CONFIG_VIDEO_OV2312) += ov2312.o
 obj-$(CONFIG_VIDEO_OV2640) += ov2640.o
 obj-$(CONFIG_VIDEO_OV2659) += ov2659.o
 obj-$(CONFIG_VIDEO_OV2680) += ov2680.o
diff --git a/drivers/media/i2c/ov2312.c b/drivers/media/i2c/ov2312.c
new file mode 100644
index 000000000000..1561e1b3602d
--- /dev/null
+++ b/drivers/media/i2c/ov2312.c
@@ -0,0 +1,939 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Omnivision OV2312 RGB-IR Image Sensor driver
+ *
+ * Copyright (c) 2022 Jai Luthra <j-luthra@ti.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/v4l2-mediabus.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-ctrls.h>
+
+#include "ov2312.h"
+
+struct ov2312 {
+	struct device *dev;
+
+	struct clk *clk;
+	unsigned long clk_rate;
+
+	struct i2c_client *client;
+	struct regmap *regmap;
+	struct gpio_desc *reset_gpio;
+
+	struct v4l2_subdev sd;
+	struct media_pad pad;
+	struct v4l2_mbus_framefmt format;
+
+	struct v4l2_ctrl_handler ctrls;
+	struct v4l2_ctrl *exposure_multi;
+	struct v4l2_ctrl *again_multi;
+	struct v4l2_ctrl *dgain_multi;
+	struct v4l2_ctrl *h_flip;
+	struct v4l2_ctrl *v_flip;
+
+	u32 fps;
+
+	struct mutex lock; /* For streaming status */
+	unsigned int enable_count;
+};
+
+static inline struct ov2312 *to_ov2312(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct ov2312, sd);
+}
+
+static int ov2312_read(struct ov2312 *ov2312, u16 addr, u32 *val, size_t nbytes)
+{
+	int ret;
+	__le32 val_le = 0;
+
+	ret = regmap_bulk_read(ov2312->regmap, addr, &val_le, nbytes);
+	if (ret < 0) {
+		dev_err(ov2312->dev, "%s: failed to read reg 0x%04x: %d\n",
+			__func__, addr, ret);
+		return ret;
+	}
+
+	*val = le32_to_cpu(val_le);
+	return 0;
+}
+
+static int ov2312_write(struct ov2312 *ov2312, u16 addr, u32 val, size_t nbytes)
+{
+	int ret;
+	__le32 val_le = cpu_to_le32(val);
+
+	ret = regmap_bulk_write(ov2312->regmap, addr, &val_le, nbytes);
+	if (ret < 0)
+		dev_err(ov2312->dev, "%s: failed to write reg 0x%04x: %d\n",
+			__func__, addr, ret);
+	return ret;
+}
+
+static int ov2312_write_table(struct ov2312 *ov2312,
+			      const struct reg_sequence *regs,
+			      unsigned int nr_regs)
+{
+	int ret, i;
+
+	for (i = 0; i < nr_regs; i++) {
+		ret = regmap_write(ov2312->regmap, regs[i].reg, regs[i].def);
+		if (ret < 0) {
+			dev_err(ov2312->dev,
+				"%s: failed to write reg[%d] 0x%04x = 0x%02x (%d)!\n",
+				__func__, i, regs[i].reg, regs[i].def, ret);
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static void ov2312_init_formats(struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *format;
+	int i;
+
+	for (i = 0; i < 2; ++i) {
+		format = v4l2_subdev_state_get_format(state, 0, i);
+		format->code = ov2312_mbus_formats[0];
+		format->width = ov2312_framesizes[0].width;
+		format->height = ov2312_framesizes[0].height;
+		format->field = V4L2_FIELD_NONE;
+		format->colorspace = V4L2_COLORSPACE_DEFAULT;
+	}
+}
+
+static int ov2312_set_fmt(struct v4l2_subdev *sd,
+			  const struct v4l2_subdev_client_info *ci,
+			  struct v4l2_subdev_state *state,
+			  struct v4l2_subdev_format *fmt)
+{
+	struct ov2312 *ov2312 = to_ov2312(sd);
+	struct v4l2_mbus_framefmt *format;
+	const struct v4l2_area *fsize;
+	u32 code;
+	int ret = 0;
+
+	if (fmt->pad != 0)
+		return -EINVAL;
+
+	if (fmt->stream != 0)
+		return -EINVAL;
+
+	/* Sensor only supports a single format. */
+	code = ov2312_mbus_formats[0];
+
+	/* Find the nearest supported frame size. */
+	fsize = v4l2_find_nearest_size(ov2312_framesizes,
+				       ARRAY_SIZE(ov2312_framesizes), width,
+				       height, fmt->format.width,
+				       fmt->format.height);
+
+	v4l2_subdev_lock_state(state);
+
+	/* Update the stored format and return it. */
+	format = v4l2_subdev_state_get_format(state, fmt->pad, fmt->stream);
+
+	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE && ov2312->enable_count) {
+		ret = -EBUSY;
+		goto done;
+	}
+
+	format->code = code;
+	format->width = fsize->width;
+	format->height = fsize->height;
+
+	fmt->format = *format;
+
+done:
+	v4l2_subdev_unlock_state(state);
+
+	return ret;
+}
+
+static int _ov2312_set_routing(struct v4l2_subdev *sd,
+			       struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes[] = {
+		{
+			.source_pad = 0,
+			.source_stream = 0,
+			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+		},
+		{
+			.source_pad = 0,
+			.source_stream = 1,
+			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+		},
+	};
+
+	struct v4l2_subdev_krouting routing = {
+		.num_routes = ARRAY_SIZE(routes),
+		.routes = routes,
+	};
+
+	int ret;
+
+	ret = v4l2_subdev_set_routing(sd, state, &routing);
+	if (ret < 0)
+		return ret;
+
+	ov2312_init_formats(state);
+
+	return 0;
+}
+
+static int ov2312_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
+				 struct v4l2_mbus_frame_desc *fd)
+{
+	struct v4l2_subdev_state *state;
+	struct v4l2_mbus_framefmt *fmt;
+	u32 bpp;
+	int ret = 0;
+	unsigned int i;
+
+	if (pad != 0)
+		return -EINVAL;
+
+	state = v4l2_subdev_lock_and_get_active_state(sd);
+
+	fmt = v4l2_subdev_state_get_format(state, 0, 0);
+	if (!fmt) {
+		ret = -EPIPE;
+		goto out;
+	}
+
+	memset(fd, 0, sizeof(*fd));
+
+	fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
+
+	/* pixel stream - 2 virtual channels */
+
+	bpp = 10;
+
+	for (i = 0; i < 2; ++i) {
+		fd->entry[fd->num_entries].stream = i;
+
+		fd->entry[fd->num_entries].flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX;
+		fd->entry[fd->num_entries].length = fmt->width * fmt->height * bpp / 8;
+		fd->entry[fd->num_entries].pixelcode = fmt->code;
+		fd->entry[fd->num_entries].bus.csi2.vc = i;
+		fd->entry[fd->num_entries].bus.csi2.dt = 0x2b; /* RAW10 */
+
+		fd->num_entries++;
+	}
+
+out:
+	v4l2_subdev_unlock_state(state);
+
+	return ret;
+}
+
+static int ov2312_set_routing(struct v4l2_subdev *sd,
+			      struct v4l2_subdev_state *state,
+			      enum v4l2_subdev_format_whence which,
+			      struct v4l2_subdev_krouting *routing)
+{
+	int ret;
+
+	if (routing->num_routes == 0 || routing->num_routes > 2)
+		return -EINVAL;
+
+	v4l2_subdev_lock_state(state);
+
+	ret = _ov2312_set_routing(sd, state);
+
+	v4l2_subdev_unlock_state(state);
+
+	return ret;
+}
+
+static int ov2312_init_state(struct v4l2_subdev *sd,
+			   struct v4l2_subdev_state *state)
+{
+	int ret;
+
+	ret = _ov2312_set_routing(sd, state);
+
+	return ret;
+}
+
+static int ov2312_enum_mbus_code(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state,
+				 struct v4l2_subdev_mbus_code_enum *code)
+{
+	if (code->index >= ARRAY_SIZE(ov2312_mbus_formats))
+		return -EINVAL;
+
+	code->code = ov2312_mbus_formats[code->index];
+
+	return 0;
+}
+
+static int ov2312_enum_frame_sizes(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state,
+				   struct v4l2_subdev_frame_size_enum *fse)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(ov2312_mbus_formats); ++i) {
+		if (ov2312_mbus_formats[i] == fse->code)
+			break;
+	}
+
+	if (i == ARRAY_SIZE(ov2312_mbus_formats))
+		return -EINVAL;
+
+	if (fse->index >= ARRAY_SIZE(ov2312_framesizes))
+		return -EINVAL;
+
+	fse->min_width = ov2312_framesizes[fse->index].width;
+	fse->max_width = fse->min_width;
+	fse->max_height = ov2312_framesizes[fse->index].height;
+	fse->min_height = fse->max_height;
+
+	return 0;
+}
+
+static int ov2312_get_frame_interval(struct v4l2_subdev *sd,
+					 struct v4l2_subdev_state *state,
+				     struct v4l2_subdev_frame_interval *fi)
+{
+	struct ov2312 *ov2312 = to_ov2312(sd);
+
+	fi->interval.numerator = 1;
+	fi->interval.denominator = ov2312->fps / 2;
+
+	return 0;
+}
+
+static int ov2312_set_frame_interval(struct v4l2_subdev *sd,
+					 struct v4l2_subdev_state *state,
+				     struct v4l2_subdev_frame_interval *fi)
+{
+	struct ov2312 *ov2312 = to_ov2312(sd);
+
+	if (!fi->interval.numerator)
+		return -EINVAL;
+
+	dev_dbg(ov2312->dev, "%s: Set framerate %dfps\n", __func__,
+		fi->interval.denominator / fi->interval.numerator);
+
+	if ((fi->interval.denominator / fi->interval.numerator) != ov2312->fps / 2) {
+		dev_err(ov2312->dev, "%s: Framerate can only be %dfps\n",
+			__func__, ov2312->fps / 2);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int ov2312_detect(struct ov2312 *ov2312)
+{
+	int ret;
+	u32 id;
+
+	ret = ov2312_read(ov2312, OV2312_SC_CHIP_ID_HI, &id, 2);
+	if (ret < 0)
+		return ret;
+
+	id = cpu_to_be16(id);
+
+	if (id != OV2312_CHIP_ID) {
+		dev_err(ov2312->dev,
+			"%s: unknown chip ID 0x%04x\n", __func__, id);
+		return -ENODEV;
+	}
+
+	dev_dbg(ov2312->dev, "%s: detected chip ID 0x%04x\n", __func__, id);
+	return 0;
+}
+
+/*
+ */
+static int ov2312_set_group_a(struct ov2312 *ov2312)
+{
+	u32 ir_exposure = ov2312->exposure_multi->p_new.p_u32[1];
+	u32 ir_again    = ov2312->again_multi->p_new.p_u32[1];
+	u32 ir_dgain    = ov2312->dgain_multi->p_new.p_u32[1];
+	u32 ir_strobe_start = OV2312_VTS - ir_exposure - 7;
+	int ret;
+
+	struct reg_sequence ov2312_groupA[] = {
+		{0x3208, 0x00},/* Group A (IR Dominant VC0) */
+		{OV2312_AEC_PK_EXPO_HI, (ir_exposure >> 8) & 0xff},
+		{OV2312_AEC_PK_EXPO_LO, ir_exposure & 0xff},
+		{OV2312_AEC_PK_AGAIN_HI, (ir_again >> 4) & 0xff},
+		{OV2312_AEC_PK_AGAIN_LO, (ir_again & 0x0f) << 4},
+		{OV2312_AEC_PK_DGAIN_HI, (ir_dgain >> 8) & 0xff},
+		{OV2312_AEC_PK_DGAIN_LO, ir_dgain & 0xff},
+		{0x3920, 0xff},/* IR Strobe duty cycle */
+		{0x3927, (ir_exposure >> 8) & 0xff},
+		{0x3928, ir_exposure & 0xff},
+		{0x3929, (ir_strobe_start >> 8) & 0xff},
+		{0x392a, ir_strobe_start & 0xff},
+		{0x4813, 0x01},/* VC=1. This register takes effect from next frame */
+		{0x3208, 0x10},
+		{0x320D, 0x00},/* Auto mode switch between group0 and group1 ;setting to switch */
+		{0x320D, 0x31},
+		{0x3208, 0xA0},
+	};
+
+	ret = regmap_register_patch(ov2312->regmap, ov2312_groupA,
+				    ARRAY_SIZE(ov2312_groupA));
+	if (ret < 0)
+		dev_err(ov2312->dev,
+			"%s: failed to apply Group A register patch (%d)!\n",
+			__func__, ret);
+	return ret;
+}
+
+static int ov2312_set_group_b(struct ov2312 *ov2312)
+{
+	u32 rgb_exposure = ov2312->exposure_multi->p_new.p_u32[0];
+	u32 rgb_again    = ov2312->again_multi->p_new.p_u32[0];
+	u32 rgb_dgain    = ov2312->dgain_multi->p_new.p_u32[0];
+	int ret;
+
+	struct reg_sequence ov2312_groupB[] = {
+		{0x3208, 0x01},/* Group B (RGB Dominant VC1) */
+		{OV2312_AEC_PK_EXPO_HI, (rgb_exposure >> 8) & 0xff},
+		{OV2312_AEC_PK_EXPO_LO, rgb_exposure & 0xff},
+		{OV2312_AEC_PK_AGAIN_HI, (rgb_again >> 4) & 0xff},
+		{OV2312_AEC_PK_AGAIN_LO, (rgb_again & 0x0f) << 4},
+		{OV2312_AEC_PK_DGAIN_HI, (rgb_dgain >> 8) & 0xff},
+		{OV2312_AEC_PK_DGAIN_LO, rgb_dgain & 0xff},
+		{0x3920, 0x00},
+		{0x4813, 0x00},/* VC=0. This register takes effect from next frame */
+		{0x3208, 0x11},
+		{0x320D, 0x00},/* Auto mode switch between group0 and group1 ;setting to switch */
+		{0x320D, 0x30},
+		{0x3208, 0xA0},
+	};
+
+	ret = regmap_register_patch(ov2312->regmap, ov2312_groupB,
+				    ARRAY_SIZE(ov2312_groupB));
+	if (ret < 0)
+		dev_err(ov2312->dev,
+			"%s: failed to apply Group B register patch (%d)!\n",
+			__func__, ret);
+	return ret;
+}
+
+static int ov2312_set_AB_mode(struct ov2312 *ov2312)
+{
+	bool ir_ready  = ov2312->exposure_multi->p_new.p_u32[1] &&
+			 ov2312->again_multi->p_new.p_u32[1] &&
+			 ov2312->dgain_multi->p_new.p_u32[1];
+	bool rgb_ready = ov2312->exposure_multi->p_new.p_u32[0] &&
+			 ov2312->again_multi->p_new.p_u32[0] &&
+			 ov2312->dgain_multi->p_new.p_u32[0];
+	int ret;
+
+	if (ir_ready) {
+		ret = ov2312_set_group_a(ov2312);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (rgb_ready) {
+		ret = ov2312_set_group_b(ov2312);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Wait for 1 frame duration after setting AB mode registers */
+	if (ir_ready || rgb_ready)
+		msleep(33);
+
+	return 0;
+}
+
+static int ov2312_set_orientation(struct ov2312 *ov2312)
+{
+	bool v_flip = ov2312->v_flip->val;
+	bool h_flip = ov2312->h_flip->val;
+	u32 reg = (v_flip ? 0x4400 : 0) | (h_flip ? 0x0004 : 0);
+
+	return ov2312_write(ov2312, OV2312_TIMING_VFLIP, be16_to_cpu(reg), 2);
+}
+
+static int ov2312_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct ov2312 *ov2312 = container_of(ctrl->handler,
+					     struct ov2312, ctrls);
+	int ret;
+
+	/*
+	 * If the device is not powered up by the host driver do
+	 * not apply any controls to H/W at this time. Instead
+	 * the controls will be restored right after power-up.
+	 */
+	if (pm_runtime_suspended(ov2312->dev))
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_EXPOSURE_MULTI:
+	case V4L2_CID_AGAIN_MULTI:
+	case V4L2_CID_DGAIN_MULTI:
+		dev_dbg(ov2312->dev, "debug: %s: %s = [%u, %u]\n", __func__,
+				ctrl->name, ctrl->p_new.p_u32[0], ctrl->p_new.p_u32[1]);
+
+		ret = ov2312_set_AB_mode(ov2312);
+		break;
+
+	case V4L2_CID_HFLIP:
+	case V4L2_CID_VFLIP:
+		ret = ov2312_set_orientation(ov2312);
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int ov2312_power_on(struct ov2312 *ov2312)
+{
+	int ret;
+
+	ret = clk_prepare_enable(ov2312->clk);
+	if (ret < 0)
+		return ret;
+
+	if (ov2312->reset_gpio) {
+		gpiod_set_value_cansleep(ov2312->reset_gpio, 0);
+		usleep_range(100, 1000);
+		gpiod_set_value_cansleep(ov2312->reset_gpio, 1);
+		msleep(30);
+	}
+	return 0;
+}
+
+static int ov2312_power_off(struct ov2312 *ov2312)
+{
+	if (ov2312->reset_gpio) {
+		gpiod_set_value_cansleep(ov2312->reset_gpio, 0);
+		usleep_range(1, 10);
+	}
+
+	clk_disable_unprepare(ov2312->clk);
+
+	return 0;
+}
+
+static int ov2312_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ov2312 *ov2312 = to_ov2312(sd);
+
+	return ov2312_power_on(ov2312);
+}
+
+static int ov2312_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ov2312 *ov2312 = to_ov2312(sd);
+
+	return ov2312_power_off(ov2312);
+}
+
+static int ov2312_start_stream(struct ov2312 *ov2312)
+{
+	int ret;
+
+	ret = ov2312_write_table(ov2312, ov2312_1600x1300_60fps_AB,
+				 ARRAY_SIZE(ov2312_1600x1300_60fps_AB));
+	if (ret < 0)
+		return ret;
+
+	/* Update controls on wake up */
+	ret = ov2312_set_orientation(ov2312);
+	if (ret < 0)
+		return ret;
+
+	ret = ov2312_set_AB_mode(ov2312);
+	if (ret < 0)
+		return ret;
+
+	msleep(100);
+
+	/* Set active */
+	ret = ov2312_write(ov2312, OV2312_SYS_MODE_SEL, 1, 1);
+	if (ret < 0)
+		return ret;
+
+	/* No communication is possible for a while after exiting standby */
+	msleep(20);
+	return 0;
+}
+
+static int ov2312_stop_stream(struct ov2312 *ov2312)
+{
+	int ret;
+
+	/* Set standby */
+	ret = ov2312_write(ov2312, OV2312_SYS_MODE_SEL, 0, 1);
+	if (ret < 0)
+		return ret;
+
+	/* No communication is possible for a while after entering standby */
+	usleep_range(10000, 20000);
+	return 0;
+}
+
+static int ov2312_sd_enable_streams(struct v4l2_subdev *sd,
+				       struct v4l2_subdev_state *state,
+				       u32 pad, u64 streams_mask)
+{
+	struct ov2312 *ov2312 = to_ov2312(sd);
+	int ret;
+
+	guard(mutex)(&ov2312->lock);
+
+	if (!ov2312->enable_count) {
+		ret = pm_runtime_resume_and_get(ov2312->dev);
+		if (ret < 0)
+			goto err;
+
+		ret = ov2312_start_stream(ov2312);
+		if (ret < 0) {
+			pm_runtime_put(ov2312->dev);
+			goto err;
+		}
+	}
+
+	ov2312->enable_count++;
+
+	return 0;
+
+err:
+	dev_err(ov2312->dev,
+		"%s: failed to enable streams %d\n", __func__, ret);
+	return ret;
+}
+
+static int ov2312_sd_disable_streams(struct v4l2_subdev *sd,
+					struct v4l2_subdev_state *state,
+					u32 pad, u64 streams_mask)
+{
+	struct ov2312 *ov2312 = to_ov2312(sd);
+	int ret;
+
+	mutex_lock(&ov2312->lock);
+
+	if (ov2312->enable_count == 1) {
+		ret = ov2312_stop_stream(ov2312);
+		if (ret < 0)
+			goto err_runtime_put;
+	}
+
+	ov2312->enable_count--;
+	mutex_unlock(&ov2312->lock);
+
+	if (!ov2312->enable_count)
+		pm_runtime_put(ov2312->dev);
+
+	return 0;
+
+err_runtime_put:
+	mutex_unlock(&ov2312->lock);
+	pm_runtime_put(ov2312->dev);
+	return ret;
+}
+
+static const struct v4l2_subdev_pad_ops ov2312_subdev_pad_ops = {
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = ov2312_set_fmt,
+	.enum_mbus_code = ov2312_enum_mbus_code,
+	.enum_frame_size = ov2312_enum_frame_sizes,
+	.set_routing = ov2312_set_routing,
+	.get_frame_desc	= ov2312_get_frame_desc,
+	.get_frame_interval = ov2312_get_frame_interval,
+	.set_frame_interval = ov2312_set_frame_interval,
+	.enable_streams = ov2312_sd_enable_streams,
+	.disable_streams = ov2312_sd_disable_streams,
+};
+
+static const struct v4l2_subdev_internal_ops ov2312_internal_ops = {
+	.init_state = ov2312_init_state,
+};
+
+static const struct v4l2_subdev_ops ov2312_subdev_ops = {
+	.pad	= &ov2312_subdev_pad_ops,
+};
+
+static const struct v4l2_ctrl_ops ov2312_ctrl_ops = {
+	.s_ctrl	= ov2312_set_ctrl,
+};
+
+static const struct dev_pm_ops ov2312_pm_ops = {
+	SET_RUNTIME_PM_OPS(ov2312_suspend, ov2312_resume, NULL)
+};
+
+static int ov2312_probe(struct i2c_client *client)
+{
+	struct ov2312 *ov2312;
+	struct v4l2_subdev *sd;
+	struct v4l2_ctrl_handler *ctrl_hdr;
+	int ret;
+
+	/* Allocate internal struct */
+	ov2312 = devm_kzalloc(&client->dev, sizeof(*ov2312), GFP_KERNEL);
+	if (!ov2312)
+		return -ENOMEM;
+
+	ov2312->dev = &client->dev;
+	ov2312->client = client;
+
+	/* Initialize I2C Regmap */
+	ov2312->regmap = devm_regmap_init_i2c(client, &ov2312_regmap_config);
+	if (IS_ERR(ov2312->regmap))
+		return PTR_ERR(ov2312->regmap);
+
+	/* Initialize Shutdown GPIO */
+	ov2312->reset_gpio = devm_gpiod_get_optional(ov2312->dev, "reset",
+						     GPIOD_OUT_HIGH);
+	if (IS_ERR(ov2312->reset_gpio))
+		return PTR_ERR(ov2312->reset_gpio);
+
+	ov2312->clk = devm_clk_get(ov2312->dev, "xvclk");
+	if (IS_ERR(ov2312->clk))
+		return PTR_ERR(ov2312->clk);
+
+	ov2312->clk_rate = clk_get_rate(ov2312->clk);
+	dev_info(ov2312->dev, "xvclk rate: %lu Hz\n", ov2312->clk_rate);
+
+	if (ov2312->clk_rate < 6000000 || ov2312->clk_rate > 27000000)
+		return -EINVAL;
+
+	/* Power on */
+	ret = ov2312_power_on(ov2312);
+	if (ret < 0)
+		return ret;
+
+	/* Detect sensor */
+	ret = ov2312_detect(ov2312);
+	if (ret < 0)
+		goto err_power_off;
+
+	/* Initialize the subdev and its controls. */
+	sd = &ov2312->sd;
+	v4l2_i2c_subdev_init(sd, client, &ov2312_subdev_ops);
+	sd->internal_ops = &ov2312_internal_ops;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+		     V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_STREAMS;
+
+	/* Initialize the media entity. */
+	ov2312->pad.flags = MEDIA_PAD_FL_SOURCE;
+	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
+	ret = media_entity_pads_init(&sd->entity, 1, &ov2312->pad);
+	if (ret < 0) {
+		dev_err_probe(ov2312->dev, ret, "media entity init failed\n");
+		goto err_power_off;
+	}
+
+	ov2312->fps = OV2312_FRAMERATE_DEFAULT;
+	ret = devm_mutex_init(ov2312->dev, &ov2312->lock);
+	if (ret) {
+		dev_err_probe(ov2312->dev, ret, "mutex init failed\n");
+		goto err_media_cleanup;
+	}
+
+	/* Initialize controls */
+	ctrl_hdr = &ov2312->ctrls;
+	/* 3 multi-capture + 2 flip + 3 single backward-compat */
+	ret = v4l2_ctrl_handler_init(ctrl_hdr, 8);
+	if (ret < 0) {
+		ret = dev_err_probe(ov2312->dev, ret,
+				    "ctrl handler init failed\n");
+		goto err_media_cleanup;
+	}
+
+	ov2312->ctrls.lock = &ov2312->lock;
+
+	/* Multi-capture controls: index 0 = RGB (longest), index 1 = IR */
+	{
+		const struct v4l2_ctrl_config exposure_multi_cfg = {
+			.ops  = &ov2312_ctrl_ops,
+			.id   = V4L2_CID_EXPOSURE_MULTI,
+			.type = V4L2_CTRL_TYPE_U32,
+			.min  = 0,
+			.max  = OV2312_EXPOSURE_MAX,
+			.step = 1,
+			.def  = OV2312_EXPOSURE_DEFAULT,
+			.dims = { 2 },
+		};
+		const struct v4l2_ctrl_config again_multi_cfg = {
+			.ops  = &ov2312_ctrl_ops,
+			.id   = V4L2_CID_AGAIN_MULTI,
+			.type = V4L2_CTRL_TYPE_U32,
+			.min  = 0,
+			.max  = OV2312_AGAIN_MAX,
+			.step = 1,
+			.def  = OV2312_AGAIN_DEFAULT,
+			.dims = { 2 },
+		};
+		const struct v4l2_ctrl_config dgain_multi_cfg = {
+			.ops  = &ov2312_ctrl_ops,
+			.id   = V4L2_CID_DGAIN_MULTI,
+			.type = V4L2_CTRL_TYPE_U32,
+			.min  = 0,
+			.max  = OV2312_DGAIN_MAX,
+			.step = 1,
+			.def  = OV2312_DGAIN_DEFAULT,
+			.dims = { 2 },
+		};
+
+		ov2312->exposure_multi = v4l2_ctrl_new_custom(ctrl_hdr,
+							      &exposure_multi_cfg,
+							      NULL);
+		ov2312->again_multi = v4l2_ctrl_new_custom(ctrl_hdr,
+							   &again_multi_cfg,
+							   NULL);
+		ov2312->dgain_multi = v4l2_ctrl_new_custom(ctrl_hdr,
+							   &dgain_multi_cfg,
+							   NULL);
+	}
+
+	if (ov2312->exposure_multi && ov2312->again_multi && ov2312->dgain_multi) {
+		/* Set per-element initial values: [0]=RGB default, [1]=IR default */
+		ov2312->exposure_multi->p_cur.p_u32[0] = OV2312_EXPOSURE_DEFAULT;
+		ov2312->exposure_multi->p_cur.p_u32[1] = OV2312_IR_EXPOSURE;
+		ov2312->exposure_multi->p_new.p_u32[0] = OV2312_EXPOSURE_DEFAULT;
+		ov2312->exposure_multi->p_new.p_u32[1] = OV2312_IR_EXPOSURE;
+
+		ov2312->again_multi->p_cur.p_u32[0] = OV2312_AGAIN_DEFAULT;
+		ov2312->again_multi->p_cur.p_u32[1] = OV2312_AGAIN_DEFAULT;
+		ov2312->again_multi->p_new.p_u32[0] = OV2312_AGAIN_DEFAULT;
+		ov2312->again_multi->p_new.p_u32[1] = OV2312_AGAIN_DEFAULT;
+
+		ov2312->dgain_multi->p_cur.p_u32[0] = OV2312_DGAIN_DEFAULT;
+		ov2312->dgain_multi->p_cur.p_u32[1] = OV2312_DGAIN_DEFAULT;
+		ov2312->dgain_multi->p_new.p_u32[0] = OV2312_DGAIN_DEFAULT;
+		ov2312->dgain_multi->p_new.p_u32[1] = OV2312_DGAIN_DEFAULT;
+	}
+
+	ov2312->h_flip = v4l2_ctrl_new_std(ctrl_hdr, &ov2312_ctrl_ops,
+					   V4L2_CID_HFLIP, 0, 1, 1, 0);
+
+	ov2312->v_flip = v4l2_ctrl_new_std(ctrl_hdr, &ov2312_ctrl_ops,
+					   V4L2_CID_VFLIP, 0, 1, 1, 0);
+
+	/*
+	 * Register single-capture controls with proper ranges for backward
+	 * compatibility with older userspace. The v4l2 core redirects s_ctrl
+	 * for these to the corresponding multi-capture control.
+	 */
+	v4l2_ctrl_new_std(ctrl_hdr, &ov2312_ctrl_ops,
+			  V4L2_CID_EXPOSURE, 1, OV2312_EXPOSURE_MAX,
+			  1, OV2312_EXPOSURE_DEFAULT);
+	v4l2_ctrl_new_std(ctrl_hdr, &ov2312_ctrl_ops,
+			  V4L2_CID_ANALOGUE_GAIN, 0, OV2312_AGAIN_MAX,
+			  1, OV2312_AGAIN_DEFAULT);
+	v4l2_ctrl_new_std(ctrl_hdr, &ov2312_ctrl_ops,
+			  V4L2_CID_DIGITAL_GAIN, 0, OV2312_DGAIN_MAX,
+			  1, OV2312_DGAIN_DEFAULT);
+
+	ov2312->sd.ctrl_handler = ctrl_hdr;
+	if (ov2312->ctrls.error) {
+		ret = ov2312->ctrls.error;
+		ret = dev_err_probe(ov2312->dev, ret,
+				    "failed to add the ctrls\n");
+		goto err_ctrl_free;
+	}
+
+	/* PM Runtime */
+	pm_runtime_enable(ov2312->dev);
+	pm_runtime_set_suspended(ov2312->dev);
+
+	ret = v4l2_subdev_init_finalize(sd);
+	if (ret < 0)
+		goto err_pm_disable;
+
+	/* Finally, register the subdev. */
+	ret = v4l2_async_register_subdev(sd);
+	if (ret < 0) {
+		ret = dev_err_probe(ov2312->dev, ret,
+				    "v4l2 subdev register failed\n");
+		goto err_subdev_cleanup;
+	}
+
+	dev_info(ov2312->dev, "ov2312 probed\n");
+	return 0;
+
+err_subdev_cleanup:
+	v4l2_subdev_cleanup(&ov2312->sd);
+
+err_pm_disable:
+	pm_runtime_disable(ov2312->dev);
+
+err_ctrl_free:
+	v4l2_ctrl_handler_free(ctrl_hdr);
+
+err_media_cleanup:
+	media_entity_cleanup(&ov2312->sd.entity);
+
+err_power_off:
+	ov2312_power_off(ov2312);
+
+	return ret;
+}
+
+static void ov2312_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ov2312 *ov2312 = to_ov2312(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	v4l2_ctrl_handler_free(&ov2312->ctrls);
+	v4l2_subdev_cleanup(&ov2312->sd);
+	media_entity_cleanup(&sd->entity);
+
+	pm_runtime_disable(ov2312->dev);
+}
+
+static const struct i2c_device_id ov2312_id[] = {
+	{ "ov2312", 0 },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(i2c, ov2312_id);
+
+#if IS_ENABLED(CONFIG_OF)
+static const struct of_device_id ov2312_of_match[] = {
+	{ .compatible = "ovti,ov2312", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ov2312_of_match);
+#endif
+
+static struct i2c_driver ov2312_i2c_driver = {
+	.driver = {
+		.name	= "ov2312",
+		.pm	= &ov2312_pm_ops,
+		.of_match_table = of_match_ptr(ov2312_of_match),
+	},
+	.probe		= ov2312_probe,
+	.remove		= ov2312_remove,
+	.id_table	= ov2312_id,
+};
+
+module_i2c_driver(ov2312_i2c_driver);
+
+MODULE_AUTHOR("Jai Luthra <j-luthra@ti.com>");
+MODULE_DESCRIPTION("OV2312 RGB-IR Image Sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/i2c/ov2312.h b/drivers/media/i2c/ov2312.h
new file mode 100644
index 000000000000..8ed435878638
--- /dev/null
+++ b/drivers/media/i2c/ov2312.h
@@ -0,0 +1,285 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Omnivision OV2312 RGB-IR Image Sensor driver
+ *
+ * Copyright (c) 2022 Jai Luthra <j-luthra@ti.com>
+ */
+
+#include <linux/types.h>
+#include <linux/media-bus-format.h>
+
+#define OV2312_CHIP_ID	0x2311
+#define OV2312_FRAMERATE_DEFAULT 60
+
+#define OV2312_ED_LINES 1
+#define OV2312_OUT_WIDTH	1600
+#define OV2312_OUT_HEIGHT	(1300 + OV2312_ED_LINES)
+#define OV2312_VTS		0x0588
+
+#define OV2312_SYS_MODE_SEL	0x0100
+#define OV2312_SC_CHIP_ID_HI	0x300a
+#define OV2312_SC_CHIP_ID_LO	0x300b
+#define OV2312_AEC_PK_EXPO_HI	0x3501
+#define OV2312_AEC_PK_EXPO_LO	0x3502
+#define OV2312_AEC_PK_AGAIN_HI	0x3508
+#define OV2312_AEC_PK_AGAIN_LO	0x3509
+#define OV2312_AEC_PK_DGAIN_HI	0x350a
+#define OV2312_AEC_PK_DGAIN_LO	0x350b
+#define OV2312_TIMING_VFLIP	0x3820
+#define OV2312_TIMING_HFLIP	0x3821
+
+/* Exposure control */
+#define OV2312_EXPOSURE_MAX	(OV2312_VTS - 12)
+#define OV2312_EXPOSURE_DEFAULT 0x057c
+#define OV2312_IR_EXPOSURE	0x0090
+#define OV2312_IR_STROBE	OV2312_IR_EXPOSURE
+#define OV2312_IR_STROBE_START	(OV2312_VTS - OV2312_IR_EXPOSURE - 7)
+
+/* Analog gain control */
+#define OV2312_AGAIN_MAX	0x1FF
+#define OV2312_AGAIN_DEFAULT	0x010
+
+/* Digital gain control */
+#define OV2312_DGAIN_MAX	0x0FFF
+#define OV2312_DGAIN_DEFAULT	0x0100
+
+static const struct v4l2_area ov2312_framesizes[] = {
+	{
+		.width		= OV2312_OUT_WIDTH,
+		.height		= OV2312_OUT_HEIGHT,
+	},
+};
+
+static const u32 ov2312_mbus_formats[] = {
+	MEDIA_BUS_FMT_SBGGI10_1X10,
+};
+
+static const struct regmap_config ov2312_regmap_config = {
+	.reg_bits = 16,
+	.val_bits = 8,
+};
+
+static const struct reg_sequence ov2312_1600x1300_60fps_AB[] = {
+	{0x0103, 0x01},
+	{0x0100, 0x00},
+	{0x010c, 0x02},
+	{0x010b, 0x01},
+	{0x0300, 0x01},
+	{0x0302, 0x32},
+	{0x0303, 0x00},
+	{0x0304, 0x03},
+	{0x0305, 0x02},
+	{0x0306, 0x01},
+	{0x030d, 0x5a},
+	{0x030e, 0x04},
+	{0x3001, 0x02},
+	{0x3004, 0x00},
+	{0x3005, 0x00},
+	{0x3006, 0x0a},
+	{0x3011, 0x0d},
+	{0x3014, 0x04},
+	{0x301c, 0xf0},
+	{0x3020, 0x20},
+	{0x302c, 0x00},
+	{0x302d, 0x00},
+	{0x302e, 0x00},
+	{0x302f, 0x03},
+	{0x3030, 0x10},
+	{0x303f, 0x03},
+	{0x3103, 0x00},
+	{0x3106, 0x08},
+	{0x31ff, 0x01},
+	{0x3501, 0x05},
+	{0x3502, 0x7c},
+	{0x3506, 0x00},
+	{0x3507, 0x00},
+	{0x3620, 0x67},
+	{0x3633, 0x78},
+	{0x3662, 0x65},
+	{0x3664, 0xb0},
+	{0x3666, 0x70},
+	{0x3670, 0x68},
+	{0x3674, 0x10},
+	{0x3675, 0x00},
+	{0x367e, 0x90},
+	{0x3680, 0x84},
+	{0x36a2, 0x04},
+	{0x36a3, 0x80},
+	{0x36b0, 0x00},
+	{0x3700, 0x35},
+	{0x3704, 0x39},
+	{0x370a, 0x50},
+	{0x3712, 0x00},
+	{0x3713, 0x02},
+	{0x3778, 0x00},
+	{0x379b, 0x01},
+	{0x379c, 0x10},
+	{0x3800, 0x00},
+	{0x3801, 0x00},
+	{0x3802, 0x00},
+	{0x3803, 0x00},
+	{0x3804, 0x06},
+	{0x3805, 0x4f},
+	{0x3806, 0x05},
+	{0x3807, 0x23},
+	{0x3808, 0x06},
+	{0x3809, 0x40},
+	{0x380a, 0x05},
+	{0x380b, 0x14},
+	{0x380c, 0x03},
+	{0x380d, 0xa8},
+	{0x380e, (OV2312_VTS >> 8) & 0xff},
+	{0x380f, OV2312_VTS & 0xff},
+	{0x3810, 0x00},
+	{0x3811, 0x08},
+	{0x3812, 0x00},
+	{0x3813, 0x08},
+	{0x3814, 0x11},
+	{0x3815, 0x11},
+	{0x3816, 0x00},
+	{0x3817, 0x01},
+	{0x3818, 0x00},
+	{0x3819, 0x05},
+	{0x382b, 0x5a},
+	{0x382c, 0x0a},
+	{0x382d, 0xf8},
+	{0x3881, 0x44},
+	{0x3882, 0x02},
+	{0x3883, 0x8c},
+	{0x3885, 0x07},
+	{0x389d, 0x03},
+	{0x38a6, 0x00},
+	{0x38a7, 0x01},
+	{0x38b3, 0x07},
+	{0x38b1, 0x00},
+	{0x38e5, 0x02},
+	{0x38e7, 0x00},
+	{0x38e8, 0x00},
+	{0x3910, 0xff},
+	{0x3911, 0xff},
+	{0x3912, 0x08},
+	{0x3913, 0x00},
+	{0x3914, 0x00},
+	{0x3915, 0x00},
+	{0x391c, 0x00},
+	{0x3920, 0xff},
+	{0x3921, 0x80},
+	{0x3922, 0x00},
+	{0x3923, 0x00},
+	{0x3924, 0x05},
+	{0x3925, 0x00},
+	{0x3926, 0x00},
+	{0x3927, 0x00},
+	{0x3928, 0x1a},
+	{0x392d, 0x03},
+	{0x392e, 0xa8},
+	{0x392f, 0x08},
+	{0x4001, 0x00},
+	{0x4003, 0x40},
+	{0x4008, 0x04},
+	{0x4009, 0x1b},
+	{0x400c, 0x04},
+	{0x400d, 0x1b},
+	{0x4010, 0xf4},
+	{0x4011, 0x00},
+	{0x4016, 0x00},
+	{0x4017, 0x04},
+	{0x4042, 0x11},
+	{0x4043, 0x70},
+	{0x4045, 0x00},
+	{0x4409, 0x5f},
+	{0x4509, 0x00},
+	{0x450b, 0x00},
+	{0x4600, 0x00},
+	{0x4601, 0x80},
+	{0x4708, 0x09},
+	{0x470c, 0x81},
+	{0x4710, 0x06},
+	{0x4711, 0x00},
+	{0x4800, 0x00},
+	{0x481f, 0x30},
+	{0x4837, 0x14},
+	{0x4f00, 0x00},
+	{0x4f07, 0x00},
+	{0x4f08, 0x03},
+	{0x4f09, 0x08},
+	{0x4f0c, 0x05},
+	{0x4f0d, 0xb4},
+	{0x4f10, 0x00},
+	{0x4f11, 0x00},
+	{0x4f12, 0x07},
+	{0x4f13, 0xe2},
+	{0x5000, 0x9f},
+	{0x5001, 0x20},
+	{0x5026, 0x00},
+	{0x5c00, 0x00},
+	{0x5c01, 0x2c},
+	{0x5c02, 0x00},
+	{0x5c03, 0x7f},
+	{0x5e00, 0x00},
+	{0x5e01, 0x41},
+	{0x38b1, 0x02},
+	{0x0100, 0x01},
+	{0x3006, 0x08},/* Strobe control  */
+	{0x3004, 0x02},
+	{0x3007, 0x02},
+	{0x301c, 0x20},
+	{0x3020, 0x20},
+	{0x3025, 0x02},
+	{0x382c, 0x0a},
+	{0x382d, 0xf8},
+	{0x3920, 0xff},
+	{0x3921, 0x00},
+	{0x3923, 0x00},
+	{0x3924, 0x00},
+	{0x3925, 0x00},
+	{0x3926, 0x00},
+	{0x3927, 0x00},
+	{0x3928, 0x80},
+	{0x392b, 0x00},
+	{0x392c, 0x00},
+	{0x392d, 0x03},
+	{0x392e, 0xa8},
+	{0x392f, 0x0b},
+	{0x38b3, 0x07},
+	{0x3885, 0x07},
+	{0x382b, 0x3a},
+	{0x3670, 0x68},
+	{0x3016, 0xF1},
+	{0x0100, 0x01},
+	{0x4814, 0x6B},
+	{0x3218, 0x32},
+	{0x3216, 0x01},
+	{0x3208, 0x04},
+	{0x4813, 0x01},
+	{0x321a, 0x01},
+	{0x3920, 0x01},
+	{0x3501, 0x02},
+	{0x3508, 0x02},
+	{0x350e, 0x02},
+	{0x3208, 0x14},
+	{0x3662, 0x65},
+	{0x366F, 0x1A},
+	{0x3674, 0x11},
+	{0x3016, 0xF0},
+	{0x301C, 0xF0},/* AB mode - Group auto switch example setting */
+	{0x3209, 0x01},/* Stay in Group A for 1 Frame */
+	{0x320A, 0x01},/* Stay in Group B for 1 Frame */
+	{0x320B, 0x00},
+	{0x320C, 0x00},
+	{0x3208, 0x00},/* Group A (IR Dominant VC0) default values */
+	{OV2312_AEC_PK_EXPO_HI, (OV2312_IR_EXPOSURE >> 8) & 0xff},
+	{OV2312_AEC_PK_EXPO_LO, OV2312_IR_EXPOSURE & 0xff},
+	{OV2312_AEC_PK_AGAIN_HI, 0x01},
+	{OV2312_AEC_PK_AGAIN_LO, 0x00},
+	{OV2312_AEC_PK_DGAIN_HI, 0x01},
+	{OV2312_AEC_PK_DGAIN_LO, 0x00},
+	{0x3920, 0xff},/* IR Strobe duty cycle */
+	{0x3927, (OV2312_IR_STROBE >> 8) & 0xff},
+	{0x3928, OV2312_IR_STROBE & 0xff},
+	{0x3929, (OV2312_IR_STROBE_START >> 8) & 0xff},
+	{0x392a, OV2312_IR_STROBE_START & 0xff},
+	{0x4813, 0x01},/* VC=1. This register takes effect from next frame */
+	{0x3208, 0x10},
+
+};
-- 
2.34.1


  parent reply	other threads:[~2026-09-25 13:31 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-25 13:29 [RFC PATCH 0/8] Add OmniVision OV2312 RGB-IR sensor driver Rishikesh Donadkar
2026-09-25 13:29 ` [RFC PATCH 1/8] dt-bindings: media: Add bindings for Omnivision OV2312 Rishikesh Donadkar
2026-09-26 14:51   ` Laurent Pinchart
2026-09-25 13:29 ` [RFC PATCH 2/8] media: v4l: Add 10-bit RGBIr formats Rishikesh Donadkar
2026-09-26 14:30   ` Sakari Ailus
2026-09-26 14:40     ` Laurent Pinchart
2026-09-27  5:09       ` Rishikesh Donadkar
2026-09-27  5:08     ` Rishikesh Donadkar
2026-09-27  5:59       ` Sakari Ailus
2026-09-25 13:29 ` [RFC PATCH 3/8] media: i2c: ds90ub960: " Rishikesh Donadkar
2026-09-25 13:29 ` [RFC PATCH 4/8] media: cadence: csi2rx: Add RAW10 " Rishikesh Donadkar
2026-09-26 14:52   ` Laurent Pinchart
2026-09-27  5:20     ` Rishikesh Donadkar
2026-09-27 14:19       ` Laurent Pinchart
2026-09-25 13:29 ` [RFC PATCH 5/8] media: ti: j721e-csi2rx: " Rishikesh Donadkar
2026-09-25 13:29 ` Rishikesh Donadkar [this message]
2026-10-02 16:16   ` [RFC PATCH 6/8] media: i2c: ov2312: add Omnivison OV2312 driver Mirela Rabulea
2026-10-03  2:05     ` Jai Luthra
2026-10-05 18:12       ` Mirela Rabulea
2026-10-06  5:19     ` Rishikesh Donadkar
2026-10-06 12:33       ` [EXT] " Mirela Rabulea
2026-10-06  7:36     ` Sakari Ailus
2026-10-06 13:55       ` Mirela Rabulea
2026-10-06 15:37         ` Laurent Pinchart
2026-09-25 13:30 ` [RFC PATCH 7/8] arm64: dts: ti: k3-am62a7: FPDLink overlays for LI OV2312 Rishikesh Donadkar
2026-09-26 14:57   ` Laurent Pinchart
2026-09-27  5:23     ` Rishikesh Donadkar
2026-09-25 13:30 ` [RFC PATCH 8/8] arm64: defconfig: Enable OV2312 Rishikesh Donadkar
2026-09-26 14:55   ` Laurent Pinchart

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=20260925133001.2780868-7-r-donadkar@ti.com \
    --to=r-donadkar@ti.com \
    --cc=conor+dt@kernel.org \
    --cc=devarsht@ti.com \
    --cc=devicetree@vger.kernel.org \
    --cc=jai.luthra@ideasonboard.com \
    --cc=jai.luthra@linux.dev \
    --cc=kristo@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=laurent.pinchart@ideasonboard.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=mchehab@kernel.org \
    --cc=mirela.rabulea@nxp.com \
    --cc=mripard@kernel.org \
    --cc=nm@ti.com \
    --cc=robh@kernel.org \
    --cc=sakari.ailus@linux.intel.com \
    --cc=tomi.valkeinen@ideasonboard.com \
    --cc=vigneshr@ti.com \
    --cc=y-abhilashchandra@ti.com \
    /path/to/YOUR_REPLY

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

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

all inboxes | Powered by JetHome®