[PATCH] media: i2c: Add OV05C camera sensor driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

Signed-off-by: Pratap Nirujogi <pratap.nirujogi@xxxxxxx>
---
 drivers/media/i2c/Kconfig  |   10 +
 drivers/media/i2c/Makefile |    1 +
 drivers/media/i2c/ov05c.c  | 1031 ++++++++++++++++++++++++++++++++++++
 3 files changed, 1042 insertions(+)
 create mode 100644 drivers/media/i2c/ov05c.c

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 8ba096b8ebca..fd160feabc41 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -337,6 +337,16 @@ config VIDEO_OG01A1B
 	  To compile this driver as a module, choose M here: the
 	  module will be called og01a1b.
 
+config VIDEO_OV05C
+	tristate "OmniVision OV05 sensor support"
+	select V4L2_CCI_I2C
+	help
+	  This is a Video4Linux2 sensor driver for the OmniVision
+	  OV05C camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called OV05C.
+
 config VIDEO_OV01A10
 	tristate "OmniVision OV01A10 sensor support"
 	help
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index fbb988bd067a..08bfc2d59be2 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -80,6 +80,7 @@ obj-$(CONFIG_VIDEO_MT9V011) += mt9v011.o
 obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o
 obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o
 obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o
+obj-$(CONFIG_VIDEO_OV05C) += ov05c.o
 obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
 obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
 obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
diff --git a/drivers/media/i2c/ov05c.c b/drivers/media/i2c/ov05c.c
new file mode 100644
index 000000000000..96c4f74af4a9
--- /dev/null
+++ b/drivers/media/i2c/ov05c.c
@@ -0,0 +1,1031 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (C) 2025 Advanced Micro Devices, Inc. All rights reserved.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include <linux/acpi.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/units.h>
+#include <linux/pm_runtime.h>
+#include <linux/gpio.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-cci.h>
+
+#define DRV_NAME			"ov05c"
+
+/* Chip ID */
+#define OV05C_REG_CHIP_ID		CCI_REG24(0x00)
+#define OV05C_CHIP_ID			0x430556
+
+/* Control registers */
+#define OV05C_REG_PAGE_CTL		CCI_REG8(0xFD)
+#define OV05C_REG_TRIGGER		CCI_REG8(0x01)
+
+/* V_TIMING internal */
+#define OV05C_REG_VTS			CCI_REG16(0x05)
+#define OV05C_VTS_30FPS			1860
+#define OV05C_VTS_MAX			0x7fff
+
+/* H TIMING internal */
+#define OV05C_REG_HTS			CCI_REG16(0x37)
+#define OV05C_HTS_30FPS			0x0280
+
+/* Exposure control */
+#define OV05C_REG_EXPOSURE		CCI_REG24(0x02)
+#define OV05C_EXPOSURE_MAX_MARGIN	33
+#define OV05C_EXPOSURE_MIN		4
+#define OV05C_EXPOSURE_STEP		1
+#define OV05C_EXPOSURE_DEFAULT		0x40
+
+/* Analog gain control */
+#define OV05C_REG_ANALOG_GAIN		CCI_REG8(0x24)
+#define OV05C_ANA_GAIN_MIN		0x80
+#define OV05C_ANA_GAIN_MAX		0x07c0
+#define OV05C_ANA_GAIN_STEP		1
+#define OV05C_ANA_GAIN_DEFAULT		0x80
+
+/* Digital gain control */
+#define OV05C_REG_DGTL_GAIN_H		CCI_REG8(0x21)
+#define OV05C_REG_DGTL_GAIN_L		CCI_REG8(0x22)
+
+#define OV05C_DGTL_GAIN_MIN		64		/* Min = 1 X */
+#define OV05C_DGTL_GAIN_MAX		(256 - 1)	/* Max = 4 X */
+#define OV05C_DGTL_GAIN_DEFAULT		0x80		/* Default gain = 2x */
+#define OV05C_DGTL_GAIN_STEP		1		/* Each step = 1/64 */
+
+#define OV05C_DGTL_GAIN_L_MASK		0xFF
+#define OV05C_DGTL_GAIN_H_SHIFT		8
+#define OV05C_DGTL_GAIN_H_MASK		0xFF00
+
+/* Test Pattern Control */
+#define OV05C_REG_TEST_PATTERN_CTL	CCI_REG8(0xF3)
+#define OV05C_REG_TEST_PATTERN		CCI_REG8(0x12)
+#define OV05C_TEST_PATTERN_ENABLE	BIT(0)
+
+#define NUM_OF_PADS			3
+
+enum {
+	OV05C_LINK_FREQ_900MHZ_INDEX,
+};
+
+struct ov05c_reg_list {
+	u32 num_of_regs;
+	const struct cci_reg_sequence *regs;
+};
+
+/* Link frequency config */
+struct ov05c_link_freq_config {
+	/* registers for this link frequency */
+	struct ov05c_reg_list reg_list;
+};
+
+/* Mode : resolution and related config&values */
+struct ov05c_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 ov05c_reg_list reg_list;
+};
+
+static const struct cci_reg_sequence
+	mode_2888_1808_30fps_1800mbps_2lane_24mhz_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_OV05C_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_OV05C_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 ov05c_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 OV05C_LINK_FREQ_900MHZ		(900 * HZ_PER_MHZ)
+
+/* Number of lanes supported */
+#define OV05C_DATA_LANES		2
+
+/* Bits per sample of sensor output */
+#define OV05C_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)
+{
+	f *= 2 * OV05C_DATA_LANES;
+	do_div(f, OV05C_BITS_PER_SAMPLE);
+
+	return f;
+}
+
+/* Menu items for LINK_FREQ V4L2 control */
+static const s64 ov05c_link_freq_menu_items[] = {
+	OV05C_LINK_FREQ_900MHZ,
+};
+
+/* Mode configs */
+static const struct ov05c_mode supported_modes[] = {
+	{
+		.width = 2888,
+		.height = 1808,
+		.vts_def = OV05C_VTS_30FPS,
+		.vts_min = OV05C_VTS_30FPS,
+		.hts = 640,
+		.lanes = 2,
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_2888_1808_30fps_1800mbps_2lane_24mhz_regs),
+			.regs = mode_2888_1808_30fps_1800mbps_2lane_24mhz_regs,
+		},
+		.link_freq_index = OV05C_LINK_FREQ_900MHZ_INDEX,
+	},
+};
+
+struct ov05c {
+	struct v4l2_subdev sd;
+	struct media_pad pads[NUM_OF_PADS];
+
+	/* 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;
+
+	/* Current mode */
+	const struct ov05c_mode *cur_mode;
+
+	struct regmap *regmap;
+
+	/* Mutex for serialized access */
+	struct mutex mutex;
+
+	/* gpio descriptor */
+	struct gpio_desc *enable_gpio;
+};
+
+#define to_ov05c(_sd)	container_of(_sd, struct ov05c, sd)
+
+static int ov05c_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	const struct ov05c_mode *default_mode = &supported_modes[0];
+	struct ov05c *ov05c = to_ov05c(sd);
+	struct v4l2_mbus_framefmt *try_fmt =
+		v4l2_subdev_state_get_format(fh->state, 0);
+
+	mutex_lock(&ov05c->mutex);
+
+	/* Initialize try_fmt */
+	try_fmt->width = default_mode->width;
+	try_fmt->height = default_mode->height;
+	try_fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10;
+	try_fmt->field = V4L2_FIELD_NONE;
+
+	/* No crop or compose */
+	mutex_unlock(&ov05c->mutex);
+
+	return 0;
+}
+
+static int ov05c_update_cid_vblank(struct ov05c *ov05c, u32 vblank)
+{
+	int ret = 0;
+
+	cci_write(ov05c->regmap, OV05C_REG_PAGE_CTL,
+		  BIT(0), &ret);
+	if (ret)
+		return ret;
+
+	cci_write(ov05c->regmap, OV05C_REG_VTS,
+		  ov05c->cur_mode->height
+		  + vblank, &ret);
+	if (ret)
+		return ret;
+
+	cci_write(ov05c->regmap, OV05C_REG_TRIGGER,
+		  BIT(0), &ret);
+
+	return ret;
+}
+
+static int ov05c_update_cid_exposure(struct ov05c *ov05c, u32 exposure)
+{
+	int ret = 0;
+
+	cci_write(ov05c->regmap, OV05C_REG_PAGE_CTL,
+		  BIT(0), &ret);
+	if (ret)
+		return ret;
+
+	cci_write(ov05c->regmap, OV05C_REG_EXPOSURE,
+		  exposure, &ret);
+	if (ret)
+		return ret;
+
+	cci_write(ov05c->regmap, OV05C_REG_TRIGGER,
+		  BIT(0), &ret);
+
+	return ret;
+}
+
+static int ov05c_update_analog_gain(struct ov05c *ov05c, u32 a_gain)
+{
+	int ret;
+
+	ret = cci_write(ov05c->regmap, OV05C_REG_PAGE_CTL, BIT(0), NULL);
+	if (ret)
+		return ret;
+
+	ret = cci_write(ov05c->regmap, OV05C_REG_ANALOG_GAIN, a_gain, NULL);
+	if (ret)
+		return ret;
+
+	return cci_write(ov05c->regmap, OV05C_REG_TRIGGER, BIT(0), NULL);
+}
+
+static int ov05_update_digital_gain(struct ov05c *ov05c, u32 d_gain)
+{
+	int ret;
+	u64 val;
+
+	ret = cci_write(ov05c->regmap, OV05C_REG_PAGE_CTL, BIT(0), NULL);
+	if (ret)
+		return ret;
+
+	val = d_gain & OV05C_DGTL_GAIN_L_MASK;
+	ret = cci_write(ov05c->regmap, OV05C_REG_DGTL_GAIN_L, val, NULL);
+	if (ret)
+		return ret;
+
+	val = (d_gain & OV05C_DGTL_GAIN_H_MASK) >> OV05C_DGTL_GAIN_H_SHIFT;
+
+	ret = cci_write(ov05c->regmap, OV05C_REG_DGTL_GAIN_H, val, NULL);
+	if (ret)
+		return ret;
+
+	return cci_write(ov05c->regmap, OV05C_REG_TRIGGER, BIT(0), NULL);
+}
+
+static int ov05c_enable_test_pattern(struct ov05c *ov05c, u32 pattern)
+{
+	int ret;
+	u64 val;
+
+	ret = cci_write(ov05c->regmap, OV05C_REG_PAGE_CTL, BIT(2), NULL);
+	if (ret)
+		return ret;
+
+	if (pattern) {
+		ret = cci_read(ov05c->regmap, OV05C_REG_TEST_PATTERN_CTL, &val, NULL);
+		if (ret)
+			return ret;
+
+		ret = cci_write(ov05c->regmap, OV05C_REG_TEST_PATTERN_CTL, val | BIT(1), NULL);
+		if (ret)
+			return ret;
+
+		ret = cci_read(ov05c->regmap, OV05C_REG_TEST_PATTERN, &val, NULL);
+		if (ret)
+			return ret;
+
+		val &= OV05C_TEST_PATTERN_ENABLE;
+	} else {
+		ret = cci_read(ov05c->regmap, OV05C_REG_TEST_PATTERN, &val, NULL);
+		if (ret)
+			return ret;
+
+		val &= ~OV05C_TEST_PATTERN_ENABLE;
+	}
+
+	ret = cci_write(ov05c->regmap, OV05C_REG_TEST_PATTERN, val, NULL);
+	if (ret)
+		return ret;
+
+	return cci_write(ov05c->regmap, OV05C_REG_TRIGGER, BIT(0), NULL);
+}
+
+static int ov05c_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct ov05c *ov05c = container_of(ctrl->handler,
+					  struct ov05c, ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&ov05c->sd);
+	s64 max;
+	int ret = 0;
+
+	/* Propagate change of current control to all related controls */
+	switch (ctrl->id) {
+	case V4L2_CID_VBLANK:
+		/* Update max exposure while meeting expected vblanking */
+		max = ov05c->cur_mode->height + ctrl->val - OV05C_EXPOSURE_MAX_MARGIN;
+		__v4l2_ctrl_modify_range(ov05c->exposure,
+					 ov05c->exposure->minimum,
+					 max, ov05c->exposure->step, max);
+		break;
+	}
+
+	/*
+	 * 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 = ov05c_update_analog_gain(ov05c, ctrl->val);
+		break;
+	case V4L2_CID_DIGITAL_GAIN:
+		ret = ov05_update_digital_gain(ov05c, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		ret = ov05c_update_cid_exposure(ov05c, ctrl->val);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = ov05c_update_cid_vblank(ov05c, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		ret = ov05c_enable_test_pattern(ov05c, ctrl->val);
+		break;
+	default:
+		dev_info(&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 ov05c_ctrl_ops = {
+	.s_ctrl = ov05c_set_ctrl,
+};
+
+static int ov05c_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 ov05c_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))
+		return -EINVAL;
+
+	if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
+		return -EINVAL;
+
+	fse->min_width = supported_modes[fse->index].width;
+	fse->max_width = fse->min_width;
+	fse->min_height = supported_modes[fse->index].height;
+	fse->max_height = fse->min_height;
+
+	return 0;
+}
+
+static void ov05c_update_pad_format(const struct ov05c_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 ov05c_do_get_pad_format(struct ov05c *ov05c,
+				   struct v4l2_subdev_state *sd_state,
+				   struct v4l2_subdev_format *fmt)
+{
+	struct v4l2_mbus_framefmt *framefmt;
+
+	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+		framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
+		fmt->format = *framefmt;
+	} else {
+		ov05c_update_pad_format(ov05c->cur_mode, fmt);
+	}
+
+	return 0;
+}
+
+static int ov05c_get_pad_format(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *sd_state,
+				struct v4l2_subdev_format *fmt)
+{
+	struct ov05c *ov05c = to_ov05c(sd);
+	int ret;
+
+	mutex_lock(&ov05c->mutex);
+	ret = ov05c_do_get_pad_format(ov05c, sd_state, fmt);
+	mutex_unlock(&ov05c->mutex);
+
+	return ret;
+}
+
+static int ov05c_set_pad_format(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *sd_state,
+				struct v4l2_subdev_format *fmt)
+{
+	struct ov05c *ov05c = to_ov05c(sd);
+	const struct ov05c_mode *mode;
+	struct v4l2_mbus_framefmt *framefmt;
+	s32 vblank_def;
+	s32 vblank_min;
+	s64 h_blank;
+	s64 pixel_rate;
+	s64 link_freq;
+
+	mutex_lock(&ov05c->mutex);
+
+	/* 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 = v4l2_find_nearest_size(supported_modes,
+				      ARRAY_SIZE(supported_modes),
+				      width, height,
+				      fmt->format.width, fmt->format.height);
+	ov05c_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 {
+		ov05c->cur_mode = mode;
+		__v4l2_ctrl_s_ctrl(ov05c->link_freq, mode->link_freq_index);
+		link_freq = ov05c_link_freq_menu_items[mode->link_freq_index];
+		pixel_rate = link_freq_to_pixel_rate(link_freq);
+		__v4l2_ctrl_s_ctrl_int64(ov05c->pixel_rate, pixel_rate);
+
+		/* Update limits and set FPS to default */
+		vblank_def = ov05c->cur_mode->vts_def -
+			     ov05c->cur_mode->height;
+		vblank_min = ov05c->cur_mode->vts_min -
+			     ov05c->cur_mode->height;
+		__v4l2_ctrl_modify_range(ov05c->vblank, vblank_min,
+					 OV05C_VTS_MAX
+					 - ov05c->cur_mode->height,
+					 1,
+					 vblank_def);
+		__v4l2_ctrl_s_ctrl(ov05c->vblank, vblank_def);
+		h_blank = ov05c->cur_mode->hts;
+		__v4l2_ctrl_modify_range(ov05c->hblank, h_blank,
+					 h_blank, 1, h_blank);
+	}
+
+	mutex_unlock(&ov05c->mutex);
+
+	return 0;
+}
+
+static int ov05c_start_streaming(struct ov05c *ov05c)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&ov05c->sd);
+	const struct ov05c_reg_list *reg_list;
+	int ret = 0;
+
+	/* Apply default values of current mode */
+	reg_list = &ov05c->cur_mode->reg_list;
+	cci_multi_reg_write(ov05c->regmap, reg_list->regs, reg_list->num_of_regs,
+			    &ret);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set mode, ret: %d\n", __func__, ret);
+		return ret;
+	}
+
+	/* Apply customized values from user */
+	ret =  __v4l2_ctrl_handler_setup(ov05c->sd.ctrl_handler);
+	if (ret) {
+		dev_err(&client->dev, "__v4l2_ctrl_handler_setup err:%d", ret);
+		return ret;
+	}
+
+	cci_multi_reg_write(ov05c->regmap, mode_OV05C_stream_on_regs,
+			    ARRAY_SIZE(mode_OV05C_stream_on_regs), &ret);
+	if (ret)
+		dev_err(&client->dev, "%s failed to start the streaming\n", __func__);
+
+	return ret;
+}
+
+static int ov05c_stop_streaming(struct ov05c *ov05c)
+{
+	int ret = 0;
+	struct i2c_client *client = v4l2_get_subdevdata(&ov05c->sd);
+
+	cci_multi_reg_write(ov05c->regmap, mode_OV05C_stream_off_regs,
+			    ARRAY_SIZE(mode_OV05C_stream_off_regs), &ret);
+	if (ret)
+		dev_err(&client->dev, "%s failed to stop the streaming\n", __func__);
+
+	return ret;
+}
+
+static void ov05c_sensor_enable(struct ov05c *ov05c, bool enable)
+{
+	if (enable) {
+		gpiod_set_value(ov05c->enable_gpio, 0);
+		usleep_range(10, 20);
+
+		gpiod_set_value(ov05c->enable_gpio, 1);
+		/*  The delay is to make sure the sensor is completely turned on */
+		usleep_range(1000, 2000);
+	} else {
+		gpiod_set_value(ov05c->enable_gpio, 0);
+		usleep_range(10, 20);
+	}
+}
+
+static int ov05c_set_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct ov05c *ov05c = to_ov05c(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int ret = 0;
+
+	mutex_lock(&ov05c->mutex);
+
+	if (enable) {
+		ov05c_sensor_enable(ov05c, true);
+
+		ret = pm_runtime_resume_and_get(&client->dev);
+		if (ret < 0)
+			goto err_unlock;
+
+		/*
+		 * Apply default & customized values
+		 * and then start streaming.
+		 */
+		ret = ov05c_start_streaming(ov05c);
+		if (ret)
+			goto err_rpm_put;
+	} else {
+		ov05c_stop_streaming(ov05c);
+		pm_runtime_put(&client->dev);
+
+		ov05c_sensor_enable(ov05c, false);
+	}
+
+	mutex_unlock(&ov05c->mutex);
+
+	return ret;
+
+err_rpm_put:
+	pm_runtime_put(&client->dev);
+err_unlock:
+	mutex_unlock(&ov05c->mutex);
+
+	return ret;
+}
+
+static const struct v4l2_subdev_video_ops ov05c_video_ops = {
+	.s_stream = ov05c_set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops ov05c_pad_ops = {
+	.enum_mbus_code = ov05c_enum_mbus_code,
+	.get_fmt = ov05c_get_pad_format,
+	.set_fmt = ov05c_set_pad_format,
+	.enum_frame_size = ov05c_enum_frame_size,
+};
+
+static const struct v4l2_subdev_ops ov05c_subdev_ops = {
+	.video = &ov05c_video_ops,
+	.pad = &ov05c_pad_ops,
+};
+
+static const struct media_entity_operations ov05c_subdev_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+static const struct v4l2_subdev_internal_ops ov05c_internal_ops = {
+	.open = ov05c_open,
+};
+
+static int ov05c_init_controls(struct ov05c *ov05c)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&ov05c->sd);
+	struct v4l2_fwnode_device_properties props;
+	struct v4l2_ctrl_handler *ctrl_hdlr;
+	const struct ov05c_mode *mode;
+	s64 exposure_max;
+	s64 vblank_def;
+	s64 vblank_min;
+	s64 hblank;
+	s64 pixel_rate_max;
+	u32 max_items;
+	int ret;
+
+	/* Initialize V4L2 control handler with 10 items */
+	ret = v4l2_ctrl_handler_init(&ov05c->ctrl_handler, 10);
+	if (ret)
+		return ret;
+	ctrl_hdlr = &ov05c->ctrl_handler;
+
+	/* Initialize mutex for serialization */
+	mutex_init(&ov05c->mutex);
+	ctrl_hdlr->lock = &ov05c->mutex;
+
+	/* Initialize Link frequency control item */
+	max_items = ARRAY_SIZE(ov05c_link_freq_menu_items) - 1;
+	ov05c->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr,
+						  NULL,
+						  V4L2_CID_LINK_FREQ,
+						  max_items,
+						  0,
+						  ov05c_link_freq_menu_items);
+	if (ov05c->link_freq)
+		ov05c->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	/* Initialize Pixel rate control item */
+	pixel_rate_max = link_freq_to_pixel_rate(ov05c_link_freq_menu_items[0]);
+	ov05c->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
+					      V4L2_CID_PIXEL_RATE,
+					      0, pixel_rate_max,
+					      1, pixel_rate_max);
+
+	/* Initialize VBLANK control item */
+	mode = ov05c->cur_mode;
+	vblank_def = mode->vts_def - mode->height;
+	vblank_min = mode->vts_min - mode->height;
+	ov05c->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c_ctrl_ops,
+					  V4L2_CID_VBLANK,
+					  vblank_min, OV05C_VTS_MAX - mode->height,
+					  1, vblank_def);
+
+	/* Initialize HBLANK control item */
+	hblank = max(0, ov05c->cur_mode->hts - ov05c->cur_mode->width);
+	ov05c->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL,
+					  V4L2_CID_HBLANK,
+					  hblank, hblank, 1, hblank);
+	if (ov05c->hblank)
+		ov05c->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	/* Initialize EXPOSURE control item */
+	exposure_max = mode->vts_def - OV05C_EXPOSURE_MAX_MARGIN;
+	ov05c->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov05c_ctrl_ops,
+					    V4L2_CID_EXPOSURE,
+					    OV05C_EXPOSURE_MIN, exposure_max,
+					    OV05C_EXPOSURE_STEP, exposure_max);
+
+	/* Initialize Analog gain control item */
+	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+			  OV05C_ANA_GAIN_MIN, OV05C_ANA_GAIN_MAX,
+			  OV05C_ANA_GAIN_STEP, OV05C_ANA_GAIN_DEFAULT);
+
+	/* Initialize Digital gain control item */
+	v4l2_ctrl_new_std(ctrl_hdlr, &ov05c_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+			  OV05C_DGTL_GAIN_MIN, OV05C_DGTL_GAIN_MAX,
+			  OV05C_DGTL_GAIN_STEP, OV05C_DGTL_GAIN_DEFAULT);
+
+
+	/* Initialize Test pattern control item */
+	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov05c_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(ov05c_test_pattern_menu) - 1, 0,
+				     0, ov05c_test_pattern_menu);
+
+	if (ctrl_hdlr->error) {
+		ret = ctrl_hdlr->error;
+		dev_err(&client->dev, "%s V4L2 control init failed (%d)", __func__, ret);
+		goto error;
+	}
+
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret)
+		goto error;
+
+	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov05c_ctrl_ops, &props);
+	if (ret)
+		goto error;
+
+	ov05c->sd.ctrl_handler = ctrl_hdlr;
+
+	return 0;
+
+error:
+	v4l2_ctrl_handler_free(ctrl_hdlr);
+	mutex_destroy(&ov05c->mutex);
+
+	return ret;
+}
+
+static void ov05c_free_controls(struct ov05c *ov05c)
+{
+	v4l2_ctrl_handler_free(ov05c->sd.ctrl_handler);
+	mutex_destroy(&ov05c->mutex);
+}
+
+static int ov05c_probe(struct i2c_client *client)
+{
+	struct ov05c *ov05c;
+	int i, ret;
+
+	ov05c = devm_kzalloc(&client->dev, sizeof(*ov05c), GFP_KERNEL);
+	if (!ov05c)
+		return -ENOMEM;
+
+	client->dev.init_name = DRV_NAME;
+
+	/* create sensor enable gpio control */
+	ov05c->enable_gpio = devm_gpiod_get(&client->dev, "sensor0_enable", GPIOD_OUT_LOW);
+	if (IS_ERR_OR_NULL(ov05c->enable_gpio))
+		return PTR_ERR(ov05c->enable_gpio);
+
+	/* Initialize subdev */
+	v4l2_i2c_subdev_init(&ov05c->sd, client, &ov05c_subdev_ops);
+
+	/* Initialize CCI */
+	ov05c->regmap = devm_cci_regmap_init_i2c(client, 8);
+	if (IS_ERR(ov05c->regmap)) {
+		dev_err(&client->dev, "Failed to initialize CCI\n");
+		return PTR_ERR(ov05c->regmap);
+	}
+
+	/* Set default mode to max resolution */
+	ov05c->cur_mode = &supported_modes[0];
+
+	/* Initialize V4L2 controls */
+	ret = ov05c_init_controls(ov05c);
+	if (ret)
+		return ret;
+
+	/* Initialize V4L2 subdev */
+	ov05c->sd.internal_ops = &ov05c_internal_ops;
+	ov05c->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	ov05c->sd.entity.ops = &ov05c_subdev_entity_ops;
+	ov05c->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+	ov05c->sd.entity.name = "OV05C";
+
+	/* Initialize source pad */
+	for (i = 0; i < NUM_OF_PADS; i++)
+		ov05c->pads[i].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&ov05c->sd.entity, NUM_OF_PADS, ov05c->pads);
+	if (ret)
+		goto error_handler_free;
+
+	ret = v4l2_async_register_subdev_sensor(&ov05c->sd);
+	if (ret)
+		goto error_media_entity;
+
+	/*
+	 * Device is already turned on by i2c-core with ACPI domain PM.
+	 * Enable runtime PM and turn off the device.
+	 */
+	pm_runtime_set_active(&client->dev);
+	pm_runtime_enable(&client->dev);
+	pm_runtime_idle(&client->dev);
+
+	dev_info(&client->dev, "%s success", __func__);
+
+	return 0;
+
+error_media_entity:
+	media_entity_cleanup(&ov05c->sd.entity);
+
+error_handler_free:
+	ov05c_free_controls(ov05c);
+
+	return ret;
+}
+
+static void ov05c_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ov05c *ov05c = to_ov05c(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	ov05c_free_controls(ov05c);
+
+	pm_runtime_disable(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+static const struct i2c_device_id ov05c_id[] = {
+	{"ov05c", 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, ov05c_id);
+
+static struct i2c_driver ov05c_i2c_driver = {
+	.driver = {
+		.name = DRV_NAME,
+	},
+	.id_table = ov05c_id,
+	.probe = ov05c_probe,
+	.remove = ov05c_remove,
+};
+
+module_i2c_driver(ov05c_i2c_driver);
+
+MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@xxxxxxx>");
+MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@xxxxxxx>");
+MODULE_DESCRIPTION("OmniVision OV05C sensor driver");
+MODULE_ALIAS("ov05c");
+MODULE_LICENSE("GPL and additional rights");
-- 
2.43.0





[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux