Contributors: 8
Author Tokens Token Proportion Commits Commit Proportion
Benjamin Mugnier 9723 95.55% 6 40.00%
Julien Massot 349 3.43% 1 6.67%
Alain Volmat 74 0.73% 2 13.33%
Laurent Pinchart 20 0.20% 2 13.33%
Steve Longerbeam 4 0.04% 1 6.67%
Andy Shevchenko 3 0.03% 1 6.67%
Sakari Ailus 2 0.02% 1 6.67%
Uwe Kleine-König 1 0.01% 1 6.67%
Total 10176 15


// SPDX-License-Identifier: GPL-2.0
/*
 * Driver for VGXY61 global shutter sensor family driver
 *
 * Copyright (C) 2022 STMicroelectronics SA
 */

#include <linux/clk.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/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/units.h>

#include <asm/unaligned.h>

#include <media/mipi-csi2.h>
#include <media/v4l2-async.h>
#include <media/v4l2-cci.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-event.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-subdev.h>

#define VGXY61_REG_MODEL_ID				CCI_REG16_LE(0x0000)
#define VG5661_MODEL_ID					0x5661
#define VG5761_MODEL_ID					0x5761
#define VGXY61_REG_REVISION				CCI_REG16_LE(0x0002)
#define VGXY61_REG_FWPATCH_REVISION			CCI_REG16_LE(0x0014)
#define VGXY61_REG_FWPATCH_START_ADDR			CCI_REG8(0x2000)
#define VGXY61_REG_SYSTEM_FSM				CCI_REG8(0x0020)
#define VGXY61_SYSTEM_FSM_SW_STBY			0x03
#define VGXY61_SYSTEM_FSM_STREAMING			0x04
#define VGXY61_REG_NVM					CCI_REG8(0x0023)
#define VGXY61_NVM_OK					0x04
#define VGXY61_REG_STBY					CCI_REG8(0x0201)
#define VGXY61_STBY_NO_REQ				0
#define VGXY61_STBY_REQ_TMP_READ			BIT(2)
#define VGXY61_REG_STREAMING				CCI_REG8(0x0202)
#define VGXY61_STREAMING_NO_REQ				0
#define VGXY61_STREAMING_REQ_STOP			BIT(0)
#define VGXY61_STREAMING_REQ_START			BIT(1)
#define VGXY61_REG_EXT_CLOCK				CCI_REG32_LE(0x0220)
#define VGXY61_REG_CLK_PLL_PREDIV			CCI_REG8(0x0224)
#define VGXY61_REG_CLK_SYS_PLL_MULT			CCI_REG8(0x0225)
#define VGXY61_REG_GPIO_0_CTRL				CCI_REG8(0x0236)
#define VGXY61_REG_GPIO_1_CTRL				CCI_REG8(0x0237)
#define VGXY61_REG_GPIO_2_CTRL				CCI_REG8(0x0238)
#define VGXY61_REG_GPIO_3_CTRL				CCI_REG8(0x0239)
#define VGXY61_REG_SIGNALS_POLARITY_CTRL		CCI_REG8(0x023b)
#define VGXY61_REG_LINE_LENGTH				CCI_REG16_LE(0x0300)
#define VGXY61_REG_ORIENTATION				CCI_REG8(0x0302)
#define VGXY61_REG_VT_CTRL				CCI_REG8(0x0304)
#define VGXY61_REG_FORMAT_CTRL				CCI_REG8(0x0305)
#define VGXY61_REG_OIF_CTRL				CCI_REG16_LE(0x0306)
#define VGXY61_REG_OIF_ROI0_CTRL			CCI_REG8(0x030a)
#define VGXY61_REG_ROI0_START_H				CCI_REG16_LE(0x0400)
#define VGXY61_REG_ROI0_START_V				CCI_REG16_LE(0x0402)
#define VGXY61_REG_ROI0_END_H				CCI_REG16_LE(0x0404)
#define VGXY61_REG_ROI0_END_V				CCI_REG16_LE(0x0406)
#define VGXY61_REG_PATGEN_CTRL				CCI_REG32_LE(0x0440)
#define VGXY61_PATGEN_LONG_ENABLE			BIT(16)
#define VGXY61_PATGEN_SHORT_ENABLE			BIT(0)
#define VGXY61_PATGEN_LONG_TYPE_SHIFT			18
#define VGXY61_PATGEN_SHORT_TYPE_SHIFT			4
#define VGXY61_REG_FRAME_CONTENT_CTRL			CCI_REG8(0x0478)
#define VGXY61_REG_COARSE_EXPOSURE_LONG			CCI_REG16_LE(0x0500)
#define VGXY61_REG_COARSE_EXPOSURE_SHORT		CCI_REG16_LE(0x0504)
#define VGXY61_REG_ANALOG_GAIN				CCI_REG8(0x0508)
#define VGXY61_REG_DIGITAL_GAIN_LONG			CCI_REG16_LE(0x050a)
#define VGXY61_REG_DIGITAL_GAIN_SHORT			CCI_REG16_LE(0x0512)
#define VGXY61_REG_FRAME_LENGTH				CCI_REG16_LE(0x051a)
#define VGXY61_REG_SIGNALS_CTRL				CCI_REG16_LE(0x0522)
#define VGXY61_SIGNALS_GPIO_ID_SHIFT			4
#define VGXY61_REG_READOUT_CTRL				CCI_REG8(0x0530)
#define VGXY61_REG_HDR_CTRL				CCI_REG8(0x0532)
#define VGXY61_REG_PATGEN_LONG_DATA_GR			CCI_REG16_LE(0x092c)
#define VGXY61_REG_PATGEN_LONG_DATA_R			CCI_REG16_LE(0x092e)
#define VGXY61_REG_PATGEN_LONG_DATA_B			CCI_REG16_LE(0x0930)
#define VGXY61_REG_PATGEN_LONG_DATA_GB			CCI_REG16_LE(0x0932)
#define VGXY61_REG_PATGEN_SHORT_DATA_GR			CCI_REG16_LE(0x0950)
#define VGXY61_REG_PATGEN_SHORT_DATA_R			CCI_REG16_LE(0x0952)
#define VGXY61_REG_PATGEN_SHORT_DATA_B			CCI_REG16_LE(0x0954)
#define VGXY61_REG_PATGEN_SHORT_DATA_GB			CCI_REG16_LE(0x0956)
#define VGXY61_REG_BYPASS_CTRL				CCI_REG8(0x0a60)

#define VGX661_WIDTH					1464
#define VGX661_HEIGHT					1104
#define VGX761_WIDTH					1944
#define VGX761_HEIGHT					1204
#define VGX661_DEFAULT_MODE				1
#define VGX761_DEFAULT_MODE				1
#define VGX661_SHORT_ROT_TERM				93
#define VGX761_SHORT_ROT_TERM				90
#define VGXY61_EXPOS_ROT_TERM				66
#define VGXY61_WRITE_MULTIPLE_CHUNK_MAX			16
#define VGXY61_NB_GPIOS					4
#define VGXY61_NB_POLARITIES				5
#define VGXY61_FRAME_LENGTH_DEF				1313
#define VGXY61_MIN_FRAME_LENGTH				1288
#define VGXY61_MIN_EXPOSURE				10
#define VGXY61_HDR_LINEAR_RATIO				10
#define VGXY61_TIMEOUT_MS				500
#define VGXY61_MEDIA_BUS_FMT_DEF			MEDIA_BUS_FMT_Y8_1X8

#define VGXY61_FWPATCH_REVISION_MAJOR			2
#define VGXY61_FWPATCH_REVISION_MINOR			0
#define VGXY61_FWPATCH_REVISION_MICRO			5

static const u8 patch_array[] = {
	0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45,
	0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c,
	0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0,
	0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1,
	0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40,
	0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0,
	0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41,
	0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80,
	0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80,
	0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0,
	0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c,
	0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03,
	0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa,
	0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0,
	0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60,
	0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c,
	0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d,
	0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84,
	0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d,
	0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02,
	0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c,
	0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1,
	0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1,
	0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01,
	0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40,
	0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0,
	0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1,
	0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40,
	0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80,
	0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80,
	0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02,
	0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00,
	0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1,
	0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1,
	0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef,
	0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53,
	0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8,
	0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55,
	0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef,
	0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c,
	0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64,
	0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0,
	0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63,
	0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c,
	0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0,
	0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c,
	0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44,
	0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04,
	0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40,
	0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54,
	0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89,
	0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00,
	0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85,
	0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83,
	0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83,
	0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42,
	0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54,
	0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54,
	0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02,
	0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0,
	0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82,
	0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70,
	0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90,
	0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00,
	0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0,
	0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00,
	0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52,
	0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00,
};

static const char * const vgxy61_test_pattern_menu[] = {
	"Disabled",
	"Solid",
	"Colorbar",
	"Gradbar",
	"Hgrey",
	"Vgrey",
	"Dgrey",
	"PN28",
};

static const char * const vgxy61_hdr_mode_menu[] = {
	"HDR linearize",
	"HDR substraction",
	"No HDR",
};

static const char * const vgxy61_supply_name[] = {
	"VCORE",
	"VDDIO",
	"VANA",
};

static const s64 link_freq[] = {
	/*
	 * MIPI output freq is 804Mhz / 2, as it uses both rising edge and
	 * falling edges to send data
	 */
	402000000ULL
};

enum vgxy61_bin_mode {
	VGXY61_BIN_MODE_NORMAL,
	VGXY61_BIN_MODE_DIGITAL_X2,
	VGXY61_BIN_MODE_DIGITAL_X4,
};

enum vgxy61_hdr_mode {
	VGXY61_HDR_LINEAR,
	VGXY61_HDR_SUB,
	VGXY61_NO_HDR,
};

enum vgxy61_strobe_mode {
	VGXY61_STROBE_DISABLED,
	VGXY61_STROBE_LONG,
	VGXY61_STROBE_ENABLED,
};

struct vgxy61_mode_info {
	u32 width;
	u32 height;
	enum vgxy61_bin_mode bin_mode;
	struct v4l2_rect crop;
};

struct vgxy61_fmt_desc {
	u32 code;
	u8 bpp;
	u8 data_type;
};

static const struct vgxy61_fmt_desc vgxy61_supported_codes[] = {
	{
		.code = MEDIA_BUS_FMT_Y8_1X8,
		.bpp = 8,
		.data_type = MIPI_CSI2_DT_RAW8,
	},
	{
		.code = MEDIA_BUS_FMT_Y10_1X10,
		.bpp = 10,
		.data_type = MIPI_CSI2_DT_RAW10,
	},
	{
		.code = MEDIA_BUS_FMT_Y12_1X12,
		.bpp = 12,
		.data_type = MIPI_CSI2_DT_RAW12,
	},
	{
		.code = MEDIA_BUS_FMT_Y14_1X14,
		.bpp = 14,
		.data_type = MIPI_CSI2_DT_RAW14,
	},
	{
		.code = MEDIA_BUS_FMT_Y16_1X16,
		.bpp = 16,
		.data_type = MIPI_CSI2_DT_RAW16,
	},
};

static const struct vgxy61_mode_info vgx661_mode_data[] = {
	{
		.width = VGX661_WIDTH,
		.height = VGX661_HEIGHT,
		.bin_mode = VGXY61_BIN_MODE_NORMAL,
		.crop = {
			.left = 0,
			.top = 0,
			.width = VGX661_WIDTH,
			.height = VGX661_HEIGHT,
		},
	},
	{
		.width = 1280,
		.height = 720,
		.bin_mode = VGXY61_BIN_MODE_NORMAL,
		.crop = {
			.left = 92,
			.top = 192,
			.width = 1280,
			.height = 720,
		},
	},
	{
		.width = 640,
		.height = 480,
		.bin_mode = VGXY61_BIN_MODE_DIGITAL_X2,
		.crop = {
			.left = 92,
			.top = 72,
			.width = 1280,
			.height = 960,
		},
	},
	{
		.width = 320,
		.height = 240,
		.bin_mode = VGXY61_BIN_MODE_DIGITAL_X4,
		.crop = {
			.left = 92,
			.top = 72,
			.width = 1280,
			.height = 960,
		},
	},
};

static const struct vgxy61_mode_info vgx761_mode_data[] = {
	{
		.width = VGX761_WIDTH,
		.height = VGX761_HEIGHT,
		.bin_mode = VGXY61_BIN_MODE_NORMAL,
		.crop = {
			.left = 0,
			.top = 0,
			.width = VGX761_WIDTH,
			.height = VGX761_HEIGHT,
		},
	},
	{
		.width = 1920,
		.height = 1080,
		.bin_mode = VGXY61_BIN_MODE_NORMAL,
		.crop = {
			.left = 12,
			.top = 62,
			.width = 1920,
			.height = 1080,
		},
	},
	{
		.width = 1280,
		.height = 720,
		.bin_mode = VGXY61_BIN_MODE_NORMAL,
		.crop = {
			.left = 332,
			.top = 242,
			.width = 1280,
			.height = 720,
		},
	},
	{
		.width = 640,
		.height = 480,
		.bin_mode = VGXY61_BIN_MODE_DIGITAL_X2,
		.crop = {
			.left = 332,
			.top = 122,
			.width = 1280,
			.height = 960,
		},
	},
	{
		.width = 320,
		.height = 240,
		.bin_mode = VGXY61_BIN_MODE_DIGITAL_X4,
		.crop = {
			.left = 332,
			.top = 122,
			.width = 1280,
			.height = 960,
		},
	},
};

struct vgxy61_dev {
	struct i2c_client *i2c_client;
	struct regmap *regmap;
	struct v4l2_subdev sd;
	struct media_pad pad;
	struct regulator_bulk_data supplies[ARRAY_SIZE(vgxy61_supply_name)];
	struct gpio_desc *reset_gpio;
	struct clk *xclk;
	u32 clk_freq;
	u16 id;
	u16 sensor_width;
	u16 sensor_height;
	u16 oif_ctrl;
	unsigned int nb_of_lane;
	u32 data_rate_in_mbps;
	u32 pclk;
	u16 line_length;
	u16 rot_term;
	bool gpios_polarity;
	/* Lock to protect all members below */
	struct mutex lock;
	struct v4l2_ctrl_handler ctrl_handler;
	struct v4l2_ctrl *pixel_rate_ctrl;
	struct v4l2_ctrl *expo_ctrl;
	struct v4l2_ctrl *vblank_ctrl;
	struct v4l2_ctrl *vflip_ctrl;
	struct v4l2_ctrl *hflip_ctrl;
	bool streaming;
	struct v4l2_mbus_framefmt fmt;
	const struct vgxy61_mode_info *sensor_modes;
	unsigned int sensor_modes_nb;
	const struct vgxy61_mode_info *default_mode;
	const struct vgxy61_mode_info *current_mode;
	bool hflip;
	bool vflip;
	enum vgxy61_hdr_mode hdr;
	u16 expo_long;
	u16 expo_short;
	u16 expo_max;
	u16 expo_min;
	u16 vblank;
	u16 vblank_min;
	u16 frame_length;
	u16 digital_gain;
	u8 analog_gain;
	enum vgxy61_strobe_mode strobe_mode;
	u32 pattern;
};

static u8 get_bpp_by_code(__u32 code)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(vgxy61_supported_codes); i++) {
		if (vgxy61_supported_codes[i].code == code)
			return vgxy61_supported_codes[i].bpp;
	}
	/* Should never happen */
	WARN(1, "Unsupported code %d. default to 8 bpp", code);
	return 8;
}

static u8 get_data_type_by_code(__u32 code)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(vgxy61_supported_codes); i++) {
		if (vgxy61_supported_codes[i].code == code)
			return vgxy61_supported_codes[i].data_type;
	}
	/* Should never happen */
	WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type",
	     code);
	return MIPI_CSI2_DT_RAW8;
}

static void compute_pll_parameters_by_freq(u32 freq, u8 *prediv, u8 *mult)
{
	const unsigned int predivs[] = {1, 2, 4};
	unsigned int i;

	/*
	 * Freq range is [6Mhz-27Mhz] already checked.
	 * Output of divider should be in [6Mhz-12Mhz[.
	 */
	for (i = 0; i < ARRAY_SIZE(predivs); i++) {
		*prediv = predivs[i];
		if (freq / *prediv < 12 * HZ_PER_MHZ)
			break;
	}
	WARN_ON(i == ARRAY_SIZE(predivs));

	/*
	 * Target freq is 804Mhz. Don't change this as it will impact image
	 * quality.
	 */
	*mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq;
}

static s32 get_pixel_rate(struct vgxy61_dev *sensor)
{
	return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane,
			 get_bpp_by_code(sensor->fmt.code));
}

static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd)
{
	return container_of(sd, struct vgxy61_dev, sd);
}

static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
{
	return &container_of(ctrl->handler, struct vgxy61_dev,
			     ctrl_handler)->sd;
}

static unsigned int get_chunk_size(struct vgxy61_dev *sensor)
{
	struct i2c_adapter *adapter = sensor->i2c_client->adapter;
	int max_write_len = VGXY61_WRITE_MULTIPLE_CHUNK_MAX;

	if (adapter->quirks && adapter->quirks->max_write_len)
		max_write_len = adapter->quirks->max_write_len - 2;

	max_write_len = min(max_write_len, VGXY61_WRITE_MULTIPLE_CHUNK_MAX);

	return max(max_write_len, 1);
}

static int vgxy61_write_array(struct vgxy61_dev *sensor, u32 reg,
			      unsigned int nb, const u8 *array)
{
	const unsigned int chunk_size = get_chunk_size(sensor);
	int ret;
	unsigned int sz;

	while (nb) {
		sz = min(nb, chunk_size);
		ret = regmap_bulk_write(sensor->regmap, CCI_REG_ADDR(reg),
					array, sz);
		if (ret < 0)
			return ret;
		nb -= sz;
		reg += sz;
		array += sz;
	}

	return 0;
}

static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u32 reg, u8 poll_val,
			   unsigned int timeout_ms)
{
	const unsigned int loop_delay_ms = 10;
	u64 val;
	int ret;

	return read_poll_timeout(cci_read, ret,
				 ((ret < 0) || (val == poll_val)),
				 loop_delay_ms * 1000, timeout_ms * 1000,
				 false, sensor->regmap, reg, &val, NULL);
}

static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state,
			     unsigned int timeout_ms)
{
	return vgxy61_poll_reg(sensor, VGXY61_REG_SYSTEM_FSM, state,
			       timeout_ms);
}

static int vgxy61_check_bw(struct vgxy61_dev *sensor)
{
	/*
	 * Simplification of time needed to send short packets and for the MIPI
	 * to add transition times (EoT, LPS, and SoT packet delimiters) needed
	 * by the protocol to go in low power between 2 packets of data. This
	 * is a mipi IP constant for the sensor.
	 */
	const unsigned int mipi_margin = 1056;
	unsigned int binning_scale = sensor->current_mode->crop.height /
				     sensor->current_mode->height;
	u8 bpp = get_bpp_by_code(sensor->fmt.code);
	unsigned int max_bit_per_line;
	unsigned int bit_per_line;
	u64 line_rate;

	line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps *
		    sensor->line_length;
	max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin;
	bit_per_line = (bpp * sensor->current_mode->width) / binning_scale;

	return bit_per_line > max_bit_per_line ? -EINVAL : 0;
}

static int vgxy61_apply_exposure(struct vgxy61_dev *sensor)
{
	int ret = 0;

	 /* We first set expo to zero to avoid forbidden parameters couple */
	cci_write(sensor->regmap, VGXY61_REG_COARSE_EXPOSURE_SHORT, 0, &ret);
	cci_write(sensor->regmap, VGXY61_REG_COARSE_EXPOSURE_LONG,
		  sensor->expo_long, &ret);
	cci_write(sensor->regmap, VGXY61_REG_COARSE_EXPOSURE_SHORT,
		  sensor->expo_short, &ret);

	return ret;
}

static int vgxy61_get_regulators(struct vgxy61_dev *sensor)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(vgxy61_supply_name); i++)
		sensor->supplies[i].supply = vgxy61_supply_name[i];

	return devm_regulator_bulk_get(&sensor->i2c_client->dev,
				       ARRAY_SIZE(vgxy61_supply_name),
				       sensor->supplies);
}

static int vgxy61_apply_reset(struct vgxy61_dev *sensor)
{
	gpiod_set_value_cansleep(sensor->reset_gpio, 0);
	usleep_range(5000, 10000);
	gpiod_set_value_cansleep(sensor->reset_gpio, 1);
	usleep_range(5000, 10000);
	gpiod_set_value_cansleep(sensor->reset_gpio, 0);
	usleep_range(40000, 100000);
	return vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY,
				 VGXY61_TIMEOUT_MS);
}

static void vgxy61_fill_framefmt(struct vgxy61_dev *sensor,
				 const struct vgxy61_mode_info *mode,
				 struct v4l2_mbus_framefmt *fmt, u32 code)
{
	fmt->code = code;
	fmt->width = mode->width;
	fmt->height = mode->height;
	fmt->colorspace = V4L2_COLORSPACE_RAW;
	fmt->field = V4L2_FIELD_NONE;
	fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
	fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
	fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
}

static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd,
				   struct v4l2_mbus_framefmt *fmt,
				   const struct vgxy61_mode_info **new_mode)
{
	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
	const struct vgxy61_mode_info *mode;
	unsigned int index;

	for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) {
		if (vgxy61_supported_codes[index].code == fmt->code)
			break;
	}
	if (index == ARRAY_SIZE(vgxy61_supported_codes))
		index = 0;

	mode = v4l2_find_nearest_size(sensor->sensor_modes,
				      sensor->sensor_modes_nb, width, height,
				      fmt->width, fmt->height);
	if (new_mode)
		*new_mode = mode;

	vgxy61_fill_framefmt(sensor, mode, fmt,
			     vgxy61_supported_codes[index].code);

	return 0;
}

static int vgxy61_get_selection(struct v4l2_subdev *sd,
				struct v4l2_subdev_state *sd_state,
				struct v4l2_subdev_selection *sel)
{
	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);

	switch (sel->target) {
	case V4L2_SEL_TGT_CROP:
		sel->r = sensor->current_mode->crop;
		return 0;
	case V4L2_SEL_TGT_NATIVE_SIZE:
	case V4L2_SEL_TGT_CROP_DEFAULT:
	case V4L2_SEL_TGT_CROP_BOUNDS:
		sel->r.top = 0;
		sel->r.left = 0;
		sel->r.width = sensor->sensor_width;
		sel->r.height = sensor->sensor_height;
		return 0;
	}

	return -EINVAL;
}

static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd,
				 struct v4l2_subdev_state *sd_state,
				 struct v4l2_subdev_mbus_code_enum *code)
{
	if (code->index >= ARRAY_SIZE(vgxy61_supported_codes))
		return -EINVAL;

	code->code = vgxy61_supported_codes[code->index].code;

	return 0;
}

static int vgxy61_get_fmt(struct v4l2_subdev *sd,
			  struct v4l2_subdev_state *sd_state,
			  struct v4l2_subdev_format *format)
{
	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
	struct v4l2_mbus_framefmt *fmt;

	mutex_lock(&sensor->lock);

	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
		fmt = v4l2_subdev_state_get_format(sd_state, format->pad);
	else
		fmt = &sensor->fmt;

	format->format = *fmt;

	mutex_unlock(&sensor->lock);

	return 0;
}

static u16 vgxy61_get_vblank_min(struct vgxy61_dev *sensor,
				 enum vgxy61_hdr_mode hdr)
{
	u16 min_vblank =  VGXY61_MIN_FRAME_LENGTH -
			  sensor->current_mode->crop.height;
	/* Ensure the first rule of thumb can't be negative */
	u16 min_vblank_hdr =  VGXY61_MIN_EXPOSURE + sensor->rot_term + 1;

	if (hdr != VGXY61_NO_HDR)
		return max(min_vblank, min_vblank_hdr);
	return min_vblank;
}

static int vgxy61_enum_frame_size(struct v4l2_subdev *sd,
				  struct v4l2_subdev_state *sd_state,
				  struct v4l2_subdev_frame_size_enum *fse)
{
	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);

	if (fse->index >= sensor->sensor_modes_nb)
		return -EINVAL;

	fse->min_width = sensor->sensor_modes[fse->index].width;
	fse->max_width = fse->min_width;
	fse->min_height = sensor->sensor_modes[fse->index].height;
	fse->max_height = fse->min_height;

	return 0;
}

static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target)
{
	sensor->analog_gain = target;

	if (sensor->streaming)
		return cci_write(sensor->regmap, VGXY61_REG_ANALOG_GAIN, target,
				 NULL);
	return 0;
}

static int vgxy61_apply_digital_gain(struct vgxy61_dev *sensor,
				     u32 digital_gain)
{
	int ret = 0;

	/*
	 * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and
	 * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all
	 * four sub pixels.
	 */
	cci_write(sensor->regmap, VGXY61_REG_DIGITAL_GAIN_LONG, digital_gain,
		  &ret);
	cci_write(sensor->regmap, VGXY61_REG_DIGITAL_GAIN_SHORT, digital_gain,
		  &ret);

	return ret;
}

static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target)
{
	sensor->digital_gain = target;

	if (sensor->streaming)
		return vgxy61_apply_digital_gain(sensor, sensor->digital_gain);
	return 0;
}

static int vgxy61_apply_patgen(struct vgxy61_dev *sensor, u32 index)
{
	static const u8 index2val[] = {
		0x0, 0x1, 0x2, 0x3, 0x10, 0x11, 0x12, 0x13
	};
	u32 pattern = index2val[index];
	u32 reg = (pattern << VGXY61_PATGEN_LONG_TYPE_SHIFT) |
	      (pattern << VGXY61_PATGEN_SHORT_TYPE_SHIFT);

	if (pattern)
		reg |= VGXY61_PATGEN_LONG_ENABLE | VGXY61_PATGEN_SHORT_ENABLE;
	return cci_write(sensor->regmap, VGXY61_REG_PATGEN_CTRL, reg, NULL);
}

static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 pattern)
{
	sensor->pattern = pattern;

	if (sensor->streaming)
		return vgxy61_apply_patgen(sensor, sensor->pattern);
	return 0;
}

static int vgxy61_apply_gpiox_strobe_mode(struct vgxy61_dev *sensor,
					  enum vgxy61_strobe_mode mode,
					  unsigned int idx)
{
	static const u8 index2val[] = {0x0, 0x1, 0x3};
	u16 mask, val;

	mask = 0xf << (idx * VGXY61_SIGNALS_GPIO_ID_SHIFT);
	val = index2val[mode] << (idx * VGXY61_SIGNALS_GPIO_ID_SHIFT);

	return cci_update_bits(sensor->regmap, VGXY61_REG_SIGNALS_CTRL,
			       mask, val, NULL);
}

static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor,
					   enum vgxy61_hdr_mode hdr)
{
	unsigned int i;
	int ret;

	switch (hdr) {
	case VGXY61_HDR_LINEAR:
		sensor->strobe_mode = VGXY61_STROBE_ENABLED;
		break;
	case VGXY61_HDR_SUB:
	case VGXY61_NO_HDR:
		sensor->strobe_mode = VGXY61_STROBE_LONG;
		break;
	default:
		/* Should never happen */
		WARN_ON(true);
		break;
	}

	if (!sensor->streaming)
		return 0;

	for (i = 0; i < VGXY61_NB_GPIOS; i++) {
		ret = vgxy61_apply_gpiox_strobe_mode(sensor,
						     sensor->strobe_mode,
						     i);
		if (ret)
			return ret;
	}

	return 0;
}

static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor,
					       bool polarity)
{
	int ret = 0;

	if (sensor->streaming)
		return -EBUSY;

	cci_write(sensor->regmap, VGXY61_REG_GPIO_0_CTRL, polarity << 1, &ret);
	cci_write(sensor->regmap, VGXY61_REG_GPIO_1_CTRL, polarity << 1, &ret);
	cci_write(sensor->regmap, VGXY61_REG_GPIO_2_CTRL, polarity << 1, &ret);
	cci_write(sensor->regmap, VGXY61_REG_GPIO_3_CTRL, polarity << 1, &ret);
	cci_write(sensor->regmap, VGXY61_REG_SIGNALS_POLARITY_CTRL, polarity,
		  &ret);

	return ret;
}

static u32 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor,
				    unsigned int short_expo_ratio)
{
	u32 first_rot_max_expo, second_rot_max_expo, third_rot_max_expo;

	/* Apply sensor's rules of thumb */
	/*
	 * Short exposure + height must be less than frame length to avoid bad
	 * pixel line at the botom of the image
	 */
	first_rot_max_expo =
		((sensor->frame_length - sensor->current_mode->crop.height -
		sensor->rot_term) * short_expo_ratio) - 1;

	/*
	 * Total exposition time must be less than frame length to avoid sensor
	 * crash
	 */
	second_rot_max_expo =
		(((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) *
		short_expo_ratio) / (short_expo_ratio + 1)) - 1;

	/*
	 * Short exposure times 71 must be less than frame length to avoid
	 * sensor crash
	 */
	third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio;

	/* Take the minimum from all rules */
	return min(min(first_rot_max_expo, second_rot_max_expo),
		   third_rot_max_expo);
}

static int vgxy61_update_exposure(struct vgxy61_dev *sensor, u16 new_expo_long,
				  enum vgxy61_hdr_mode hdr)
{
	struct i2c_client *client = sensor->i2c_client;
	u16 new_expo_short = 0;
	u16 expo_short_max = 0;
	u16 expo_long_min = VGXY61_MIN_EXPOSURE;
	u16 expo_long_max = 0;

	/* Compute short exposure according to hdr mode and long exposure */
	switch (hdr) {
	case VGXY61_HDR_LINEAR:
		/*
		 * Take ratio into account for minimal exposures in
		 * VGXY61_HDR_LINEAR
		 */
		expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO;
		new_expo_long = max(expo_long_min, new_expo_long);

		expo_long_max =
			vgxy61_get_expo_long_max(sensor,
						 VGXY61_HDR_LINEAR_RATIO);
		expo_short_max = (expo_long_max +
				 (VGXY61_HDR_LINEAR_RATIO / 2)) /
				 VGXY61_HDR_LINEAR_RATIO;
		new_expo_short = (new_expo_long +
				 (VGXY61_HDR_LINEAR_RATIO / 2)) /
				 VGXY61_HDR_LINEAR_RATIO;
		break;
	case VGXY61_HDR_SUB:
		new_expo_long = max(expo_long_min, new_expo_long);

		expo_long_max = vgxy61_get_expo_long_max(sensor, 1);
		/* Short and long are the same in VGXY61_HDR_SUB */
		expo_short_max = expo_long_max;
		new_expo_short = new_expo_long;
		break;
	case VGXY61_NO_HDR:
		new_expo_long = max(expo_long_min, new_expo_long);

		/*
		 * As short expo is 0 here, only the second rule of thumb
		 * applies, see vgxy61_get_expo_long_max for more
		 */
		expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM;
		break;
	default:
		/* Should never happen */
		WARN_ON(true);
		break;
	}

	/* If this happens, something is wrong with formulas */
	WARN_ON(expo_long_min > expo_long_max);

	if (new_expo_long > expo_long_max) {
		dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n",
			 new_expo_long, expo_long_max);
		new_expo_long = expo_long_max;
		new_expo_short = expo_short_max;
	}

	sensor->expo_long = new_expo_long;
	sensor->expo_short = new_expo_short;
	sensor->expo_max = expo_long_max;
	sensor->expo_min = expo_long_min;

	if (sensor->streaming)
		return vgxy61_apply_exposure(sensor);
	return 0;
}

static int vgxy61_apply_framelength(struct vgxy61_dev *sensor)
{
	return cci_write(sensor->regmap, VGXY61_REG_FRAME_LENGTH,
			 sensor->frame_length, NULL);
}

static int vgxy61_update_vblank(struct vgxy61_dev *sensor, u16 vblank,
				enum vgxy61_hdr_mode hdr)
{
	int ret;

	sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr);
	sensor->vblank = max(sensor->vblank_min, vblank);
	sensor->frame_length = sensor->current_mode->crop.height +
			       sensor->vblank;

	/* Update exposure according to vblank */
	ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr);
	if (ret)
		return ret;

	if (sensor->streaming)
		return vgxy61_apply_framelength(sensor);
	return 0;
}

static int vgxy61_apply_hdr(struct vgxy61_dev *sensor,
			    enum vgxy61_hdr_mode index)
{
	static const u8 index2val[] = {0x1, 0x4, 0xa};

	return cci_write(sensor->regmap, VGXY61_REG_HDR_CTRL, index2val[index],
			 NULL);
}

static int vgxy61_update_hdr(struct vgxy61_dev *sensor,
			     enum vgxy61_hdr_mode index)
{
	int ret;

	/*
	 * vblank and short exposure change according to HDR mode, do it first
	 * as it can violate sensors 'rule of thumbs' and therefore will require
	 * to change the long exposure.
	 */
	ret = vgxy61_update_vblank(sensor, sensor->vblank, index);
	if (ret)
		return ret;

	/* Update strobe mode according to HDR */
	ret = vgxy61_update_gpios_strobe_mode(sensor, index);
	if (ret)
		return ret;

	sensor->hdr = index;

	if (sensor->streaming)
		return vgxy61_apply_hdr(sensor, sensor->hdr);
	return 0;
}

static int vgxy61_apply_settings(struct vgxy61_dev *sensor)
{
	int ret;
	unsigned int i;

	ret = vgxy61_apply_hdr(sensor, sensor->hdr);
	if (ret)
		return ret;

	ret = vgxy61_apply_framelength(sensor);
	if (ret)
		return ret;

	ret = vgxy61_apply_exposure(sensor);
	if (ret)
		return ret;

	ret = cci_write(sensor->regmap, VGXY61_REG_ANALOG_GAIN,
			sensor->analog_gain, NULL);
	if (ret)
		return ret;
	ret = vgxy61_apply_digital_gain(sensor, sensor->digital_gain);
	if (ret)
		return ret;

	ret = cci_write(sensor->regmap, VGXY61_REG_ORIENTATION,
			sensor->hflip | (sensor->vflip << 1), NULL);
	if (ret)
		return ret;

	ret = vgxy61_apply_patgen(sensor, sensor->pattern);
	if (ret)
		return ret;

	for (i = 0; i < VGXY61_NB_GPIOS; i++) {
		ret = vgxy61_apply_gpiox_strobe_mode(sensor,
						     sensor->strobe_mode, i);
		if (ret)
			return ret;
	}

	return 0;
}

static int vgxy61_stream_enable(struct vgxy61_dev *sensor)
{
	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
	const struct v4l2_rect *crop = &sensor->current_mode->crop;
	int ret = 0;

	ret = vgxy61_check_bw(sensor);
	if (ret)
		return ret;

	ret = pm_runtime_resume_and_get(&client->dev);
	if (ret)
		return ret;

	cci_write(sensor->regmap, VGXY61_REG_FORMAT_CTRL,
		  get_bpp_by_code(sensor->fmt.code), &ret);
	cci_write(sensor->regmap, VGXY61_REG_OIF_ROI0_CTRL,
		  get_data_type_by_code(sensor->fmt.code), &ret);

	cci_write(sensor->regmap, VGXY61_REG_READOUT_CTRL,
		  sensor->current_mode->bin_mode, &ret);
	cci_write(sensor->regmap, VGXY61_REG_ROI0_START_H, crop->left, &ret);
	cci_write(sensor->regmap, VGXY61_REG_ROI0_END_H,
		  crop->left + crop->width - 1, &ret);
	cci_write(sensor->regmap, VGXY61_REG_ROI0_START_V, crop->top, &ret);
	cci_write(sensor->regmap, VGXY61_REG_ROI0_END_V,
		  crop->top + crop->height - 1, &ret);
	if (ret)
		goto err_rpm_put;

	ret = vgxy61_apply_settings(sensor);
	if (ret)
		goto err_rpm_put;

	ret = cci_write(sensor->regmap, VGXY61_REG_STREAMING,
			VGXY61_STREAMING_REQ_START, NULL);
	if (ret)
		goto err_rpm_put;

	ret = vgxy61_poll_reg(sensor, VGXY61_REG_STREAMING,
			      VGXY61_STREAMING_NO_REQ, VGXY61_TIMEOUT_MS);
	if (ret)
		goto err_rpm_put;

	ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_STREAMING,
				VGXY61_TIMEOUT_MS);
	if (ret)
		goto err_rpm_put;

	/* vflip and hflip cannot change during streaming */
	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);

	return 0;

err_rpm_put:
	pm_runtime_put(&client->dev);
	return ret;
}

static int vgxy61_stream_disable(struct vgxy61_dev *sensor)
{
	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
	int ret;

	ret = cci_write(sensor->regmap, VGXY61_REG_STREAMING,
			VGXY61_STREAMING_REQ_STOP, NULL);
	if (ret)
		goto err_str_dis;

	ret = vgxy61_poll_reg(sensor, VGXY61_REG_STREAMING,
			      VGXY61_STREAMING_NO_REQ, 2000);
	if (ret)
		goto err_str_dis;

	ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY,
				VGXY61_TIMEOUT_MS);
	if (ret)
		goto err_str_dis;

	__v4l2_ctrl_grab(sensor->vflip_ctrl, false);
	__v4l2_ctrl_grab(sensor->hflip_ctrl, false);

err_str_dis:
	if (ret)
		WARN(1, "Can't disable stream");
	pm_runtime_put(&client->dev);

	return ret;
}

static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable)
{
	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
	int ret = 0;

	mutex_lock(&sensor->lock);

	ret = enable ? vgxy61_stream_enable(sensor) :
	      vgxy61_stream_disable(sensor);
	if (!ret)
		sensor->streaming = enable;

	mutex_unlock(&sensor->lock);

	return ret;
}

static int vgxy61_set_fmt(struct v4l2_subdev *sd,
			  struct v4l2_subdev_state *sd_state,
			  struct v4l2_subdev_format *format)
{
	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
	const struct vgxy61_mode_info *new_mode;
	struct v4l2_mbus_framefmt *fmt;
	int ret;

	mutex_lock(&sensor->lock);

	if (sensor->streaming) {
		ret = -EBUSY;
		goto out;
	}

	ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode);
	if (ret)
		goto out;

	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
		fmt = v4l2_subdev_state_get_format(sd_state, 0);
		*fmt = format->format;
	} else if (sensor->current_mode != new_mode ||
		   sensor->fmt.code != format->format.code) {
		fmt = &sensor->fmt;
		*fmt = format->format;

		sensor->current_mode = new_mode;

		/* Reset vblank and framelength to default */
		ret = vgxy61_update_vblank(sensor,
					   VGXY61_FRAME_LENGTH_DEF -
					   new_mode->crop.height,
					   sensor->hdr);

		/* Update controls to reflect new mode */
		__v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl,
					 get_pixel_rate(sensor));
		__v4l2_ctrl_modify_range(sensor->vblank_ctrl,
					 sensor->vblank_min,
					 0xffff - new_mode->crop.height,
					 1, sensor->vblank);
		__v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank);
		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
					 sensor->expo_max, 1,
					 sensor->expo_long);
	}

out:
	mutex_unlock(&sensor->lock);

	return ret;
}

static int vgxy61_init_state(struct v4l2_subdev *sd,
			     struct v4l2_subdev_state *sd_state)
{
	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
	struct v4l2_subdev_format fmt = { 0 };

	vgxy61_fill_framefmt(sensor, sensor->current_mode, &fmt.format,
			     VGXY61_MEDIA_BUS_FMT_DEF);

	return vgxy61_set_fmt(sd, sd_state, &fmt);
}

static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl)
{
	struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
	const struct vgxy61_mode_info *cur_mode = sensor->current_mode;
	int ret;

	switch (ctrl->id) {
	case V4L2_CID_EXPOSURE:
		ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr);
		ctrl->val = sensor->expo_long;
		break;
	case V4L2_CID_ANALOGUE_GAIN:
		ret = vgxy61_update_analog_gain(sensor, ctrl->val);
		break;
	case V4L2_CID_DIGITAL_GAIN:
		ret = vgxy61_update_digital_gain(sensor, ctrl->val);
		break;
	case V4L2_CID_VFLIP:
	case V4L2_CID_HFLIP:
		if (sensor->streaming) {
			ret = -EBUSY;
			break;
		}
		if (ctrl->id == V4L2_CID_VFLIP)
			sensor->vflip = ctrl->val;
		if (ctrl->id == V4L2_CID_HFLIP)
			sensor->hflip = ctrl->val;
		ret = 0;
		break;
	case V4L2_CID_TEST_PATTERN:
		ret = vgxy61_update_patgen(sensor, ctrl->val);
		break;
	case V4L2_CID_HDR_SENSOR_MODE:
		ret = vgxy61_update_hdr(sensor, ctrl->val);
		/* Update vblank and exposure controls to match new hdr */
		__v4l2_ctrl_modify_range(sensor->vblank_ctrl,
					 sensor->vblank_min,
					 0xffff - cur_mode->crop.height,
					 1, sensor->vblank);
		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
					 sensor->expo_max, 1,
					 sensor->expo_long);
		break;
	case V4L2_CID_VBLANK:
		ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr);
		/* Update exposure control to match new vblank */
		__v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min,
					 sensor->expo_max, 1,
					 sensor->expo_long);
		break;
	default:
		ret = -EINVAL;
		break;
	}

	return ret;
}

static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = {
	.s_ctrl = vgxy61_s_ctrl,
};

static int vgxy61_init_controls(struct vgxy61_dev *sensor)
{
	const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops;
	struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler;
	const struct vgxy61_mode_info *cur_mode = sensor->current_mode;
	struct v4l2_fwnode_device_properties props;
	struct v4l2_ctrl *ctrl;
	int ret;

	v4l2_ctrl_handler_init(hdl, 16);
	/* We can use our own mutex for the ctrl lock */
	hdl->lock = &sensor->lock;
	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1,
			  sensor->analog_gain);
	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1,
			  sensor->digital_gain);
	v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN,
				     ARRAY_SIZE(vgxy61_test_pattern_menu) - 1,
				     0, 0, vgxy61_test_pattern_menu);
	ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0,
				 sensor->line_length, 1,
				 sensor->line_length - cur_mode->width);
	if (ctrl)
		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
	ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
				      ARRAY_SIZE(link_freq) - 1, 0, link_freq);
	if (ctrl)
		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
	v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_HDR_SENSOR_MODE,
				     ARRAY_SIZE(vgxy61_hdr_mode_menu) - 1, 0,
				     VGXY61_NO_HDR, vgxy61_hdr_mode_menu);

	/*
	 * Keep a pointer to these controls as we need to update them when
	 * setting the format
	 */
	sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops,
						    V4L2_CID_PIXEL_RATE, 1,
						    INT_MAX, 1,
						    get_pixel_rate(sensor));
	if (sensor->pixel_rate_ctrl)
		sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
	sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE,
					      sensor->expo_min,
					      sensor->expo_max, 1,
					      sensor->expo_long);
	sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK,
						sensor->vblank_min,
						0xffff - cur_mode->crop.height,
						1, sensor->vblank);
	sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP,
					       0, 1, 1, sensor->vflip);
	sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP,
					       0, 1, 1, sensor->hflip);

	if (hdl->error) {
		ret = hdl->error;
		goto free_ctrls;
	}

	ret = v4l2_fwnode_device_parse(&sensor->i2c_client->dev, &props);
	if (ret)
		goto free_ctrls;

	ret = v4l2_ctrl_new_fwnode_properties(hdl, ops, &props);
	if (ret)
		goto free_ctrls;

	sensor->sd.ctrl_handler = hdl;
	return 0;

free_ctrls:
	v4l2_ctrl_handler_free(hdl);
	return ret;
}

static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
};

static const struct v4l2_subdev_video_ops vgxy61_video_ops = {
	.s_stream = vgxy61_s_stream,
};

static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = {
	.enum_mbus_code = vgxy61_enum_mbus_code,
	.get_fmt = vgxy61_get_fmt,
	.set_fmt = vgxy61_set_fmt,
	.get_selection = vgxy61_get_selection,
	.enum_frame_size = vgxy61_enum_frame_size,
};

static const struct v4l2_subdev_ops vgxy61_subdev_ops = {
	.core = &vgxy61_core_ops,
	.video = &vgxy61_video_ops,
	.pad = &vgxy61_pad_ops,
};

static const struct v4l2_subdev_internal_ops vgxy61_internal_ops = {
	.init_state = vgxy61_init_state,
};

static const struct media_entity_operations vgxy61_subdev_entity_ops = {
	.link_validate = v4l2_subdev_link_validate,
};

static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor,
			     struct fwnode_handle *handle)
{
	struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
	struct i2c_client *client = sensor->i2c_client;
	u32 log2phy[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
	u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0};
	int polarities[VGXY61_NB_POLARITIES] = {0, 0, 0, 0, 0};
	int l_nb;
	unsigned int p, l, i;
	int ret;

	ret = v4l2_fwnode_endpoint_alloc_parse(handle, &ep);
	if (ret)
		return -EINVAL;

	l_nb = ep.bus.mipi_csi2.num_data_lanes;
	if (l_nb != 1 && l_nb != 2 && l_nb != 4) {
		dev_err(&client->dev, "invalid data lane number %d\n", l_nb);
		goto error_ep;
	}

	/* Build log2phy, phy2log and polarities from ep info */
	log2phy[0] = ep.bus.mipi_csi2.clock_lane;
	phy2log[log2phy[0]] = 0;
	for (l = 1; l < l_nb + 1; l++) {
		log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1];
		phy2log[log2phy[l]] = l;
	}
	/*
	 * Then fill remaining slots for every physical slot to have something
	 * valid for hardware stuff.
	 */
	for (p = 0; p < VGXY61_NB_POLARITIES; p++) {
		if (phy2log[p] != ~0)
			continue;
		phy2log[p] = l;
		log2phy[l] = p;
		l++;
	}
	for (l = 0; l < l_nb + 1; l++)
		polarities[l] = ep.bus.mipi_csi2.lane_polarities[l];

	if (log2phy[0] != 0) {
		dev_err(&client->dev, "clk lane must be map to physical lane 0\n");
		goto error_ep;
	}
	sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) +
			   (polarities[3] << 12) + ((phy2log[3] - 1) << 10) +
			   (polarities[2] <<  9) + ((phy2log[2] - 1) <<  7) +
			   (polarities[1] <<  6) + ((phy2log[1] - 1) <<  4) +
			   (polarities[0] <<  3) +
			   l_nb;
	sensor->nb_of_lane = l_nb;

	dev_dbg(&client->dev, "tx uses %d lanes", l_nb);
	for (i = 0; i < VGXY61_NB_POLARITIES; i++) {
		dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]);
		dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]);
		dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]);
	}
	dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl);

	v4l2_fwnode_endpoint_free(&ep);

	return 0;

error_ep:
	v4l2_fwnode_endpoint_free(&ep);

	return -EINVAL;
}

static int vgxy61_configure(struct vgxy61_dev *sensor)
{
	u32 sensor_freq;
	u8 prediv, mult;
	u64 line_length;
	int ret = 0;

	compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult);
	sensor_freq = (mult * sensor->clk_freq) / prediv;
	/* Frequency to data rate is 1:1 ratio for MIPI */
	sensor->data_rate_in_mbps = sensor_freq;
	/* Video timing ISP path (pixel clock)  requires 804/5 mhz = 160 mhz */
	sensor->pclk = sensor_freq / 5;

	cci_read(sensor->regmap, VGXY61_REG_LINE_LENGTH, &line_length, &ret);
	if (ret < 0)
		return ret;
	sensor->line_length = (u16)line_length;
	cci_write(sensor->regmap, VGXY61_REG_EXT_CLOCK, sensor->clk_freq, &ret);
	cci_write(sensor->regmap, VGXY61_REG_CLK_PLL_PREDIV, prediv, &ret);
	cci_write(sensor->regmap, VGXY61_REG_CLK_SYS_PLL_MULT, mult, &ret);
	cci_write(sensor->regmap, VGXY61_REG_OIF_CTRL, sensor->oif_ctrl, &ret);
	cci_write(sensor->regmap, VGXY61_REG_FRAME_CONTENT_CTRL, 0, &ret);
	cci_write(sensor->regmap, VGXY61_REG_BYPASS_CTRL, 4, &ret);
	if (ret)
		return ret;
	vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity);
	/* Set pattern generator solid to middle value */
	cci_write(sensor->regmap, VGXY61_REG_PATGEN_LONG_DATA_GR, 0x800, &ret);
	cci_write(sensor->regmap, VGXY61_REG_PATGEN_LONG_DATA_R, 0x800, &ret);
	cci_write(sensor->regmap, VGXY61_REG_PATGEN_LONG_DATA_B, 0x800, &ret);
	cci_write(sensor->regmap, VGXY61_REG_PATGEN_LONG_DATA_GB, 0x800, &ret);
	cci_write(sensor->regmap, VGXY61_REG_PATGEN_SHORT_DATA_GR, 0x800, &ret);
	cci_write(sensor->regmap, VGXY61_REG_PATGEN_SHORT_DATA_R, 0x800, &ret);
	cci_write(sensor->regmap, VGXY61_REG_PATGEN_SHORT_DATA_B, 0x800, &ret);
	cci_write(sensor->regmap, VGXY61_REG_PATGEN_SHORT_DATA_GB, 0x800, &ret);
	if (ret)
		return ret;

	return 0;
}

static int vgxy61_patch(struct vgxy61_dev *sensor)
{
	struct i2c_client *client = sensor->i2c_client;
	u64 patch;
	int ret;

	ret = vgxy61_write_array(sensor, VGXY61_REG_FWPATCH_START_ADDR,
				 sizeof(patch_array), patch_array);
	cci_write(sensor->regmap, VGXY61_REG_STBY, 0x10, &ret);
	if (ret)
		return ret;

	ret = vgxy61_poll_reg(sensor, VGXY61_REG_STBY, 0, VGXY61_TIMEOUT_MS);
	cci_read(sensor->regmap, VGXY61_REG_FWPATCH_REVISION, &patch, &ret);
	if (ret < 0)
		return ret;

	if (patch != (VGXY61_FWPATCH_REVISION_MAJOR << 12) +
		     (VGXY61_FWPATCH_REVISION_MINOR << 8) +
		     VGXY61_FWPATCH_REVISION_MICRO) {
		dev_err(&client->dev,
			"bad patch version expected %d.%d.%d got %u.%u.%u\n",
			VGXY61_FWPATCH_REVISION_MAJOR,
			VGXY61_FWPATCH_REVISION_MINOR,
			VGXY61_FWPATCH_REVISION_MICRO,
			(u16)patch >> 12, ((u16)patch >> 8) & 0x0f, (u16)patch & 0xff);
		return -ENODEV;
	}
	dev_dbg(&client->dev, "patch %u.%u.%u applied\n",
		(u16)patch >> 12, ((u16)patch >> 8) & 0x0f, (u16)patch & 0xff);

	return 0;
}

static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor)
{
	struct i2c_client *client = sensor->i2c_client;
	u64 device_rev;
	int ret;

	ret = cci_read(sensor->regmap, VGXY61_REG_REVISION, &device_rev, NULL);
	if (ret < 0)
		return ret;

	switch (device_rev >> 8) {
	case 0xA:
		dev_dbg(&client->dev, "Cut1 detected\n");
		dev_err(&client->dev, "Cut1 not supported by this driver\n");
		return -ENODEV;
	case 0xB:
		dev_dbg(&client->dev, "Cut2 detected\n");
		return 0;
	case 0xC:
		dev_dbg(&client->dev, "Cut3 detected\n");
		return 0;
	default:
		dev_err(&client->dev, "Unable to detect cut version\n");
		return -ENODEV;
	}
}

static int vgxy61_detect(struct vgxy61_dev *sensor)
{
	struct i2c_client *client = sensor->i2c_client;
	u64 st, id = 0;
	int ret;

	ret = cci_read(sensor->regmap, VGXY61_REG_MODEL_ID, &id, NULL);
	if (ret < 0)
		return ret;
	if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) {
		dev_warn(&client->dev, "Unsupported sensor id %x\n", (u16)id);
		return -ENODEV;
	}
	dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", (u16)id);
	sensor->id = id;

	ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY,
				VGXY61_TIMEOUT_MS);
	if (ret)
		return ret;

	ret = cci_read(sensor->regmap, VGXY61_REG_NVM, &st, NULL);
	if (ret < 0)
		return st;
	if (st != VGXY61_NVM_OK)
		dev_warn(&client->dev, "Bad nvm state got %u\n", (u8)st);

	ret = vgxy61_detect_cut_version(sensor);
	if (ret)
		return ret;

	return 0;
}

/* Power/clock management functions */
static int vgxy61_power_on(struct device *dev)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
	int ret;

	ret = regulator_bulk_enable(ARRAY_SIZE(vgxy61_supply_name),
				    sensor->supplies);
	if (ret) {
		dev_err(&client->dev, "failed to enable regulators %d\n", ret);
		return ret;
	}

	ret = clk_prepare_enable(sensor->xclk);
	if (ret) {
		dev_err(&client->dev, "failed to enable clock %d\n", ret);
		goto disable_bulk;
	}

	if (sensor->reset_gpio) {
		ret = vgxy61_apply_reset(sensor);
		if (ret) {
			dev_err(&client->dev, "sensor reset failed %d\n", ret);
			goto disable_clock;
		}
	}

	ret = vgxy61_detect(sensor);
	if (ret) {
		dev_err(&client->dev, "sensor detect failed %d\n", ret);
		goto disable_clock;
	}

	ret = vgxy61_patch(sensor);
	if (ret) {
		dev_err(&client->dev, "sensor patch failed %d\n", ret);
		goto disable_clock;
	}

	ret = vgxy61_configure(sensor);
	if (ret) {
		dev_err(&client->dev, "sensor configuration failed %d\n", ret);
		goto disable_clock;
	}

	return 0;

disable_clock:
	clk_disable_unprepare(sensor->xclk);
disable_bulk:
	regulator_bulk_disable(ARRAY_SIZE(vgxy61_supply_name),
			       sensor->supplies);

	return ret;
}

static int vgxy61_power_off(struct device *dev)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);

	clk_disable_unprepare(sensor->xclk);
	regulator_bulk_disable(ARRAY_SIZE(vgxy61_supply_name),
			       sensor->supplies);
	return 0;
}

static void vgxy61_fill_sensor_param(struct vgxy61_dev *sensor)
{
	if (sensor->id == VG5761_MODEL_ID) {
		sensor->sensor_width = VGX761_WIDTH;
		sensor->sensor_height = VGX761_HEIGHT;
		sensor->sensor_modes = vgx761_mode_data;
		sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data);
		sensor->default_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE];
		sensor->rot_term = VGX761_SHORT_ROT_TERM;
	} else if (sensor->id == VG5661_MODEL_ID) {
		sensor->sensor_width = VGX661_WIDTH;
		sensor->sensor_height = VGX661_HEIGHT;
		sensor->sensor_modes = vgx661_mode_data;
		sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data);
		sensor->default_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE];
		sensor->rot_term = VGX661_SHORT_ROT_TERM;
	} else {
		/* Should never happen */
		WARN_ON(true);
	}
	sensor->current_mode = sensor->default_mode;
}

static int vgxy61_probe(struct i2c_client *client)
{
	struct device *dev = &client->dev;
	struct fwnode_handle *handle;
	struct vgxy61_dev *sensor;
	int ret;

	sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
	if (!sensor)
		return -ENOMEM;

	sensor->i2c_client = client;
	sensor->streaming = false;
	sensor->hdr = VGXY61_NO_HDR;
	sensor->expo_long = 200;
	sensor->expo_short = 0;
	sensor->hflip = false;
	sensor->vflip = false;
	sensor->analog_gain = 0;
	sensor->digital_gain = 256;

	sensor->regmap = devm_cci_regmap_init_i2c(client, 16);
	if (IS_ERR(sensor->regmap)) {
		ret = PTR_ERR(sensor->regmap);
		return dev_err_probe(dev, ret, "Failed to init regmap\n");
	}

	handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
	if (!handle) {
		dev_err(dev, "handle node not found\n");
		return -EINVAL;
	}

	ret = vgxy61_tx_from_ep(sensor, handle);
	fwnode_handle_put(handle);
	if (ret) {
		dev_err(dev, "Failed to parse handle %d\n", ret);
		return ret;
	}

	sensor->xclk = devm_clk_get(dev, NULL);
	if (IS_ERR(sensor->xclk)) {
		dev_err(dev, "failed to get xclk\n");
		return PTR_ERR(sensor->xclk);
	}
	sensor->clk_freq = clk_get_rate(sensor->xclk);
	if (sensor->clk_freq < 6 * HZ_PER_MHZ ||
	    sensor->clk_freq > 27 * HZ_PER_MHZ) {
		dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n",
			sensor->clk_freq / HZ_PER_MHZ);
		return -EINVAL;
	}
	sensor->gpios_polarity =
		device_property_read_bool(dev, "st,strobe-gpios-polarity");

	v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops);
	sensor->sd.internal_ops = &vgxy61_internal_ops;
	sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
			    V4L2_SUBDEV_FL_HAS_EVENTS;
	sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
	sensor->sd.entity.ops = &vgxy61_subdev_entity_ops;
	sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;

	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset",
						     GPIOD_OUT_HIGH);

	ret = vgxy61_get_regulators(sensor);
	if (ret) {
		dev_err(&client->dev, "failed to get regulators %d\n", ret);
		return ret;
	}

	ret = vgxy61_power_on(dev);
	if (ret)
		return ret;

	vgxy61_fill_sensor_param(sensor);
	vgxy61_fill_framefmt(sensor, sensor->current_mode, &sensor->fmt,
			     VGXY61_MEDIA_BUS_FMT_DEF);

	mutex_init(&sensor->lock);

	ret = vgxy61_update_hdr(sensor, sensor->hdr);
	if (ret)
		goto error_power_off;

	ret = vgxy61_init_controls(sensor);
	if (ret) {
		dev_err(&client->dev, "controls initialization failed %d\n",
			ret);
		goto error_power_off;
	}

	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
	if (ret) {
		dev_err(&client->dev, "pads init failed %d\n", ret);
		goto error_handler_free;
	}

	/* Enable runtime PM and turn off the device */
	pm_runtime_set_active(dev);
	pm_runtime_enable(dev);
	pm_runtime_idle(dev);

	ret = v4l2_async_register_subdev(&sensor->sd);
	if (ret) {
		dev_err(&client->dev, "async subdev register failed %d\n", ret);
		goto error_pm_runtime;
	}

	pm_runtime_set_autosuspend_delay(&client->dev, 1000);
	pm_runtime_use_autosuspend(&client->dev);

	dev_dbg(&client->dev, "vgxy61 probe successfully\n");

	return 0;

error_pm_runtime:
	pm_runtime_disable(&client->dev);
	pm_runtime_set_suspended(&client->dev);
	media_entity_cleanup(&sensor->sd.entity);
error_handler_free:
	v4l2_ctrl_handler_free(sensor->sd.ctrl_handler);
error_power_off:
	mutex_destroy(&sensor->lock);
	vgxy61_power_off(dev);

	return ret;
}

static void vgxy61_remove(struct i2c_client *client)
{
	struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);

	v4l2_async_unregister_subdev(&sensor->sd);
	mutex_destroy(&sensor->lock);
	media_entity_cleanup(&sensor->sd.entity);

	pm_runtime_disable(&client->dev);
	if (!pm_runtime_status_suspended(&client->dev))
		vgxy61_power_off(&client->dev);
	pm_runtime_set_suspended(&client->dev);
}

static const struct of_device_id vgxy61_dt_ids[] = {
	{ .compatible = "st,st-vgxy61" },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);

static const struct dev_pm_ops vgxy61_pm_ops = {
	SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL)
};

static struct i2c_driver vgxy61_i2c_driver = {
	.driver = {
		.name  = "vgxy61",
		.of_match_table = vgxy61_dt_ids,
		.pm = &vgxy61_pm_ops,
	},
	.probe = vgxy61_probe,
	.remove = vgxy61_remove,
};

module_i2c_driver(vgxy61_i2c_driver);

MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>");
MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>");
MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>");
MODULE_DESCRIPTION("VGXY61 camera subdev driver");
MODULE_LICENSE("GPL");