All of lore.kernel.org
 help / color / mirror / Atom feed
From: Alexander Patrakov <patrakov@gmail.com>
To: johannes.goede@oss.qualcomm.com
Cc: adee.sahan@gmail.com, andy@kernel.org,
	antti.laakso@linux.intel.com, dan.scally@ideasonboard.com,
	diamondback@cohunt.app, ilpo.jarvinen@linux.intel.com,
	linux-media@vger.kernel.org, platform-driver-x86@vger.kernel.org,
	sakari.ailus@linux.intel.com,
	Alexander Patrakov <patrakov@gmail.com>
Subject: [PATCH 3/3] media: i2c: ov5675: Add the OVTI5678 RGB-IR variant
Date: Tue, 20 Oct 2026 09:50:01 +0800	[thread overview]
Message-ID: <20261020015001.15672-3-patrakov@gmail.com> (raw)
In-Reply-To: <20261020015001.15672-1-patrakov@gmail.com>

The front camera of the Lenovo Yoga Duet 7 13ITL6 and of the Dell
Latitude 7320 Detachable is described in ACPI as OVTI5678. The part
reports the chip id of the OV5675 and takes its register settings, but
its color filter array is a complex 4x4 RGB-IR one.

The silicon is likely the same as the one of the OVTI5675, so it still
has some features (binned mode, hardware defective pixel concealment)
that assume the classic 2x2 GRBG Bayer array and thus produce meaningless
output with the RGB-IR array. Disable them for OVTI5678.

NOTE: This patch cannot be upstreamed as there is no way to properly
describe the color filter array layout of the OVTI5678. The color format
is a placeholder at this point.

Signed-off-by: Alexander Patrakov <patrakov@gmail.com>
---
 drivers/media/i2c/ov5675.c | 149 ++++++++++++++++++++++++++++++++-----
 1 file changed, 131 insertions(+), 18 deletions(-)

diff --git a/drivers/media/i2c/ov5675.c b/drivers/media/i2c/ov5675.c
index 9cf600c9c94a..7563a462abea 100644
--- a/drivers/media/i2c/ov5675.c
+++ b/drivers/media/i2c/ov5675.c
@@ -6,8 +6,10 @@
 #include <linux/delay.h>
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
 #include <linux/module.h>
 #include <linux/pm_runtime.h>
+#include <linux/property.h>
 #include <linux/regulator/consumer.h>
 #include <linux/unaligned.h>
 
@@ -28,6 +30,10 @@
 #define OV5675_REG_CHIP_ID		0x300a
 #define OV5675_CHIP_ID			0x5675
 
+/* On-chip processing; bits 1 and 2 enable the defect pixel correction */
+#define OV5675_REG_ISP_CTRL		0x5000
+#define OV5675_ISP_CTRL_NO_DPC		0x71
+
 #define OV5675_REG_MODE_SELECT		0x0100
 #define OV5675_MODE_STANDBY		0x00
 #define OV5675_MODE_STREAMING		0x01
@@ -492,8 +498,58 @@ static const struct ov5675_mode supported_modes[] = {
 	}
 };
 
+/* The modes of the RGB-IR part, see ov5675_get_modes() */
+static const struct ov5675_mode ov5678_supported_modes[] = {
+	{
+		.width = 2592,
+		.height = 1944,
+		.hts = 1500,
+		.vts_def = OV5675_VTS_30FPS,
+		.vts_min = OV5675_VTS_30FPS_MIN,
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_2592x1944_regs),
+			.regs = mode_2592x1944_regs,
+		},
+		.link_freq_index = OV5675_LINK_FREQ_900MBPS,
+	}
+};
+
+/**
+ * struct ov5675_variant - What differs between the parts this driver handles
+ * @model: Name of the sub-device, by which user space tells the parts apart
+ * @code: Media bus code of the output
+ */
+struct ov5675_variant {
+	const char *model;
+	u32 code;
+};
+
+static const struct ov5675_variant ov5675_variant_bayer = {
+	.model = "ov5675",
+	.code = MEDIA_BUS_FMT_SGRBG10_1X10,
+};
+
+/*
+ * The part ACPI calls OVTI5678 reports the chip id of the OV5675 and takes
+ * its register settings, but has a 4x4 RGB-IR color filter array:
+ *
+ *	G I G I
+ *	R G B G
+ *	G I G I
+ *	B G R G
+ *
+ * No media bus code describes that. MEDIA_BUS_FMT_Y10_1X10 is used because it
+ * is the 10-bit code that claims no Bayer order, so that nothing demosaics the
+ * data as if it were Bayer. The samples are not luma.
+ */
+static const struct ov5675_variant ov5675_variant_rgbir = {
+	.model = "ov5678",
+	.code = MEDIA_BUS_FMT_Y10_1X10,
+};
+
 struct ov5675 {
 	struct device *dev;
+	const struct ov5675_variant *variant;
 
 	struct v4l2_subdev sd;
 	struct media_pad pad;
@@ -857,12 +913,30 @@ static int ov5675_init_controls(struct ov5675 *ov5675)
 	return ret;
 }
 
-static void ov5675_update_pad_format(const struct ov5675_mode *mode,
+/*
+ * The 1296x972 mode is 2x2 binned. The hardware averages pixels two positions
+ * apart, which have the same color in a Bayer array, but not in the RGB-IR
+ * array. Therefore, the RGB-IR color filter array is incompatible with binning.
+ */
+static const struct ov5675_mode *ov5675_get_modes(const struct ov5675 *ov5675,
+						  unsigned int *num_modes)
+{
+	if (ov5675->variant->code != MEDIA_BUS_FMT_SGRBG10_1X10) {
+		*num_modes = ARRAY_SIZE(ov5678_supported_modes);
+		return ov5678_supported_modes;
+	}
+
+	*num_modes = ARRAY_SIZE(supported_modes);
+	return supported_modes;
+}
+
+static void ov5675_update_pad_format(struct ov5675 *ov5675,
+				     const struct ov5675_mode *mode,
 				     struct v4l2_mbus_framefmt *fmt)
 {
 	fmt->width = mode->width;
 	fmt->height = mode->height;
-	fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10;
+	fmt->code = ov5675->variant->code;
 	fmt->field = V4L2_FIELD_NONE;
 }
 
@@ -914,6 +988,25 @@ static int ov5675_start_streaming(struct ov5675 *ov5675)
 		return ret;
 	}
 
+	/*
+	 * The mode settings enable the on-chip defect pixel correction. It
+	 * compares a pixel with the ones two positions away, which have the
+	 * same color in a Bayer array. In the RGB-IR array the pixel two
+	 * positions from a red one is blue, and so comparing or mixing them
+	 * makes no sense. So defect pixel correction is on for the Bayer part
+	 * only. Intel's Windows driver for the OVTI5678 turns it off in the
+	 * same way, by writing 0x71 to this register.
+	 */
+	if (ov5675->variant->code != MEDIA_BUS_FMT_SGRBG10_1X10) {
+		ret = ov5675_write_reg(ov5675, OV5675_REG_ISP_CTRL,
+				       OV5675_REG_VALUE_08BIT,
+				       OV5675_ISP_CTRL_NO_DPC);
+		if (ret) {
+			dev_err(ov5675->dev, "failed to disable defect correction");
+			return ret;
+		}
+	}
+
 	ret = __v4l2_ctrl_handler_setup(ov5675->sd.ctrl_handler);
 	if (ret)
 		return ret;
@@ -1020,16 +1113,16 @@ static int ov5675_set_format(struct v4l2_subdev *sd,
 			     struct v4l2_subdev_format *fmt)
 {
 	struct ov5675 *ov5675 = to_ov5675(sd);
-	const struct ov5675_mode *mode;
+	const struct ov5675_mode *modes, *mode;
+	unsigned int num_modes;
 	s32 vblank_def, h_blank;
 
-	mode = v4l2_find_nearest_size(supported_modes,
-				      ARRAY_SIZE(supported_modes), width,
-				      height, fmt->format.width,
-				      fmt->format.height);
+	modes = ov5675_get_modes(ov5675, &num_modes);
+	mode = v4l2_find_nearest_size(modes, num_modes, width, height,
+				      fmt->format.width, fmt->format.height);
 
 	mutex_lock(&ov5675->mutex);
-	ov5675_update_pad_format(mode, &fmt->format);
+	ov5675_update_pad_format(ov5675, mode, &fmt->format);
 	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
 		*v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format;
 	} else {
@@ -1067,7 +1160,8 @@ static int ov5675_get_format(struct v4l2_subdev *sd,
 		fmt->format = *v4l2_subdev_state_get_format(sd_state,
 							    fmt->pad);
 	else
-		ov5675_update_pad_format(ov5675->cur_mode, &fmt->format);
+		ov5675_update_pad_format(ov5675, ov5675->cur_mode,
+					 &fmt->format);
 
 	mutex_unlock(&ov5675->mutex);
 
@@ -1104,10 +1198,12 @@ static int ov5675_enum_mbus_code(struct v4l2_subdev *sd,
 				 struct v4l2_subdev_state *sd_state,
 				 struct v4l2_subdev_mbus_code_enum *code)
 {
+	struct ov5675 *ov5675 = to_ov5675(sd);
+
 	if (code->index > 0)
 		return -EINVAL;
 
-	code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
+	code->code = ov5675->variant->code;
 
 	return 0;
 }
@@ -1116,15 +1212,20 @@ static int ov5675_enum_frame_size(struct v4l2_subdev *sd,
 				  struct v4l2_subdev_state *sd_state,
 				  struct v4l2_subdev_frame_size_enum *fse)
 {
-	if (fse->index >= ARRAY_SIZE(supported_modes))
+	struct ov5675 *ov5675 = to_ov5675(sd);
+	const struct ov5675_mode *modes;
+	unsigned int num_modes;
+
+	modes = ov5675_get_modes(ov5675, &num_modes);
+	if (fse->index >= num_modes)
 		return -EINVAL;
 
-	if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
+	if (fse->code != ov5675->variant->code)
 		return -EINVAL;
 
-	fse->min_width = supported_modes[fse->index].width;
+	fse->min_width = modes[fse->index].width;
 	fse->max_width = fse->min_width;
-	fse->min_height = supported_modes[fse->index].height;
+	fse->min_height = modes[fse->index].height;
 	fse->max_height = fse->min_height;
 
 	return 0;
@@ -1133,9 +1234,13 @@ static int ov5675_enum_frame_size(struct v4l2_subdev *sd,
 static int ov5675_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
 {
 	struct ov5675 *ov5675 = to_ov5675(sd);
+	const struct ov5675_mode *modes;
+	unsigned int num_modes;
+
+	modes = ov5675_get_modes(ov5675, &num_modes);
 
 	mutex_lock(&ov5675->mutex);
-	ov5675_update_pad_format(&supported_modes[0],
+	ov5675_update_pad_format(ov5675, &modes[0],
 				 v4l2_subdev_state_get_format(fh->state, 0));
 	mutex_unlock(&ov5675->mutex);
 
@@ -1281,6 +1386,7 @@ static void ov5675_remove(struct i2c_client *client)
 static int ov5675_probe(struct i2c_client *client)
 {
 	struct ov5675 *ov5675;
+	unsigned int num_modes;
 	bool full_power;
 	int ret;
 
@@ -1290,11 +1396,17 @@ static int ov5675_probe(struct i2c_client *client)
 
 	ov5675->dev = &client->dev;
 
+	ov5675->variant = device_get_match_data(ov5675->dev);
+	if (!ov5675->variant)
+		ov5675->variant = &ov5675_variant_bayer;
+
 	ret = ov5675_get_hwcfg(ov5675);
 	if (ret)
 		return ret;
 
 	v4l2_i2c_subdev_init(&ov5675->sd, client, &ov5675_subdev_ops);
+	v4l2_i2c_subdev_set_name(&ov5675->sd, client, ov5675->variant->model,
+				 NULL);
 
 	ret = ov5675_power_on(ov5675->dev);
 	if (ret) {
@@ -1312,7 +1424,7 @@ static int ov5675_probe(struct i2c_client *client)
 	}
 
 	mutex_init(&ov5675->mutex);
-	ov5675->cur_mode = &supported_modes[0];
+	ov5675->cur_mode = ov5675_get_modes(ov5675, &num_modes);
 	ret = ov5675_init_controls(ov5675);
 	if (ret) {
 		dev_err(ov5675->dev, "failed to init controls: %d", ret);
@@ -1363,7 +1475,8 @@ static const struct dev_pm_ops ov5675_pm_ops = {
 
 #ifdef CONFIG_ACPI
 static const struct acpi_device_id ov5675_acpi_ids[] = {
-	{"OVTI5675"},
+	{"OVTI5675", (kernel_ulong_t)&ov5675_variant_bayer},
+	{"OVTI5678", (kernel_ulong_t)&ov5675_variant_rgbir},
 	{}
 };
 
@@ -1371,7 +1484,7 @@ MODULE_DEVICE_TABLE(acpi, ov5675_acpi_ids);
 #endif
 
 static const struct of_device_id ov5675_of_match[] = {
-	{ .compatible = "ovti,ov5675", },
+	{ .compatible = "ovti,ov5675", .data = &ov5675_variant_bayer },
 	{ /* sentinel */ },
 };
 MODULE_DEVICE_TABLE(of, ov5675_of_match);
-- 
2.56.0


  parent reply	other threads:[~2026-10-20  1:50 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-16 14:17 [PATCH 0/5] platform/x86: int3472: Fix AVDD and add DOVDD + DVDD mappings Hans de Goede
2026-10-16 14:17 ` [PATCH 1/5] media: ov05c10: Support full range of power rails Hans de Goede
2026-10-17  7:20   ` Andy Shevchenko
2026-10-19 10:27   ` Dan Scally
2026-10-16 14:17 ` [PATCH 2/5] platform/x86: int3472: Rename DOVDD GPIO type to AVDD Hans de Goede
2026-10-19  7:32   ` Andy Shevchenko
2026-10-19 10:54   ` Dan Scally
2026-10-16 14:17 ` [PATCH 3/5] platform/x86: int3472: Add support for DOVDD and DVDD GPIO types Hans de Goede
2026-10-19  9:01   ` Andy Shevchenko
2026-10-19 11:02   ` Dan Scally
2026-10-16 14:17 ` [PATCH 4/5] platform/x86: int3472: Rename avdd_second_sensor to power_enable_second_sensor Hans de Goede
2026-10-19 10:52   ` Andy Shevchenko
2026-10-19 10:54   ` Dan Scally
2026-10-16 14:17 ` [PATCH 5/5] platform/x86: int3472: Properly map the AVDD GPIO type to a "avdd" regulator Hans de Goede
2026-10-19 11:01   ` Andy Shevchenko
2026-10-19 11:02   ` Dan Scally
2026-10-20  1:41 ` [PATCH 0/5] platform/x86: int3472: Fix AVDD and add DOVDD + DVDD mappings Alexander Patrakov
2026-10-20  1:49   ` [PATCH 1/3] media: ipu-bridge: Add sensor configuration for OV5675 (OVTI5678) Alexander Patrakov
2026-10-20  1:50     ` [PATCH 2/3] media: i2c: ov5675: Defer the probe until the endpoint exists Alexander Patrakov
2026-10-20  6:34       ` Andy Shevchenko
2026-10-20  8:39       ` Sakari Ailus
2026-10-20  1:50     ` Alexander Patrakov [this message]
2026-10-20  6:42       ` [PATCH 3/3] media: i2c: ov5675: Add the OVTI5678 RGB-IR variant Andy Shevchenko
2026-10-20  6:46         ` Alexander Patrakov
2026-10-20  8:42       ` Sakari Ailus
2026-10-20  8:57         ` Andy Shevchenko
2026-10-20 12:13           ` Uwe Kleine-König
2026-10-20  6:07     ` [PATCH 1/3] media: ipu-bridge: Add sensor configuration for OV5675 (OVTI5678) Andy Shevchenko

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=20261020015001.15672-3-patrakov@gmail.com \
    --to=patrakov@gmail.com \
    --cc=adee.sahan@gmail.com \
    --cc=andy@kernel.org \
    --cc=antti.laakso@linux.intel.com \
    --cc=dan.scally@ideasonboard.com \
    --cc=diamondback@cohunt.app \
    --cc=ilpo.jarvinen@linux.intel.com \
    --cc=johannes.goede@oss.qualcomm.com \
    --cc=linux-media@vger.kernel.org \
    --cc=platform-driver-x86@vger.kernel.org \
    --cc=sakari.ailus@linux.intel.com \
    /path/to/YOUR_REPLY

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

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