All of lore.kernel.org
 help / color / mirror / Atom feed
From: Tomasz Figa <tfiga@chromium.org>
To: linux-media@vger.kernel.org
Cc: Mauro Carvalho Chehab <mchehab@kernel.org>,
	Rob Herring <robh+dt@kernel.org>,
	Sakari Ailus <sakari.ailus@linux.intel.com>,
	Hao He <hao.he@bitland.com.cn>,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	drinkcat@chromium.org, Xingyu Wu <wuxy@bitland.com.cn>,
	dongchun.zhu@mediatek.com, sj.huang@mediatek.com,
	darfur_liu@gcoreinc.com, hao.he7@gmail.com,
	Tomasz Figa <tfiga@chromium.org>
Subject: [PATCH v4 3/4] media: i2c: Add a driver for the Galaxycore GC5035 sensor
Date: Wed,  2 Sep 2020 22:48:12 +0000	[thread overview]
Message-ID: <20200902224813.14283-4-tfiga@chromium.org> (raw)
In-Reply-To: <20200902224813.14283-1-tfiga@chromium.org>

From: Hao He <hao.he@bitland.com.cn>

Galaxycore GC5035 is a 5 Mpix 10-bit Bayer sensor with a 2-lane MIPI
CSI-2 data and I2C control interfaces.

For the iniitial version support 2592x1944@30fps, 1296x972@30fps and
1280x720@60fps modes, assuming link frequency of 438 MHz and external
clock of 24 MHz.

Signed-off-by: Hao He <hao.he@bitland.com.cn>
Signed-off-by: Xingyu Wu <wuxy@bitland.com.cn>
[tfiga: Moved OTP handling to separate patch and fixed review comments.]
Signed-off-by: Tomasz Figa <tfiga@chromium.org>
---
 MAINTAINERS                |    7 +
 drivers/media/i2c/Kconfig  |   13 +
 drivers/media/i2c/Makefile |    1 +
 drivers/media/i2c/gc5035.c | 1494 ++++++++++++++++++++++++++++++++++++
 4 files changed, 1515 insertions(+)
 create mode 100644 drivers/media/i2c/gc5035.c

diff --git a/MAINTAINERS b/MAINTAINERS
index b7a09406aff0..4e09cfc7c924 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7249,6 +7249,13 @@ F:	kernel/futex.c
 F:	tools/perf/bench/futex*
 F:	tools/testing/selftests/futex/
 
+GALAXYCORE GC5035 SENSOR DRIVER
+M:	Tomasz Figa <tfiga@chromium.org>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+T:	git git://linuxtv.org/media_tree.git
+F:	drivers/media/i2c/gc5035.c
+
 GATEWORKS SYSTEM CONTROLLER (GSC) DRIVER
 M:	Tim Harvey <tharvey@gateworks.com>
 M:	Robert Jones <rjones@gateworks.com>
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 878f66ef2719..aff639aba3ac 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -725,6 +725,19 @@ config VIDEO_APTINA_PLL
 config VIDEO_SMIAPP_PLL
 	tristate
 
+config VIDEO_GC5035
+	tristate "Galaxycore GC5035 sensor support"
+	depends on I2C && VIDEO_V4L2
+	select MEDIA_CONTROLLER
+	select V4L2_FWNODE
+	select VIDEO_V4L2_SUBDEV_API
+	help
+	  This is a Video4Linux2 sensor driver for the Galaxycore
+	  GC5035 imaging sensor.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gc5035.
+
 config VIDEO_HI556
 	tristate "Hynix Hi-556 sensor support"
 	depends on I2C && VIDEO_V4L2
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index f0a77473979d..0645c50c4145 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -123,5 +123,6 @@ obj-$(CONFIG_VIDEO_MAX9286)	+= max9286.o
 rdacm20-camera_module-objs	:= rdacm20.o max9271.o
 obj-$(CONFIG_VIDEO_RDACM20)	+= rdacm20-camera_module.o
 obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
+obj-$(CONFIG_VIDEO_GC5035) += gc5035.o
 
 obj-$(CONFIG_SDR_MAX2175) += max2175.o
diff --git a/drivers/media/i2c/gc5035.c b/drivers/media/i2c/gc5035.c
new file mode 100644
index 000000000000..12e1b3a430b5
--- /dev/null
+++ b/drivers/media/i2c/gc5035.c
@@ -0,0 +1,1494 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2020 Bitland Inc.
+// Copyright 2020 Google LLC.
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <media/media-entity.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+/* External clock frequency supported by the driver */
+#define GC5035_MCLK_RATE				24000000UL
+/* Number of lanes supported by this driver */
+#define GC5035_DATA_LANES				2
+/* Bits per sample of sensor output */
+#define GC5035_BITS_PER_SAMPLE				10
+
+/* System registers (accessible regardless of the page. */
+
+/* Chip ID */
+#define GC5035_REG_CHIP_ID_H				0xf0
+#define GC5035_REG_CHIP_ID_L				0xf1
+#define GC5035_CHIP_ID						0x5035
+#define GC5035_ID(_msb, _lsb)				((_msb) << 8 | (_lsb))
+
+/* Register page selection register */
+#define GC5035_PAGE_REG					0xfe
+
+/* Page 0 registers */
+
+/* Exposure control */
+#define GC5035_REG_EXPOSURE_H				0x03
+#define GC5035_REG_EXPOSURE_L				0x04
+#define GC5035_EXPOSURE_H_MASK				0x3f
+#define GC5035_EXPOSURE_MIN				4
+#define GC5035_EXPOSURE_STEP				1
+
+/* Analog gain control */
+#define GC5035_REG_ANALOG_GAIN				0xb6
+#define GC5035_ANALOG_GAIN_MIN				0
+#define GC5035_ANALOG_GAIN_MAX				31
+#define GC5035_ANALOG_GAIN_STEP				1
+#define GC5035_ANALOG_GAIN_DEFAULT			GC5035_ANALOG_GAIN_MIN
+
+/* Digital gain control */
+#define GC5035_REG_DIGI_GAIN_H				0xb1
+#define GC5035_REG_DIGI_GAIN_L				0xb2
+#define GC5035_DGAIN_H_MASK				0x0f
+#define GC5035_DGAIN_L_MASK				0xfc
+#define GC5035_DGAIN_L_SHIFT				2
+#define GC5035_DIGI_GAIN_MIN				0
+#define GC5035_DIGI_GAIN_MAX				1023
+#define GC5035_DIGI_GAIN_STEP				1
+#define GC5035_DIGI_GAIN_DEFAULT			GC5035_DIGI_GAIN_MAX
+
+/* Vblank control */
+#define GC5035_REG_VTS_H				0x41
+#define GC5035_REG_VTS_L				0x42
+#define GC5035_VTS_H_MASK				0x3f
+#define GC5035_VTS_MAX					16383
+#define GC5035_EXPOSURE_MARGIN				16
+
+#define GC5035_REG_CTRL_MODE				0x3e
+#define GC5035_MODE_SW_STANDBY				0x01
+#define GC5035_MODE_STREAMING				0x91
+
+/* Page 1 registers */
+
+/* Test pattern control */
+#define GC5035_REG_TEST_PATTERN				0x8c
+#define GC5035_TEST_PATTERN_ENABLE			0x11
+#define GC5035_TEST_PATTERN_DISABLE			0x10
+
+static const char * const gc5035_supplies[] = {
+	/*
+	 * Requested separately due to power sequencing needs:
+	 * "iovdd",	 * Power supply for I/O circuits *
+	 */
+	"dvdd12",	/* Digital core power */
+	"avdd21",	/* Analog power */
+};
+
+struct gc5035_regval {
+	u8 addr;
+	u8 val;
+};
+
+struct gc5035_mode {
+	u32 width;
+	u32 height;
+	u32 max_fps;
+	u32 hts_def;
+	u32 vts_def;
+	u32 exp_def;
+	const struct gc5035_regval *reg_list;
+	size_t num_regs;
+};
+
+struct gc5035 {
+	struct i2c_client *client;
+	struct clk *mclk;
+	unsigned long mclk_rate;
+	struct gpio_desc *resetb_gpio;
+	struct gpio_desc *pwdn_gpio;
+	struct regulator *iovdd_supply;
+	struct regulator_bulk_data supplies[ARRAY_SIZE(gc5035_supplies)];
+
+	struct v4l2_subdev subdev;
+	struct media_pad pad;
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct v4l2_ctrl *exposure;
+	struct v4l2_ctrl *hblank;
+	struct v4l2_ctrl *vblank;
+
+	/*
+	 * Serialize control access, get/set format, get selection
+	 * and start streaming.
+	 */
+	struct mutex mutex;
+	bool streaming;
+	const struct gc5035_mode *cur_mode;
+};
+
+static inline struct gc5035 *to_gc5035(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct gc5035, subdev);
+}
+
+/*
+ * Xclk 24Mhz
+ * Pclk 87.6Mhz
+ * grabwindow_width 2592
+ * grabwindow_height 1944
+ * max_framerate 30fps
+ * mipi_datarate per lane 876Mbps
+ */
+static const struct gc5035_regval gc5035_global_regs[] = {
+	/*init*/
+	{0xfc, 0x01},
+	{0xf4, 0x40},
+	{0xf5, 0xe9},
+	{0xf6, 0x14},
+	{0xf8, 0x49},
+	{0xf9, 0x82},
+	{0xfa, 0x00},
+	{0xfc, 0x81},
+	{0xfe, 0x00},
+	{0x36, 0x01},
+	{0xd3, 0x87},
+	{0x36, 0x00},
+	{0x33, 0x00},
+	{0xfe, 0x03},
+	{0x01, 0xe7},
+	{0xf7, 0x01},
+	{0xfc, 0x8f},
+	{0xfc, 0x8f},
+	{0xfc, 0x8e},
+	{0xfe, 0x00},
+	{0xee, 0x30},
+	{0x87, 0x18},
+	{0xfe, 0x01},
+	{0x8c, 0x90},
+	{0xfe, 0x00},
+
+	/* Analog & CISCTL */
+	{0xfe, 0x00},
+	{0x05, 0x02},
+	{0x06, 0xda},
+	{0x9d, 0x0c},
+	{0x09, 0x00},
+	{0x0a, 0x04},
+	{0x0b, 0x00},
+	{0x0c, 0x03},
+	{0x0d, 0x07},
+	{0x0e, 0xa8},
+	{0x0f, 0x0a},
+	{0x10, 0x30},
+	{0x11, 0x02},
+	{0x17, 0x80},
+	{0x19, 0x05},
+	{0xfe, 0x02},
+	{0x30, 0x03},
+	{0x31, 0x03},
+	{0xfe, 0x00},
+	{0xd9, 0xc0},
+	{0x1b, 0x20},
+	{0x21, 0x48},
+	{0x28, 0x22},
+	{0x29, 0x58},
+	{0x44, 0x20},
+	{0x4b, 0x10},
+	{0x4e, 0x1a},
+	{0x50, 0x11},
+	{0x52, 0x33},
+	{0x53, 0x44},
+	{0x55, 0x10},
+	{0x5b, 0x11},
+	{0xc5, 0x02},
+	{0x8c, 0x1a},
+	{0xfe, 0x02},
+	{0x33, 0x05},
+	{0x32, 0x38},
+	{0xfe, 0x00},
+	{0x91, 0x80},
+	{0x92, 0x28},
+	{0x93, 0x20},
+	{0x95, 0xa0},
+	{0x96, 0xe0},
+	{0xd5, 0xfc},
+	{0x97, 0x28},
+	{0x16, 0x0c},
+	{0x1a, 0x1a},
+	{0x1f, 0x11},
+	{0x20, 0x10},
+	{0x46, 0x83},
+	{0x4a, 0x04},
+	{0x54, 0x02},
+	{0x62, 0x00},
+	{0x72, 0x8f},
+	{0x73, 0x89},
+	{0x7a, 0x05},
+	{0x7d, 0xcc},
+	{0x90, 0x00},
+	{0xce, 0x18},
+	{0xd0, 0xb2},
+	{0xd2, 0x40},
+	{0xe6, 0xe0},
+	{0xfe, 0x02},
+	{0x12, 0x01},
+	{0x13, 0x01},
+	{0x14, 0x01},
+	{0x15, 0x02},
+	{0x22, 0x7c},
+	{0x91, 0x00},
+	{0x92, 0x00},
+	{0x93, 0x00},
+	{0x94, 0x00},
+	{0xfe, 0x00},
+	{0xfc, 0x88},
+	{0xfe, 0x10},
+	{0xfe, 0x00},
+	{0xfc, 0x8e},
+	{0xfe, 0x00},
+	{0xfe, 0x00},
+	{0xfe, 0x00},
+	{0xfc, 0x88},
+	{0xfe, 0x10},
+	{0xfe, 0x00},
+	{0xfc, 0x8e},
+
+	/* Gain */
+	{0xfe, 0x00},
+	{0xb0, 0x6e},
+	{0xb1, 0x01},
+	{0xb2, 0x00},
+	{0xb3, 0x00},
+	{0xb4, 0x00},
+	{0xb6, 0x00},
+
+	/* ISP */
+	{0xfe, 0x01},
+	{0x53, 0x00},
+	{0x89, 0x03},
+	{0x60, 0x40},
+
+	/* BLK */
+	{0xfe, 0x01},
+	{0x42, 0x21},
+	{0x49, 0x03},
+	{0x4a, 0xff},
+	{0x4b, 0xc0},
+	{0x55, 0x00},
+
+	/* Anti_blooming */
+	{0xfe, 0x01},
+	{0x41, 0x28},
+	{0x4c, 0x00},
+	{0x4d, 0x00},
+	{0x4e, 0x3c},
+	{0x44, 0x08},
+	{0x48, 0x02},
+
+	/* Crop */
+	{0xfe, 0x01},
+	{0x91, 0x00},
+	{0x92, 0x08},
+	{0x93, 0x00},
+	{0x94, 0x07},
+	{0x95, 0x07},
+	{0x96, 0x98},
+	{0x97, 0x0a},
+	{0x98, 0x20},
+	{0x99, 0x00},
+
+	/* MIPI */
+	{0xfe, 0x03},
+	{0x02, 0x57},
+	{0x03, 0xb7},
+	{0x15, 0x14},
+	{0x18, 0x0f},
+	{0x21, 0x22},
+	{0x22, 0x06},
+	{0x23, 0x48},
+	{0x24, 0x12},
+	{0x25, 0x28},
+	{0x26, 0x08},
+	{0x29, 0x06},
+	{0x2a, 0x58},
+	{0x2b, 0x08},
+	{0xfe, 0x01},
+	{0x8c, 0x10},
+
+	{0xfe, 0x00},
+	{0x3e, 0x01},
+};
+
+/*
+ * Xclk 24Mhz
+ * Pclk 87.6Mhz
+ * grabwindow_width 2592
+ * grabwindow_height 1944
+ * max_framerate 30fps
+ * mipi_datarate per lane 876Mbps
+ */
+static const struct gc5035_regval gc5035_2592x1944_regs[] = {
+	/* System */
+	{0xfe, 0x00},
+	{0x3e, 0x01},
+	{0xfc, 0x01},
+	{0xf4, 0x40},
+	{0xf5, 0xe9},
+	{0xf6, 0x14},
+	{0xf8, 0x49},
+	{0xf9, 0x82},
+	{0xfa, 0x00},
+	{0xfc, 0x81},
+	{0xfe, 0x00},
+	{0x36, 0x01},
+	{0xd3, 0x87},
+	{0x36, 0x00},
+	{0x33, 0x00},
+	{0xfe, 0x03},
+	{0x01, 0xe7},
+	{0xf7, 0x01},
+	{0xfc, 0x8f},
+	{0xfc, 0x8f},
+	{0xfc, 0x8e},
+	{0xfe, 0x00},
+	{0xee, 0x30},
+	{0x87, 0x18},
+	{0xfe, 0x01},
+	{0x8c, 0x90},
+	{0xfe, 0x00},
+
+	/* Analog & CISCTL */
+	{0xfe, 0x00},
+	{0x05, 0x02},
+	{0x06, 0xda},
+	{0x9d, 0x0c},
+	{0x09, 0x00},
+	{0x0a, 0x04},
+	{0x0b, 0x00},
+	{0x0c, 0x03},
+	{0x0d, 0x07},
+	{0x0e, 0xa8},
+	{0x0f, 0x0a},
+	{0x10, 0x30},
+	{0x21, 0x48},
+	{0x29, 0x58},
+	{0x44, 0x20},
+	{0x4e, 0x1a},
+	{0x8c, 0x1a},
+	{0x91, 0x80},
+	{0x92, 0x28},
+	{0x93, 0x20},
+	{0x95, 0xa0},
+	{0x96, 0xe0},
+	{0xd5, 0xfc},
+	{0x97, 0x28},
+	{0x1f, 0x11},
+	{0xce, 0x18},
+	{0xd0, 0xb2},
+	{0xfe, 0x02},
+	{0x14, 0x01},
+	{0x15, 0x02},
+	{0xfe, 0x00},
+	{0xfc, 0x88},
+	{0xfe, 0x10},
+	{0xfe, 0x00},
+	{0xfc, 0x8e},
+	{0xfe, 0x00},
+	{0xfe, 0x00},
+	{0xfe, 0x00},
+	{0xfc, 0x88},
+	{0xfe, 0x10},
+	{0xfe, 0x00},
+	{0xfc, 0x8e},
+
+	/* BLK */
+	{0xfe, 0x01},
+	{0x49, 0x03},
+	{0x4a, 0xff},
+	{0x4b, 0xc0},
+
+	/* Anti_blooming */
+	{0xfe, 0x01},
+	{0x4e, 0x3c},
+	{0x44, 0x08},
+
+	/* Crop */
+	{0xfe, 0x01},
+	{0x91, 0x00},
+	{0x92, 0x08},
+	{0x93, 0x00},
+	{0x94, 0x07},
+	{0x95, 0x07},
+	{0x96, 0x98},
+	{0x97, 0x0a},
+	{0x98, 0x20},
+	{0x99, 0x00},
+
+	/* MIPI */
+	{0xfe, 0x03},
+	{0x02, 0x57},
+	{0x22, 0x06},
+	{0x26, 0x08},
+	{0x29, 0x06},
+	{0x2b, 0x08},
+	{0xfe, 0x01},
+	{0x8c, 0x10},
+
+	{0xfe, 0x00},
+	{0x3e, 0x91},
+};
+
+/*
+ * Xclk 24Mhz
+ * Pclk 87.6Mhz
+ * grabwindow_width 1296
+ * grabwindow_height 972
+ * mipi_datarate per lane 876Mbps
+ */
+static const struct gc5035_regval gc5035_1296x972_regs[] = {
+	/*NULL*/
+	{0xfe, 0x00},
+	{0x3e, 0x01},
+	{0xfc, 0x01},
+	{0xf4, 0x40},
+	{0xf5, 0xe4},
+	{0xf6, 0x14},
+	{0xf8, 0x49},
+	{0xf9, 0x12},
+	{0xfa, 0x01},
+	{0xfc, 0x81},
+	{0xfe, 0x00},
+	{0x36, 0x01},
+	{0xd3, 0x87},
+	{0x36, 0x00},
+	{0x33, 0x20},
+	{0xfe, 0x03},
+	{0x01, 0x87},
+	{0xf7, 0x11},
+	{0xfc, 0x8f},
+	{0xfc, 0x8f},
+	{0xfc, 0x8e},
+	{0xfe, 0x00},
+	{0xee, 0x30},
+	{0x87, 0x18},
+	{0xfe, 0x01},
+	{0x8c, 0x90},
+	{0xfe, 0x00},
+
+	/* Analog & CISCTL */
+	{0xfe, 0x00},
+	{0x05, 0x02},
+	{0x06, 0xda},
+	{0x9d, 0x0c},
+	{0x09, 0x00},
+	{0x0a, 0x04},
+	{0x0b, 0x00},
+	{0x0c, 0x03},
+	{0x0d, 0x07},
+	{0x0e, 0xa8},
+	{0x0f, 0x0a},
+	{0x10, 0x30},
+	{0x21, 0x60},
+	{0x29, 0x30},
+	{0x44, 0x18},
+	{0x4e, 0x20},
+	{0x8c, 0x20},
+	{0x91, 0x15},
+	{0x92, 0x3a},
+	{0x93, 0x20},
+	{0x95, 0x45},
+	{0x96, 0x35},
+	{0xd5, 0xf0},
+	{0x97, 0x20},
+	{0x1f, 0x19},
+	{0xce, 0x18},
+	{0xd0, 0xb3},
+	{0xfe, 0x02},
+	{0x14, 0x02},
+	{0x15, 0x00},
+	{0xfe, 0x00},
+	{0xfc, 0x88},
+	{0xfe, 0x10},
+	{0xfe, 0x00},
+	{0xfc, 0x8e},
+	{0xfe, 0x00},
+	{0xfe, 0x00},
+	{0xfe, 0x00},
+	{0xfc, 0x88},
+	{0xfe, 0x10},
+	{0xfe, 0x00},
+	{0xfc, 0x8e},
+
+	/* BLK */
+	{0xfe, 0x01},
+	{0x49, 0x00},
+	{0x4a, 0x01},
+	{0x4b, 0xf8},
+
+	/* Anti_blooming */
+	{0xfe, 0x01},
+	{0x4e, 0x06},
+	{0x44, 0x02},
+
+	/* Crop */
+	{0xfe, 0x01},
+	{0x91, 0x00},
+	{0x92, 0x04},
+	{0x93, 0x00},
+	{0x94, 0x03},
+	{0x95, 0x03},
+	{0x96, 0xcc},
+	{0x97, 0x05},
+	{0x98, 0x10},
+	{0x99, 0x00},
+
+	/* MIPI */
+	{0xfe, 0x03},
+	{0x02, 0x58},
+	{0x22, 0x03},
+	{0x26, 0x06},
+	{0x29, 0x03},
+	{0x2b, 0x06},
+	{0xfe, 0x01},
+	{0x8c, 0x10},
+};
+
+/*
+ * Xclk 24Mhz
+ * Pclk 87.6Mhz
+ * linelength 672{0x2a0)
+ * framelength 2232{0x8b8)
+ * grabwindow_width 1280
+ * grabwindow_height 720
+ * max_framerate 30fps
+ * mipi_datarate per lane 876Mbps
+ */
+static const struct gc5035_regval gc5035_1280x720_regs[] = {
+	/* System */
+	{0xfe, 0x00},
+	{0x3e, 0x01},
+	{0xfc, 0x01},
+	{0xf4, 0x40},
+	{0xf5, 0xe4},
+	{0xf6, 0x14},
+	{0xf8, 0x49},
+	{0xf9, 0x12},
+	{0xfa, 0x01},
+	{0xfc, 0x81},
+	{0xfe, 0x00},
+	{0x36, 0x01},
+	{0xd3, 0x87},
+	{0x36, 0x00},
+	{0x33, 0x20},
+	{0xfe, 0x03},
+	{0x01, 0x87},
+	{0xf7, 0x11},
+	{0xfc, 0x8f},
+	{0xfc, 0x8f},
+	{0xfc, 0x8e},
+	{0xfe, 0x00},
+	{0xee, 0x30},
+	{0x87, 0x18},
+	{0xfe, 0x01},
+	{0x8c, 0x90},
+	{0xfe, 0x00},
+
+	/* Analog & CISCTL */
+	{0xfe, 0x00},
+	{0x05, 0x02},
+	{0x06, 0xda},
+	{0x9d, 0x0c},
+	{0x09, 0x00},
+	{0x0a, 0x04},
+	{0x0b, 0x00},
+	{0x0c, 0x03},
+	{0x0d, 0x07},
+	{0x0e, 0xa8},
+	{0x0f, 0x0a},
+	{0x10, 0x30},
+	{0x21, 0x60},
+	{0x29, 0x30},
+	{0x44, 0x18},
+	{0x4e, 0x20},
+	{0x8c, 0x20},
+	{0x91, 0x15},
+	{0x92, 0x3a},
+	{0x93, 0x20},
+	{0x95, 0x45},
+	{0x96, 0x35},
+	{0xd5, 0xf0},
+	{0x97, 0x20},
+	{0x1f, 0x19},
+	{0xce, 0x18},
+	{0xd0, 0xb3},
+	{0xfe, 0x02},
+	{0x14, 0x02},
+	{0x15, 0x00},
+	{0xfe, 0x00},
+	{0xfc, 0x88},
+	{0xfe, 0x10},
+	{0xfe, 0x00},
+	{0xfc, 0x8e},
+	{0xfe, 0x00},
+	{0xfe, 0x00},
+	{0xfe, 0x00},
+	{0xfc, 0x88},
+	{0xfe, 0x10},
+	{0xfe, 0x00},
+	{0xfc, 0x8e},
+
+	/* BLK */
+	{0xfe, 0x01},
+	{0x49, 0x00},
+	{0x4a, 0x01},
+	{0x4b, 0xf8},
+
+	/* Anti_blooming */
+	{0xfe, 0x01},
+	{0x4e, 0x06},
+	{0x44, 0x02},
+
+	/* Crop */
+	{0xfe, 0x01},
+	{0x91, 0x00},
+	{0x92, 0x0a},
+	{0x93, 0x00},
+	{0x94, 0x0b},
+	{0x95, 0x02},
+	{0x96, 0xd0},
+	{0x97, 0x05},
+	{0x98, 0x00},
+	{0x99, 0x00},
+
+	/* MIPI */
+	{0xfe, 0x03},
+	{0x02, 0x58},
+	{0x22, 0x03},
+	{0x26, 0x06},
+	{0x29, 0x03},
+	{0x2b, 0x06},
+	{0xfe, 0x01},
+	{0x8c, 0x10},
+	{0xfe, 0x00},
+	{0x3e, 0x91},
+};
+
+static const struct gc5035_mode gc5035_modes[] = {
+	{
+		.width = 2592,
+		.height = 1944,
+		.max_fps = 30,
+		.exp_def = 0x258,
+		.hts_def = 2920,
+		.vts_def = 2008,
+		.reg_list = gc5035_2592x1944_regs,
+		.num_regs = ARRAY_SIZE(gc5035_2592x1944_regs),
+	},
+	{
+		.width = 1296,
+		.height = 972,
+		.max_fps = 30,
+		.exp_def = 0x258,
+		.hts_def = 1460,
+		.vts_def = 2008,
+		.reg_list = gc5035_1296x972_regs,
+		.num_regs = ARRAY_SIZE(gc5035_1296x972_regs),
+	},
+	{
+		.width = 1280,
+		.height = 720,
+		.max_fps = 60,
+		.exp_def = 0x258,
+		.hts_def = 1896,
+		.vts_def = 1536,
+		.reg_list = gc5035_1280x720_regs,
+		.num_regs = ARRAY_SIZE(gc5035_1280x720_regs),
+	},
+};
+
+static const char * const gc5035_test_pattern_menu[] = {
+	"Disabled",
+	"Color Bar",
+};
+
+static const s64 gc5035_link_freqs[] = {
+	438000000,
+};
+
+static u64 gc5035_link_to_pixel_rate(u32 f_index)
+{
+	u64 pixel_rate = gc5035_link_freqs[f_index] * 2 * GC5035_DATA_LANES;
+
+	do_div(pixel_rate, GC5035_BITS_PER_SAMPLE);
+
+	return pixel_rate;
+}
+
+static int gc5035_write_reg(struct gc5035 *gc5035, u8 reg, u8 val)
+{
+	return i2c_smbus_write_byte_data(gc5035->client, reg, val);
+}
+
+static int gc5035_write_array(struct gc5035 *gc5035,
+			      const struct gc5035_regval *regs,
+			      size_t num_regs)
+{
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < num_regs; i++) {
+		ret = gc5035_write_reg(gc5035, regs[i].addr, regs[i].val);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int gc5035_read_reg(struct gc5035 *gc5035, u8 reg, u8 *val)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(gc5035->client, reg);
+	if (ret < 0)
+		return ret;
+
+	*val = (unsigned char)ret;
+
+	return 0;
+}
+
+static int gc5035_set_fmt(struct v4l2_subdev *sd,
+			  struct v4l2_subdev_pad_config *cfg,
+			  struct v4l2_subdev_format *fmt)
+{
+	struct gc5035 *gc5035 = to_gc5035(sd);
+	const struct gc5035_mode *mode;
+	s64 h_blank, vblank_def;
+
+	mode = v4l2_find_nearest_size(gc5035_modes,
+				      ARRAY_SIZE(gc5035_modes), width,
+				      height, fmt->format.width,
+				      fmt->format.height);
+
+	fmt->format.code = MEDIA_BUS_FMT_SRGGB10_1X10;
+	fmt->format.width = mode->width;
+	fmt->format.height = mode->height;
+	fmt->format.field = V4L2_FIELD_NONE;
+
+	mutex_lock(&gc5035->mutex);
+	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+		*v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format;
+	} else {
+		gc5035->cur_mode = mode;
+		h_blank = mode->hts_def - mode->width;
+		__v4l2_ctrl_modify_range(gc5035->hblank, h_blank,
+					 h_blank, 1, h_blank);
+		vblank_def = round_up(mode->vts_def, 4) - mode->height;
+		__v4l2_ctrl_modify_range(gc5035->vblank, vblank_def,
+					 GC5035_VTS_MAX - mode->height,
+					 1, vblank_def);
+	}
+	mutex_unlock(&gc5035->mutex);
+
+	return 0;
+}
+
+static int gc5035_get_fmt(struct v4l2_subdev *sd,
+			  struct v4l2_subdev_pad_config *cfg,
+			  struct v4l2_subdev_format *fmt)
+{
+	struct gc5035 *gc5035 = to_gc5035(sd);
+	const struct gc5035_mode *mode = gc5035->cur_mode;
+
+	mutex_lock(&gc5035->mutex);
+	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+		fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
+	} else {
+		fmt->format.width = mode->width;
+		fmt->format.height = mode->height;
+		fmt->format.code = MEDIA_BUS_FMT_SRGGB10_1X10;
+		fmt->format.field = V4L2_FIELD_NONE;
+	}
+	mutex_unlock(&gc5035->mutex);
+
+	return 0;
+}
+
+static int gc5035_enum_mbus_code(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_pad_config *cfg,
+				 struct v4l2_subdev_mbus_code_enum *code)
+{
+	if (code->index != 0)
+		return -EINVAL;
+
+	code->code = MEDIA_BUS_FMT_SRGGB10_1X10;
+
+	return 0;
+}
+
+static int gc5035_enum_frame_sizes(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_pad_config *cfg,
+				   struct v4l2_subdev_frame_size_enum *fse)
+{
+	if (fse->index >= ARRAY_SIZE(gc5035_modes))
+		return -EINVAL;
+
+	if (fse->code != MEDIA_BUS_FMT_SRGGB10_1X10)
+		return -EINVAL;
+
+	fse->min_width  = gc5035_modes[fse->index].width;
+	fse->max_width  = gc5035_modes[fse->index].width;
+	fse->max_height = gc5035_modes[fse->index].height;
+	fse->min_height = gc5035_modes[fse->index].height;
+
+	return 0;
+}
+
+static int __gc5035_start_stream(struct gc5035 *gc5035)
+{
+	int ret;
+
+	ret = gc5035_write_array(gc5035, gc5035_global_regs,
+				 ARRAY_SIZE(gc5035_global_regs));
+	if (ret)
+		return ret;
+
+	ret = gc5035_write_array(gc5035, gc5035->cur_mode->reg_list,
+				 gc5035->cur_mode->num_regs);
+	if (ret)
+		return ret;
+
+	/* In case these controls are set before streaming */
+	ret = __v4l2_ctrl_handler_setup(&gc5035->ctrl_handler);
+	if (ret)
+		return ret;
+
+	gc5035_write_reg(gc5035, GC5035_PAGE_REG, 0);
+	if (ret)
+		return ret;
+
+	return gc5035_write_reg(gc5035, GC5035_REG_CTRL_MODE,
+				GC5035_MODE_STREAMING);
+}
+
+static int __gc5035_stop_stream(struct gc5035 *gc5035)
+{
+	int ret;
+
+	ret = gc5035_write_reg(gc5035, GC5035_PAGE_REG, 0);
+	if (ret)
+		return ret;
+
+	return gc5035_write_reg(gc5035, GC5035_REG_CTRL_MODE,
+				GC5035_MODE_SW_STANDBY);
+}
+
+static int gc5035_s_stream(struct v4l2_subdev *sd, int on)
+{
+	struct gc5035 *gc5035 = to_gc5035(sd);
+	struct i2c_client *client = gc5035->client;
+	int ret = 0;
+
+	mutex_lock(&gc5035->mutex);
+	on = !!on;
+	if (on == gc5035->streaming)
+		goto unlock_and_return;
+
+	if (on) {
+		ret = pm_runtime_get_sync(&client->dev);
+		if (ret < 0) {
+			pm_runtime_put_noidle(&client->dev);
+			goto unlock_and_return;
+		}
+
+		ret = __gc5035_start_stream(gc5035);
+		if (ret) {
+			dev_err(&client->dev, "start stream failed\n");
+			pm_runtime_put(&client->dev);
+			goto unlock_and_return;
+		}
+	} else {
+		__gc5035_stop_stream(gc5035);
+		pm_runtime_put(&client->dev);
+	}
+
+	gc5035->streaming = on;
+
+unlock_and_return:
+	mutex_unlock(&gc5035->mutex);
+
+	return ret;
+}
+
+static int gc5035_runtime_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct gc5035 *gc5035 = to_gc5035(sd);
+	unsigned long delay;
+	int ret;
+
+	/* IOVDD must be enabled first. */
+	ret = regulator_enable(gc5035->iovdd_supply);
+	if (ret) {
+		dev_err(dev, "Failed to enable iovdd regulator: %d\n", ret);
+		return ret;
+	}
+
+	/* Wait at least 50 us between IOVDD and AVDD/DVDD. */
+	usleep_range(50, 150);
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(gc5035_supplies),
+				    gc5035->supplies);
+	if (ret) {
+		dev_err(dev, "Failed to enable regulators: %d\n", ret);
+		goto disable_iovdd;
+	}
+
+	gpiod_set_value_cansleep(gc5035->resetb_gpio, 0);
+	gpiod_set_value_cansleep(gc5035->pwdn_gpio, 0);
+
+	ret = clk_prepare_enable(gc5035->mclk);
+	if (ret < 0) {
+		dev_err(dev, "Failed to enable mclk\n");
+		goto enable_pwdn;
+	}
+
+	/* min. 1200 MCLK cycles required before first I2C transaction. */
+	delay = DIV_ROUND_UP(1200UL * USEC_PER_SEC, gc5035->mclk_rate);
+	usleep_range(delay, delay + 200);
+
+	return 0;
+
+enable_pwdn:
+	gpiod_set_value_cansleep(gc5035->pwdn_gpio, 1);
+	gpiod_set_value_cansleep(gc5035->resetb_gpio, 1);
+	regulator_bulk_disable(ARRAY_SIZE(gc5035_supplies), gc5035->supplies);
+disable_iovdd:
+	regulator_disable(gc5035->iovdd_supply);
+
+	return ret;
+}
+
+static int gc5035_runtime_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct gc5035 *gc5035 = to_gc5035(sd);
+	unsigned long delay;
+
+	/* min. 2000 MCLK cycles required after streaming stops. */
+	delay = DIV_ROUND_UP(2000UL * USEC_PER_SEC, gc5035->mclk_rate);
+	usleep_range(delay, delay + 200);
+
+	clk_disable_unprepare(gc5035->mclk);
+
+	gpiod_set_value_cansleep(gc5035->pwdn_gpio, 1);
+	gpiod_set_value_cansleep(gc5035->resetb_gpio, 1);
+
+	regulator_bulk_disable(ARRAY_SIZE(gc5035_supplies), gc5035->supplies);
+	regulator_disable(gc5035->iovdd_supply);
+
+	return 0;
+}
+
+static int gc5035_entity_init_cfg(struct v4l2_subdev *subdev,
+				struct v4l2_subdev_pad_config *cfg)
+{
+	struct v4l2_subdev_format fmt = {
+		.which = V4L2_SUBDEV_FORMAT_TRY,
+		.format = {
+			.width = 2592,
+			.height = 1944,
+		}
+	};
+
+	gc5035_set_fmt(subdev, cfg, &fmt);
+
+	return 0;
+}
+
+static const struct dev_pm_ops gc5035_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				pm_runtime_force_resume)
+	SET_RUNTIME_PM_OPS(gc5035_runtime_suspend,
+			   gc5035_runtime_resume, NULL)
+};
+
+static const struct v4l2_subdev_video_ops gc5035_video_ops = {
+	.s_stream = gc5035_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops gc5035_pad_ops = {
+	.init_cfg = gc5035_entity_init_cfg,
+	.enum_mbus_code = gc5035_enum_mbus_code,
+	.enum_frame_size = gc5035_enum_frame_sizes,
+	.get_fmt = gc5035_get_fmt,
+	.set_fmt = gc5035_set_fmt,
+};
+
+static const struct v4l2_subdev_ops gc5035_subdev_ops = {
+	.video	= &gc5035_video_ops,
+	.pad	= &gc5035_pad_ops,
+};
+
+static const struct media_entity_operations gc5035_subdev_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+static int gc5035_set_exposure(struct gc5035 *gc5035, int val)
+{
+	int ret;
+
+	ret = gc5035_write_reg(gc5035, GC5035_PAGE_REG, 0);
+	if (ret)
+		return ret;
+
+	ret = gc5035_write_reg(gc5035, GC5035_REG_EXPOSURE_H,
+			       (val >> 8) & GC5035_EXPOSURE_H_MASK);
+	if (ret)
+		return ret;
+
+	return gc5035_write_reg(gc5035, GC5035_REG_EXPOSURE_L, val & 0xff);
+}
+
+static int gc5035_set_analogue_gain(struct gc5035 *gc5035, int val)
+{
+	int ret;
+
+	ret = gc5035_write_reg(gc5035, GC5035_PAGE_REG, 0);
+	if (ret)
+		return ret;
+
+	return gc5035_write_reg(gc5035, GC5035_REG_ANALOG_GAIN, val);
+}
+
+static int gc5035_set_digital_gain(struct gc5035 *gc5035, int val)
+{
+	int ret;
+
+	ret = gc5035_write_reg(gc5035, GC5035_PAGE_REG, 0);
+	if (ret)
+		return ret;
+
+	ret = gc5035_write_reg(gc5035, GC5035_REG_DIGI_GAIN_H,
+			       (val >> 8) & GC5035_DGAIN_H_MASK);
+	if (ret)
+		return ret;
+
+	return gc5035_write_reg(gc5035, GC5035_REG_DIGI_GAIN_L,
+				(val << GC5035_DGAIN_L_SHIFT)
+				& GC5035_DGAIN_L_MASK);
+}
+
+static int gc5035_set_vblank(struct gc5035 *gc5035, int val)
+{
+	int frame_length = 0;
+	int ret;
+
+	frame_length = val + gc5035->cur_mode->height;
+
+	ret = gc5035_write_reg(gc5035, GC5035_PAGE_REG, 0);
+	if (ret)
+		return ret;
+
+	ret = gc5035_write_reg(gc5035, GC5035_REG_VTS_H,
+			       (frame_length >> 8) & GC5035_VTS_H_MASK);
+	if (ret)
+		return ret;
+
+	return gc5035_write_reg(gc5035, GC5035_REG_VTS_L, frame_length & 0xff);
+}
+
+static int gc5035_enable_test_pattern(struct gc5035 *gc5035, int pattern)
+{
+	int ret;
+
+	if (pattern)
+		pattern = GC5035_TEST_PATTERN_ENABLE;
+	else
+		pattern = GC5035_TEST_PATTERN_DISABLE;
+
+	ret = gc5035_write_reg(gc5035, GC5035_PAGE_REG, 1);
+	if (ret)
+		return ret;
+
+	ret = gc5035_write_reg(gc5035, GC5035_REG_TEST_PATTERN, pattern);
+	if (ret)
+		return ret;
+
+	return gc5035_write_reg(gc5035, GC5035_PAGE_REG, 0);
+}
+
+static int gc5035_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gc5035 *gc5035 = container_of(ctrl->handler,
+					     struct gc5035, ctrl_handler);
+	struct i2c_client *client = gc5035->client;
+	s64 max;
+	int ret;
+
+	/* Propagate change of current control to all related controls */
+	switch (ctrl->id) {
+	case V4L2_CID_VBLANK:
+		/* Update max exposure while meeting expected vblanking */
+		max = gc5035->cur_mode->height + ctrl->val
+			- GC5035_EXPOSURE_MARGIN;
+		__v4l2_ctrl_modify_range(gc5035->exposure,
+					 gc5035->exposure->minimum, max,
+					 gc5035->exposure->step,
+					 gc5035->exposure->default_value);
+		break;
+	}
+
+	if (!pm_runtime_get_if_in_use(&client->dev))
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_EXPOSURE:
+		ret = gc5035_set_exposure(gc5035, ctrl->val);
+		break;
+	case V4L2_CID_ANALOGUE_GAIN:
+		ret = gc5035_set_analogue_gain(gc5035, ctrl->val);
+		break;
+	case V4L2_CID_DIGITAL_GAIN:
+		ret = gc5035_set_digital_gain(gc5035, ctrl->val);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = gc5035_set_vblank(gc5035, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		ret = gc5035_enable_test_pattern(gc5035, ctrl->val);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	};
+
+	pm_runtime_put(&client->dev);
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops gc5035_ctrl_ops = {
+	.s_ctrl = gc5035_set_ctrl,
+};
+
+static int gc5035_initialize_controls(struct gc5035 *gc5035)
+{
+	const struct gc5035_mode *mode;
+	struct v4l2_ctrl_handler *handler;
+	struct v4l2_ctrl *ctrl;
+	u64 exposure_max, pixel_rate;
+	u32 h_blank, vblank_def;
+	int ret;
+
+	handler = &gc5035->ctrl_handler;
+	mode = gc5035->cur_mode;
+	ret = v4l2_ctrl_handler_init(handler, 8);
+	if (ret)
+		return ret;
+
+	handler->lock = &gc5035->mutex;
+
+	ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ,
+				      0, 0, gc5035_link_freqs);
+	if (ctrl)
+		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	pixel_rate = gc5035_link_to_pixel_rate(0);
+	v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE,
+			  0, pixel_rate, 1, pixel_rate);
+
+	h_blank = mode->hts_def - mode->width;
+	gc5035->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK,
+					   h_blank, h_blank, 1, h_blank);
+	if (gc5035->hblank)
+		gc5035->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	vblank_def = round_up(mode->vts_def, 4) - mode->height;
+	gc5035->vblank = v4l2_ctrl_new_std(handler, &gc5035_ctrl_ops,
+					   V4L2_CID_VBLANK, vblank_def,
+					   GC5035_VTS_MAX - mode->height,
+					   4, vblank_def);
+
+	exposure_max = mode->vts_def - GC5035_EXPOSURE_MARGIN;
+	gc5035->exposure = v4l2_ctrl_new_std(handler, &gc5035_ctrl_ops,
+					     V4L2_CID_EXPOSURE,
+					     GC5035_EXPOSURE_MIN, exposure_max,
+					     GC5035_EXPOSURE_STEP,
+					     mode->exp_def);
+
+	v4l2_ctrl_new_std(handler, &gc5035_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+			  GC5035_ANALOG_GAIN_MIN, GC5035_ANALOG_GAIN_MAX,
+			  GC5035_ANALOG_GAIN_STEP, GC5035_ANALOG_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std(handler, &gc5035_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+			  GC5035_DIGI_GAIN_MIN, GC5035_DIGI_GAIN_MAX,
+			  GC5035_DIGI_GAIN_STEP, GC5035_DIGI_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std_menu_items(handler, &gc5035_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(gc5035_test_pattern_menu) - 1,
+				     0, 0, gc5035_test_pattern_menu);
+
+	if (handler->error) {
+		ret = handler->error;
+		dev_err(&gc5035->client->dev,
+			"Failed to init controls(%d)\n", ret);
+		goto err_free_handler;
+	}
+
+	gc5035->subdev.ctrl_handler = handler;
+
+	return 0;
+
+err_free_handler:
+	v4l2_ctrl_handler_free(handler);
+
+	return ret;
+}
+
+static int gc5035_check_sensor_id(struct gc5035 *gc5035,
+				  struct i2c_client *client)
+{
+	struct device *dev = &gc5035->client->dev;
+	u16 id;
+	u8 pid = 0;
+	u8 ver = 0;
+	int ret;
+
+	ret = gc5035_read_reg(gc5035, GC5035_REG_CHIP_ID_H, &pid);
+	if (ret)
+		return ret;
+
+	ret = gc5035_read_reg(gc5035, GC5035_REG_CHIP_ID_L, &ver);
+	if (ret)
+		return ret;
+
+	id = GC5035_ID(pid, ver);
+	if (id != GC5035_CHIP_ID) {
+		dev_err(dev, "Unexpected sensor id(%04x)\n", id);
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "Detected GC%04x sensor\n", id);
+
+	return 0;
+}
+
+static int gc5035_get_hwcfg(struct gc5035 *gc5035)
+{
+	struct device *dev = &gc5035->client->dev;
+	struct v4l2_fwnode_endpoint bus_cfg = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY
+	};
+	struct fwnode_handle *ep;
+	struct fwnode_handle *fwnode = dev_fwnode(dev);
+	unsigned long link_freq_mask = 0;
+	unsigned int i, j;
+	int ret;
+
+	if (!fwnode)
+		return -ENODEV;
+
+	ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
+	if (!ep)
+		return -ENODEV;
+
+	ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
+	if (ret)
+		goto out;
+
+	dev_dbg(dev, "num of link freqs: %d", bus_cfg.nr_of_link_frequencies);
+	if (!bus_cfg.nr_of_link_frequencies) {
+		dev_warn(dev, "no link frequencies defined");
+		goto out;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(gc5035_link_freqs); ++i) {
+		for (j = 0; j < bus_cfg.nr_of_link_frequencies; j++) {
+			if (bus_cfg.link_frequencies[j]
+			    == gc5035_link_freqs[i]) {
+				link_freq_mask |= BIT(i);
+				dev_dbg(dev, "Link frequency %lld supported\n",
+					gc5035_link_freqs[i]);
+				break;
+			}
+		}
+	}
+	if (!link_freq_mask) {
+		dev_err(dev, "No supported link frequencies found\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+out:
+	v4l2_fwnode_endpoint_free(&bus_cfg);
+	fwnode_handle_put(ep);
+	return ret;
+}
+
+static int gc5035_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct gc5035 *gc5035;
+	struct v4l2_subdev *sd;
+	int ret, i;
+	u32 freq;
+
+	gc5035 = devm_kzalloc(dev, sizeof(*gc5035), GFP_KERNEL);
+	if (!gc5035)
+		return -ENOMEM;
+
+	gc5035->client = client;
+	gc5035->cur_mode = &gc5035_modes[0];
+
+	gc5035->mclk = devm_clk_get(dev, "mclk");
+	if (IS_ERR(gc5035->mclk))
+		return dev_err_probe(dev, PTR_ERR(gc5035->mclk), "Failed to get mclk\n");
+
+	ret = device_property_read_u32(dev, "clock-frequency", &freq);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to get clock-frequency\n");
+	if (freq != GC5035_MCLK_RATE)
+		dev_warn(dev, "Given clock-frequency %u is different from expected %lu\n",
+			 freq, GC5035_MCLK_RATE);
+
+	ret = gc5035_get_hwcfg(gc5035);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to get hardware config\n");
+
+	ret = clk_set_rate(gc5035->mclk, freq);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to set mclk rate\n");
+	gc5035->mclk_rate = clk_get_rate(gc5035->mclk);
+	if (gc5035->mclk_rate != freq)
+		dev_warn(dev, "mclk rate set to %lu instead of requested %u\n",
+			 gc5035->mclk_rate, freq);
+
+	gc5035->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_OUT_HIGH);
+	if (IS_ERR(gc5035->pwdn_gpio))
+		return dev_err_probe(dev, PTR_ERR(gc5035->pwdn_gpio),
+				     "Failed to get pwdn-gpios\n");
+
+	gc5035->resetb_gpio = devm_gpiod_get(dev, "resetb", GPIOD_OUT_HIGH);
+	if (IS_ERR(gc5035->resetb_gpio))
+		return dev_err_probe(dev, PTR_ERR(gc5035->resetb_gpio),
+				     "Failed to get resetb-gpios\n");
+
+	gc5035->iovdd_supply = devm_regulator_get(dev, "iovdd");
+	if (IS_ERR(gc5035->iovdd_supply))
+		return dev_err_probe(dev, PTR_ERR(gc5035->iovdd_supply),
+				     "Failed to get iovdd regulator\n");
+
+	for (i = 0; i < ARRAY_SIZE(gc5035_supplies); i++)
+		gc5035->supplies[i].supply = gc5035_supplies[i];
+	ret = devm_regulator_bulk_get(&gc5035->client->dev,
+				       ARRAY_SIZE(gc5035_supplies),
+				       gc5035->supplies);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get regulators\n");
+
+	mutex_init(&gc5035->mutex);
+
+	sd = &gc5035->subdev;
+	v4l2_i2c_subdev_init(sd, client, &gc5035_subdev_ops);
+
+	ret = gc5035_initialize_controls(gc5035);
+	if (ret) {
+		dev_err_probe(dev, ret, "Failed to initialize controls\n");
+		goto err_destroy_mutex;
+	}
+
+	ret = gc5035_runtime_resume(dev);
+	if (ret) {
+		dev_err_probe(dev, ret, "Failed to power on\n");
+		goto err_free_handler;
+	}
+
+	ret = gc5035_check_sensor_id(gc5035, client);
+	if (ret) {
+		dev_err_probe(dev, ret, "Sensor ID check failed\n");
+		goto err_power_off;
+	}
+
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	sd->entity.ops = &gc5035_subdev_entity_ops;
+	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
+	gc5035->pad.flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_pads_init(&sd->entity, 1, &gc5035->pad);
+	if (ret < 0) {
+		dev_err_probe(dev, ret, "Failed to initialize pads\n");
+		goto err_power_off;
+	}
+
+	ret = v4l2_async_register_subdev_sensor_common(sd);
+	if (ret) {
+		dev_err_probe(dev, ret, "v4l2 async register subdev failed\n");
+		goto err_clean_entity;
+	}
+
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_idle(dev);
+
+	return 0;
+
+err_clean_entity:
+	media_entity_cleanup(&sd->entity);
+err_power_off:
+	gc5035_runtime_suspend(dev);
+err_free_handler:
+	v4l2_ctrl_handler_free(&gc5035->ctrl_handler);
+err_destroy_mutex:
+	mutex_destroy(&gc5035->mutex);
+
+	return ret;
+}
+
+static int gc5035_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct gc5035 *gc5035 = to_gc5035(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	v4l2_ctrl_handler_free(&gc5035->ctrl_handler);
+	mutex_destroy(&gc5035->mutex);
+	pm_runtime_disable(&client->dev);
+	if (!pm_runtime_status_suspended(&client->dev))
+		gc5035_runtime_suspend(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+
+	return 0;
+}
+
+static const struct of_device_id gc5035_of_match[] = {
+	{ .compatible = "galaxycore,gc5035" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, gc5035_of_match);
+
+static struct i2c_driver gc5035_i2c_driver = {
+	.driver = {
+		.name = "gc5035",
+		.pm = &gc5035_pm_ops,
+		.of_match_table = gc5035_of_match,
+	},
+	.probe_new	= &gc5035_probe,
+	.remove		= &gc5035_remove,
+};
+module_i2c_driver(gc5035_i2c_driver);
+
+MODULE_AUTHOR("Hao He <hao.he@bitland.com.cn>");
+MODULE_AUTHOR("Xingyu Wu <wuxy@bitland.com.cn>");
+MODULE_AUTHOR("Tomasz Figa <tfiga@chromium.org>");
+MODULE_DESCRIPTION("GalaxyCore gc5035 sensor driver");
+MODULE_LICENSE("GPL v2");
-- 
2.28.0.402.g5ffc5be6b7-goog


  parent reply	other threads:[~2020-09-02 22:48 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-09-02 22:48 [PATCH v4 0/4] Galaxycore GC5035 sensor driver Tomasz Figa
2020-09-02 22:48 ` [PATCH v4 1/4] dt-bindings: Add a vendor prefix for Galaxycore Inc Tomasz Figa
2020-09-14 20:26   ` Rob Herring
2020-09-02 22:48 ` [PATCH v4 2/4] media: dt-bindings: media: i2c: Add bindings for GC5035 Tomasz Figa
2020-09-02 23:30   ` Tomasz Figa
2020-09-03 16:34   ` Rob Herring
2020-09-02 22:48 ` Tomasz Figa [this message]
2020-09-02 22:48 ` [PATCH v4 4/4] media: i2c: gc5035: Add OTP configuration handling Tomasz Figa
2020-09-08  9:42   ` Sakari Ailus

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=20200902224813.14283-4-tfiga@chromium.org \
    --to=tfiga@chromium.org \
    --cc=darfur_liu@gcoreinc.com \
    --cc=devicetree@vger.kernel.org \
    --cc=dongchun.zhu@mediatek.com \
    --cc=drinkcat@chromium.org \
    --cc=hao.he7@gmail.com \
    --cc=hao.he@bitland.com.cn \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=mchehab@kernel.org \
    --cc=robh+dt@kernel.org \
    --cc=sakari.ailus@linux.intel.com \
    --cc=sj.huang@mediatek.com \
    --cc=wuxy@bitland.com.cn \
    /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.