Re: [PATCH v2 5/5] media: i2c: Add driver for ST VGXY61 camera sensor

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

 



Hi Benjamin,

Thank you for the patch.

This is a partial review as I'm short on time now.

On Fri, Apr 15, 2022 at 01:18:45PM +0200, Benjamin Mugnier wrote:
> The VGXY61 has a quad lanes CSI-2 output port running at 800mbps per
> lane, and supports RAW8, RAW10, RAW12, RAW14 and RAW16 formats.
> The driver handles both sensor types:
> - VG5661 and VG6661: 1.6 Mpx (1464 x 1104) 75fps.
> - VG5761 and VG6761: 2.3 Mpx (1944 x 1204) 60 fps.
> The driver supports:
> - HDR linearize mode, HDR substraction mode, and no HDR
> - GPIOs LEDs strobing
> - Digital binning and analog subsampling
> - Horizontal and vertical flip
> - Manual exposure
> - Analog and digital gains
> - Test patterns
> 
> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@xxxxxxxxxxx>
> ---
>  drivers/media/i2c/Kconfig     |   10 +
>  drivers/media/i2c/Makefile    |    1 +
>  drivers/media/i2c/st-vgxy61.c | 1997 +++++++++++++++++++++++++++++++++
>  3 files changed, 2008 insertions(+)
>  create mode 100644 drivers/media/i2c/st-vgxy61.c
> 
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index fae2baabb773..734f3e1130b0 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -763,6 +763,16 @@ config VIDEO_VS6624
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called vs6624.
>  
> +config VIDEO_ST_VGXY61

Alphabetical order please.

> +	tristate "ST VGXY61 sensor support"
> +	depends on OF && GPIOLIB && VIDEO_DEV && I2C
> +	select MEDIA_CONTROLLER
> +	select VIDEO_V4L2_SUBDEV_API
> +	select V4L2_FWNODE
> +	help
> +	  This is a Video4Linux2 sensor driver for the ST VGXY61
> +	  camera sensor.
> +
>  source "drivers/media/i2c/ccs/Kconfig"
>  source "drivers/media/i2c/et8ek8/Kconfig"
>  source "drivers/media/i2c/m5mols/Kconfig"
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 3e1696963e7f..7f2362228484 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -116,6 +116,7 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o
>  obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
>  obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o
>  obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
> +obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o
>  obj-$(CONFIG_VIDEO_TC358743) += tc358743.o
>  obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o
>  obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o
> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c
> new file mode 100644
> index 000000000000..1219d278e648
> --- /dev/null
> +++ b/drivers/media/i2c/st-vgxy61.c
> @@ -0,0 +1,1997 @@
> +// 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/module.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/pm_runtime.h>

Alphabetical order in headers too please.

> +#include <linux/units.h>
> +#include <linux/iopoll.h>
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +#define US_PER_MS					1000

I don't think this deserves a macro, but if you want to keep it, you
should avoid generic names that may cause namespace clashes.

> +
> +#define DEVICE_MODEL_ID_REG				0x0000

Please prefix macros with a driver-specific prefix. Many of them have a
too generic name.

> +#define VG5661_MODEL_ID					0x5661
> +#define VG5761_MODEL_ID					0x5761
> +#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_MIN_FRAME_LENGTH				1288
> +#define VGXY61_MIN_EXPOSURE				10
> +#define VGXY61_HDR_LINEAR_RATIO				10
> +#define VGXY61_TIMEOUT_MS				500
> +#define VGXY61_FRAME_LENGTH_DEF				1313
> +#define DEVICE_REVISION					0x0002
> +#define DEVICE_FWPATCH_REVISION				0x0014
> +#define DEVICE_FWPATCH_START_ADDR			0x2000
> +#define DEVICE_SYSTEM_FSM				0x0020
> +#define SW_STBY						0x03
> +#define STREAMING					0x04
> +#define DEVICE_NVM					0x0023
> +#define NVM_OK						0x04
> +#define DEVICE_THSENS1_TEMPERATURE			0x0044
> +#define DEVICE_STBY					0x0201
> +#define STBY_NO_REQ					0
> +#define STBY_REQ_TMP_READ				BIT(2)
> +#define DEVICE_STREAMING				0x0202
> +#define REQ_NO_REQUEST					0
> +#define REQ_STOP_STREAMING				BIT(0)
> +#define REQ_START_STREAMING				BIT(1)
> +#define DEVICE_EXT_CLOCK				0x0220
> +#define DEVICE_CLK_PLL_PREDIV				0x0224
> +#define DEVICE_CLK_SYS_PLL_MULT				0x0225
> +#define DEVICE_GPIO_0_CTRL				0x0236
> +#define DEVICE_GPIO_1_CTRL				0x0237
> +#define DEVICE_GPIO_2_CTRL				0x0238
> +#define DEVICE_GPIO_3_CTRL				0x0239
> +#define DEVICE_SIGNALS_POLARITY_CTRL			0x023b
> +#define DEVICE_LINE_LENGTH				0x0300
> +#define DEVICE_ORIENTATION				0x0302
> +#define DEVICE_VT_CTRL					0x0304
> +#define DEVICE_FORMAT_CTRL				0x0305
> +#define DEVICE_OIF_CTRL					0x0306
> +#define DEVICE_OIF_ROI0_CTRL				0x030a
> +#define DEVICE_ROI0_START_H				0x0400
> +#define DEVICE_ROI0_START_V				0x0402
> +#define DEVICE_ROI0_END_H				0x0404
> +#define DEVICE_ROI0_END_V				0x0406
> +#define DEVICE_PATGEN_CTRL				0x0440
> +#define DEVICE_FRAME_CONTENT_CTRL			0x0478
> +#define DEVICE_COARSE_EXPOSURE_LONG			0x0500
> +#define DEVICE_COARSE_EXPOSURE_SHORT			0x0504
> +#define DEVICE_ANALOG_GAIN				0x0508
> +#define DEVICE_DIGITAL_GAIN_LONG			0x050a
> +#define DEVICE_DIGITAL_GAIN_SHORT			0x0512
> +#define DEVICE_FRAME_LENGTH				0x051a
> +#define DEVICE_SIGNALS_CTRL				0x0522
> +#define DEVICE_READOUT_CTRL				0x0530
> +#define DEVICE_HDR_CTRL					0x0532
> +#define DEVICE_PATGEN_LONG_DATA_GR			0x092c
> +#define DEVICE_PATGEN_LONG_DATA_R			0x092e
> +#define DEVICE_PATGEN_LONG_DATA_B			0x0930
> +#define DEVICE_PATGEN_LONG_DATA_GB			0x0932
> +#define DEVICE_PATGEN_SHORT_DATA_GR			0x0950
> +#define DEVICE_PATGEN_SHORT_DATA_R			0x0952
> +#define DEVICE_PATGEN_SHORT_DATA_B			0x0954
> +#define DEVICE_PATGEN_SHORT_DATA_GB			0x0956
> +#define DEVICE_BYPASS_CTRL				0x0a60
> +
> +#define V4L2_CID_HDR					(V4L2_CID_USER_STVGXY61_BASE + 0)

This macro should have a device-specific name, should be moved to a
public header, and the control should be documented. I would split the
control documentation and macro definition to a separate patch.

> +
> +#define DEVICE_FWPATCH_REVISION_MAJOR			2
> +#define DEVICE_FWPATCH_REVISION_MINOR			0
> +#define DEVICE_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_modes[] = {
> +	"HDR linearize",
> +	"HDR substraction",
> +	"no HDR",
> +};
> +
> +/* Regulator supplies */
> +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

The link frequencies should come from DT.

> +};
> +
> +#define VGXY61_NUM_SUPPLIES		ARRAY_SIZE(vgxy61_supply_name)
> +
> +enum bin_mode {

Same comment here, all enums and structures should use a driver-specific
prefix.

> +	BIN_MODE_NORMAL,
> +	BIN_MODE_DIGITAL_X2,
> +	BIN_MODE_DIGITAL_X4,
> +};
> +
> +enum hdr {
> +	HDR_LINEAR,
> +	HDR_SUB,
> +	NO_HDR,
> +};
> +
> +enum strobe_modes {
> +	STROBE_DISABLED,
> +	STROBE_LONG,
> +	STROBE_ENABLED,
> +};
> +
> +struct vgxy61_mode_info {
> +	u32 width;
> +	u32 height;
> +	enum bin_mode bin_mode;
> +	struct v4l2_rect crop;
> +};
> +
> +static const u32 vgxy61_supported_codes[] = {
> +	MEDIA_BUS_FMT_Y8_1X8,
> +	MEDIA_BUS_FMT_Y10_1X10,
> +	MEDIA_BUS_FMT_Y12_1X12,
> +	MEDIA_BUS_FMT_Y14_1X14,
> +	MEDIA_BUS_FMT_Y16_1X16
> +};
> +
> +static const struct vgxy61_mode_info vgx661_mode_data[] = {
> +	{
> +		.width = VGX661_WIDTH,
> +		.height = VGX661_HEIGHT,
> +		.bin_mode = BIN_MODE_NORMAL,
> +		.crop = {
> +			.left = 0,
> +			.top = 0,
> +			.width = VGX661_WIDTH,
> +			.height = VGX661_HEIGHT,
> +		},

It's very nice to see no hardcoded register values in the modes tables
:-) As the register values are computed by the driver, would it make
sense to go one step further and drop the modes completely ? Analog crop
and binning could then be selected freely from userspace.

> +	},
> +	{
> +		.width = 1280,
> +		.height = 720,
> +		.bin_mode = BIN_MODE_NORMAL,
> +		.crop = {
> +			.left = 92,
> +			.top = 192,
> +			.width = 1280,
> +			.height = 720,
> +		},
> +	},
> +	{
> +		.width = 640,
> +		.height = 480,
> +		.bin_mode = BIN_MODE_DIGITAL_X2,
> +		.crop = {
> +			.left = 92,
> +			.top = 72,
> +			.width = 1280,
> +			.height = 960,
> +		},
> +	},
> +	{
> +		.width = 320,
> +		.height = 240,
> +		.bin_mode = BIN_MODE_DIGITAL_X4,

Can the bin mode computed by dividing the crop width by the output width
instead of being stored here ?

> +		.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 = BIN_MODE_NORMAL,
> +		.crop = {
> +			.left = 0,
> +			.top = 0,
> +			.width = VGX761_WIDTH,
> +			.height = VGX761_HEIGHT,
> +		},
> +	},
> +	{
> +		.width = 1920,
> +		.height = 1080,
> +		.bin_mode = BIN_MODE_NORMAL,
> +		.crop = {
> +			.left = 12,
> +			.top = 62,
> +			.width = 1920,
> +			.height = 1080,
> +		},
> +	},
> +	{
> +		.width = 1280,
> +		.height = 720,
> +		.bin_mode = BIN_MODE_NORMAL,
> +		.crop = {
> +			.left = 332,
> +			.top = 242,
> +			.width = 1280,
> +			.height = 720,
> +		},
> +	},
> +	{
> +		.width = 640,
> +		.height = 480,
> +		.bin_mode = BIN_MODE_DIGITAL_X2,
> +		.crop = {
> +			.left = 332,
> +			.top = 122,
> +			.width = 1280,
> +			.height = 960,
> +		},
> +	},
> +	{
> +		.width = 320,
> +		.height = 240,
> +		.bin_mode = BIN_MODE_DIGITAL_X4,
> +		.crop = {
> +			.left = 332,
> +			.top = 122,
> +			.width = 1280,
> +			.height = 960,
> +		},
> +	},
> +};
> +
> +struct vgxy61_dev {
> +	struct i2c_client *i2c_client;
> +	struct v4l2_subdev sd;
> +	struct media_pad pad;
> +	struct regulator_bulk_data supplies[VGXY61_NUM_SUPPLIES];
> +	struct gpio_desc *reset_gpio;
> +	struct clk *xclk;
> +	u32 clk_freq;
> +	int id;
> +	int sensor_width;
> +	int sensor_height;
> +	u16 oif_ctrl;
> +	int nb_of_lane;
> +	int data_rate_in_mbps;
> +	int pclk;
> +	u16 line_length;
> +	int rot_term;
> +	bool gpios_polarity;
> +	bool slave_mode;
> +	/* 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;
> +	int sensor_modes_nb;
> +	const struct vgxy61_mode_info *current_mode;
> +	struct v4l2_fract frame_interval;

This is not used. Please check all other fields too.

> +	bool hflip;
> +	bool vflip;
> +	enum hdr hdr;
> +	int expo_long;
> +	int expo_short;
> +	int expo_max;
> +	int expo_min;
> +	int vblank;
> +	int vblank_min;
> +	u16 frame_length;
> +};
> +
> +static u8 get_bpp_by_code(__u32 code)
> +{
> +	switch (code) {
> +	case MEDIA_BUS_FMT_Y8_1X8:
> +		return 8;
> +	case MEDIA_BUS_FMT_Y10_1X10:
> +		return 10;
> +	case MEDIA_BUS_FMT_Y12_1X12:
> +		return 12;
> +	case MEDIA_BUS_FMT_Y14_1X14:
> +		return 14;
> +	case MEDIA_BUS_FMT_Y16_1X16:
> +		return 16;
> +	default:
> +		/* Should never happen */
> +		WARN(1, "Unsupported code %d. default to 8 bpp", code);
> +		return 8;
> +	}
> +}
> +
> +static u8 get_data_type_by_code(__u32 code)
> +{
> +	switch (code) {
> +	case MEDIA_BUS_FMT_Y8_1X8:
> +		return MIPI_CSI2_DT_RAW8;
> +	case MEDIA_BUS_FMT_Y10_1X10:
> +		return MIPI_CSI2_DT_RAW10;
> +	case MEDIA_BUS_FMT_Y12_1X12:
> +		return MIPI_CSI2_DT_RAW12;
> +	case MEDIA_BUS_FMT_Y14_1X14:
> +		return MIPI_CSI2_DT_RAW14;
> +	case MEDIA_BUS_FMT_Y16_1X16:
> +		return MIPI_CSI2_DT_RAW16;
> +	default:
> +		/* Should never happen */
> +		WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", code);
> +		return MIPI_CSI2_DT_RAW8;
> +	}
> +}

I'd put all this data in a format description structure, and store that
in the vgxy61_supported_codes array.

> +
> +static void compute_pll_parameters_by_freq(u32 freq, unsigned int *prediv, unsigned int *mult)
> +{
> +	const unsigned int predivs[] = {1, 2, 4};
> +	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 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_read_multiple(struct vgxy61_dev *sensor, u16 reg, u8 *val, int len)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	struct i2c_msg msg[2];
> +	u8 buf[2];
> +	int ret;
> +
> +	buf[0] = reg >> 8;
> +	buf[1] = reg & 0xff;
> +
> +	msg[0].addr = client->addr;
> +	msg[0].flags = client->flags;
> +	msg[0].buf = buf;
> +	msg[0].len = sizeof(buf);
> +
> +	msg[1].addr = client->addr;
> +	msg[1].flags = client->flags | I2C_M_RD;
> +	msg[1].buf = val;
> +	msg[1].len = len;
> +
> +	ret = i2c_transfer(client->adapter, msg, 2);
> +	if (ret < 0) {
> +		dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", __func__,
> +			client->addr, reg, ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u16 reg, u8 *val)
> +{
> +	return vgxy61_read_multiple(sensor, reg, val, sizeof(*val));
> +}
> +
> +static inline int vgxy61_read_reg16(struct vgxy61_dev *sensor, u16 reg, u16 *val)
> +{
> +	return vgxy61_read_multiple(sensor, reg, (u8 *)val, sizeof(*val));
> +}
> +
> +static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u16 reg, const u8 *data, int len)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	struct i2c_msg msg;
> +	u8 buf[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2];
> +	int i;
> +	int ret;
> +
> +	if (len > VGXY61_WRITE_MULTIPLE_CHUNK_MAX)
> +		return -EINVAL;
> +	buf[0] = reg >> 8;
> +	buf[1] = reg & 0xff;
> +	for (i = 0; i < len; i++)
> +		buf[i + 2] = data[i];
> +
> +	msg.addr = client->addr;
> +	msg.flags = client->flags;
> +	msg.buf = buf;
> +	msg.len = len + 2;
> +
> +	ret = i2c_transfer(client->adapter, &msg, 1);
> +	if (ret < 0) {
> +		dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", __func__, reg, ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int vgxy61_write_array(struct vgxy61_dev *sensor, u16 reg, int nb, const u8 *array)
> +{
> +	const int chunk_size = get_chunk_size(sensor);
> +	int ret;
> +	int sz;
> +
> +	while (nb) {
> +		sz = min(nb, chunk_size);
> +		ret = vgxy61_write_multiple(sensor, reg, array, sz);
> +		if (ret < 0)
> +			return ret;
> +		nb -= sz;
> +		reg += sz;
> +		array += sz;
> +	}
> +
> +	return 0;
> +}
> +
> +static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u16 reg, u8 val)
> +{
> +	return vgxy61_write_multiple(sensor, reg, &val, sizeof(val));
> +}
> +
> +static inline int vgxy61_write_reg16(struct vgxy61_dev *sensor, u16 reg, u16 val)
> +{
> +	return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
> +}
> +
> +static inline int vgxy61_write_reg32(struct vgxy61_dev *sensor, u16 reg, u32 val)
> +{
> +	return vgxy61_write_multiple(sensor, reg, (u8 *)&val, sizeof(val));
> +}

You could possibly simplify handling of registers of different sizes by
encoding the size in the register address macros. See
https://gitlab.com/ideasonboard/nxp/linux/-/blob/pinchartl/v5.18/sensors/imx/drivers/media/i2c/imx296.c
for an example.

> +
> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val, int timeout_ms)
> +{
> +	const int loop_delay_ms = 10;
> +	u8 val;
> +	int ret, timeout;
> +
> +	timeout = read_poll_timeout(vgxy61_read_reg, ret, ((ret != 0) || (val == poll_val)),
> +				    loop_delay_ms * US_PER_MS, timeout_ms * US_PER_MS, false,
> +				    sensor, reg, &val);
> +	if (timeout)
> +		return timeout;
> +
> +	return ret;
> +}
> +
> +static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state, int timeout_ms)
> +{
> +	return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state, timeout_ms);
> +}
> +
> +static int vgxy61_check_bw(struct vgxy61_dev *sensor)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	/* Correction factor for time required between 2 lines */
> +	const int mipi_margin = 1056;
> +	int binning_scale = sensor->current_mode->crop.height / sensor->current_mode->height;
> +	int bpp = get_bpp_by_code(sensor->fmt.code);
> +	int max_bit_per_line;
> +	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;
> +
> +	dev_dbg(&client->dev, "max_bit_per_line = %d\n", max_bit_per_line);
> +	dev_dbg(&client->dev, "required bit_per_line = %d\n", bit_per_line);
> +
> +	return bit_per_line > max_bit_per_line ? -EINVAL : 0;
> +}
> +
> +static int apply_exposure(struct vgxy61_dev *sensor)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	int ret;
> +
> +	 /* We first set expo to zero to avoid forbidden parameters couple */
> +	ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, 0);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_LONG, sensor->expo_long);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_COARSE_EXPOSURE_SHORT, sensor->expo_short);
> +	if (ret)
> +		return ret;

Is there a risk those writes may be applied to different frames ?

> +
> +	dev_dbg(&client->dev, "%s applied expo %d (short: %d)\n", __func__,
> +		sensor->expo_long, sensor->expo_short);
> +
> +	return 0;
> +}
> +
> +static int set_frame_rate(struct vgxy61_dev *sensor)

The function sets the frame length, not the frame rate.

> +{
> +	return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, sensor->frame_length);
> +}
> +
> +static int vgxy61_get_regulators(struct vgxy61_dev *sensor)
> +{
> +	int i;
> +
> +	for (i = 0; i < VGXY61_NUM_SUPPLIES; i++)
> +		sensor->supplies[i].supply = vgxy61_supply_name[i];
> +
> +	return devm_regulator_bulk_get(&sensor->i2c_client->dev, VGXY61_NUM_SUPPLIES,
> +				       sensor->supplies);
> +}
> +
> +static int vgxy61_apply_reset(struct vgxy61_dev *sensor)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +
> +	dev_dbg(&client->dev, "%s applied reset\n", __func__);
> +	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, SW_STBY, VGXY61_TIMEOUT_MS);
> +}
> +
> +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 = sensor->sensor_modes;
> +	unsigned int index;
> +
> +	/* Select code */
> +	for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) {
> +		if (vgxy61_supported_codes[index] == fmt->code)
> +			break;
> +	}
> +	if (index == ARRAY_SIZE(vgxy61_supported_codes))
> +		index = 0;
> +
> +	/* Select size */
> +	mode = v4l2_find_nearest_size(sensor->sensor_modes, sensor->sensor_modes_nb,
> +				      width, height, fmt->width, fmt->height);
> +	if (new_mode)
> +		*new_mode = mode;
> +
> +	fmt->code = vgxy61_supported_codes[index];
> +	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;
> +
> +	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_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;
> +
> +	ret = vgxy61_check_bw(sensor);
> +	if (ret)
> +		return ret;
> +
> +	ret = pm_runtime_get_sync(&client->dev);
> +	if (ret < 0) {
> +		pm_runtime_put_noidle(&client->dev);
> +		return ret;
> +	}
> +
> +	/* Configure sensor */
> +	ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code));
> +	if (ret)
> +		goto err_rpm_put;
> +	ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL,
> +			       get_data_type_by_code(sensor->fmt.code));
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode);
> +	if (ret)
> +		goto err_rpm_put;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, crop->left);
> +	if (ret)
> +		goto err_rpm_put;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, crop->left + crop->width - 1);
> +	if (ret)
> +		goto err_rpm_put;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, crop->top);
> +	if (ret)
> +		goto err_rpm_put;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, crop->top + crop->height - 1);
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	ret = set_frame_rate(sensor);
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	ret = apply_exposure(sensor);
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	/* Start streaming */
> +	ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING);
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST, VGXY61_TIMEOUT_MS);
> +	if (ret)
> +		goto err_rpm_put;
> +
> +	ret = vgxy61_wait_state(sensor, 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 = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_STOP_STREAMING);
> +	if (ret)
> +		return ret;
> +
> +	ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST, 2000);
> +	if (ret)
> +		return ret;
> +
> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
> +	if (ret)
> +		return ret;
> +
> +	__v4l2_ctrl_grab(sensor->vflip_ctrl, true);
> +	__v4l2_ctrl_grab(sensor->hflip_ctrl, true);
> +
> +	pm_runtime_put(&client->dev);
> +
> +	return 0;
> +}
> +
> +static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +	struct i2c_client *client = sensor->i2c_client;
> +	int ret = 0;
> +
> +	mutex_lock(&sensor->lock);
> +	dev_dbg(&client->dev, "%s : requested %d / current = %d\n", __func__, enable,
> +		sensor->streaming);
> +	if (sensor->streaming == enable)
> +		goto out;

This should never happen. If you want to be on the safe side and keep
the check, I'd turn it into a WARN_ON().

> +
> +	ret = enable ? vgxy61_stream_enable(sensor) : vgxy61_stream_disable(sensor);
> +	if (!ret)
> +		sensor->streaming = enable;
> +
> +out:
> +	dev_dbg(&client->dev, "%s current now = %d / %d\n", __func__, sensor->streaming, ret);
> +	mutex_unlock(&sensor->lock);
> +
> +	return ret;
> +}
> +
> +static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd,
> +				 struct v4l2_subdev_state *sd_state,
> +				 struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +	struct i2c_client *client = sensor->i2c_client;
> +
> +	dev_dbg(&client->dev, "%s probe index %d\n", __func__, code->index);
> +	if (code->index >= ARRAY_SIZE(vgxy61_supported_codes))
> +		return -EINVAL;
> +
> +	code->code = vgxy61_supported_codes[code->index];
> +
> +	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 i2c_client *client = sensor->i2c_client;
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
> +	dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
> +
> +	mutex_lock(&sensor->lock);
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
> +		fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, format->pad);
> +	else
> +		fmt = &sensor->fmt;
> +
> +	format->format = *fmt;
> +
> +	mutex_unlock(&sensor->lock);q
> +
> +	return 0;
> +}
> +
> +static int vgxy61_get_vblank_min(struct vgxy61_dev *sensor, enum hdr hdr)
> +{
> +	int min_vblank =  VGXY61_MIN_FRAME_LENGTH - sensor->current_mode->crop.height;
> +	/* Ensure the first tule of thumb can't be negative */
> +	int min_vblank_hdr =  VGXY61_MIN_EXPOSURE + sensor->rot_term + 1;
> +
> +	if (hdr != 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);
> +	struct i2c_client *client = sensor->i2c_client;
> +
> +	dev_dbg(&client->dev, "%s for index %d\n", __func__, fse->index);
> +	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)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	int ret;
> +
> +	/* Apply gain */
> +	ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, target);
> +	if (ret)
> +		return ret;
> +
> +	dev_dbg(&client->dev, "Set analog gain: 0x%04x\n", target);
> +
> +	return 0;
> +}
> +
> +static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	int ret;
> +
> +	/*
> +	 * 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.
> +	 */
> +	ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_LONG, target);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_DIGITAL_GAIN_SHORT, target);
> +	if (ret)
> +		return ret;
> +
> +	dev_dbg(&client->dev, "Set digital gain: 0x%04x\n", target);
> +
> +	return 0;
> +}
> +
> +static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 index)
> +{
> +	u32 pattern = index <= 3 ? index : index + 12;
> +	u32 reg;
> +
> +	reg = (pattern << 18) | (pattern << 4);
> +	if (index)
> +		reg |= (1 << 16) | 1;

Macros would be nice instead of magic numbers. Same where applicable
below.

> +	return vgxy61_write_reg32(sensor, DEVICE_PATGEN_CTRL, reg);
> +}
> +
> +static int vgxy61_update_gpiox_strobe_mode(struct vgxy61_dev *sensor, enum strobe_modes mode,
> +					   int idx)
> +{
> +	const u8 index2val[] = {0x0, 0x1, 0x3};

Make this static const.

> +	u16 reg;
> +	int ret;
> +
> +	ret = vgxy61_read_reg16(sensor, DEVICE_SIGNALS_CTRL, &reg);
> +	if (ret)
> +		return ret;
> +	reg &= ~(0xf << (idx * 4));
> +	reg |= index2val[mode] << (idx * 4);
> +
> +	return vgxy61_write_reg16(sensor, DEVICE_SIGNALS_CTRL, reg);
> +}
> +
> +static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, enum hdr hdr)
> +{
> +	enum strobe_modes strobe_mode;
> +	int i, ret;
> +
> +	switch (hdr) {
> +	case HDR_LINEAR:
> +		strobe_mode = STROBE_ENABLED;
> +		break;
> +	case HDR_SUB:
> +	case NO_HDR:
> +		strobe_mode = STROBE_LONG;
> +		break;
> +	default:
> +		/* Should never happen */
> +		WARN_ON(true);
> +		break;
> +	}
> +
> +	for (i = 0; i < VGXY61_NB_GPIOS; i++) {
> +		ret = vgxy61_update_gpiox_strobe_mode(sensor, strobe_mode, i);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, int polarity)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	int ret;
> +
> +	if (sensor->streaming)
> +		return -EBUSY;
> +
> +	dev_dbg(&client->dev, "setting gpios polarity: %d\n", polarity);
> +
> +	ret = vgxy61_write_reg(sensor, DEVICE_GPIO_0_CTRL, polarity << 1);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg(sensor, DEVICE_GPIO_1_CTRL, polarity << 1);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg(sensor, DEVICE_GPIO_2_CTRL, polarity << 1);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg(sensor, DEVICE_GPIO_3_CTRL, polarity << 1);
> +	if (ret)
> +		return ret;
> +	return vgxy61_write_reg(sensor, DEVICE_SIGNALS_POLARITY_CTRL, polarity);
> +}
> +
> +static int vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, int short_expo_ratio)
> +{
> +	int first_rot_max_expo, second_rot_max_expo, third_rot_max_expo;
> +
> +	/* Apply sensor rule of thumbs */
> +	/* 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;
> +
> +	/* Avoid sensor crash */

How so ?

> +	second_rot_max_expo =
> +		(((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) /
> +		(short_expo_ratio + 1)) - 1;
> +
> +	/* 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, int new_expo_long, enum hdr hdr)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	int new_expo_short = 0;
> +	int expo_short_max = 0;
> +	int expo_long_min = VGXY61_MIN_EXPOSURE;
> +	int expo_long_max;
> +
> +	/* Compute short exposure according to hdr mode and long exposure */
> +	switch (hdr) {
> +	case HDR_LINEAR:
> +		/* Take ratio into account for minimal exposures in 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 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 HDR_SUB */
> +		expo_short_max = expo_long_max;
> +		new_expo_short = new_expo_long;
> +		break;
> +	case 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;
> +	}
> +
> +	dev_dbg(&client->dev, "frame_length %d, new_expo_long %d, new_expo_short %d\n",
> +		sensor->frame_length, new_expo_long, new_expo_short);
> +	dev_dbg(&client->dev, "expo_long_min %d, expo_long_max %d\n",
> +		expo_long_min, expo_long_max);
> +
> +	/* Apply exposure */
> +	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 apply_exposure(sensor);
> +	return 0;
> +}
> +
> +static int vgxy61_update_vblank(struct vgxy61_dev *sensor, int vblank, int 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 set_frame_rate(sensor);
> +	return 0;
> +}
> +
> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index)
> +{
> +	const u8 index2val[] = {0x1, 0x4, 0xa};
> +	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;
> +
> +	ret = vgxy61_write_reg(sensor, DEVICE_HDR_CTRL, index2val[index]);
> +	if (ret)
> +		return ret;
> +	sensor->hdr = index;
> +
> +	return 0;
> +}
> +
> +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);
> +	struct i2c_client *client = sensor->i2c_client;
> +	const struct vgxy61_mode_info *new_mode;
> +	struct v4l2_mbus_framefmt *fmt;
> +	int ret;
> +
> +	dev_dbg(&client->dev, "%s probe %d\n", __func__, format->pad);
> +	dev_dbg(&client->dev, "%s %dx%d\n", __func__, format->format.width, format->format.height);
> +
> +	mutex_lock(&sensor->lock);
> +
> +	if (sensor->streaming) {
> +		ret = -EBUSY;
> +		goto out;
> +	}
> +
> +	/* Find best format */
> +	ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode);
> +	if (ret)
> +		goto out;
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> +		fmt = v4l2_subdev_get_try_format(sd, 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 -
> +					   sensor->current_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 - sensor->current_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_get_temp_stream_enabled(struct vgxy61_dev *sensor, int *temp)
> +{
> +	int ret;
> +	u16 temperature;
> +
> +	ret = vgxy61_read_reg16(sensor, DEVICE_THSENS1_TEMPERATURE, &temperature);
> +	if (ret)
> +		return ret;
> +
> +	/* Temperature is expressed in Kelvin in Q10.2 fixed point format*/
> +	temperature = (temperature & 0x0fff) >> 2;
> +	temperature = kelvin_to_celsius(temperature);
> +
> +	*temp = temperature;
> +
> +	return 0;
> +}
> +
> +static int vgxy61_get_temp_stream_disabled(struct vgxy61_dev *sensor, int *temp)
> +{
> +	int ret;
> +
> +	/* Device needs to be in standby mode if streaming is off */
> +	ret = vgxy61_write_reg(sensor, DEVICE_STBY, STBY_REQ_TMP_READ);

Can't runtime PM be disabled here ?

> +	if (ret)
> +		return ret;
> +	ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ, VGXY61_TIMEOUT_MS);
> +	if (ret)
> +		return ret;
> +
> +	return vgxy61_get_temp_stream_enabled(sensor, temp);
> +}
> +
> +static int vgxy61_get_temp(struct vgxy61_dev *sensor, int *temp)
> +{
> +	*temp = 0;
> +	if (sensor->streaming)
> +		return vgxy61_get_temp_stream_enabled(sensor, temp);
> +	else
> +		return vgxy61_get_temp_stream_disabled(sensor, temp);
> +}
> +
> +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);
> +	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 = vgxy61_write_reg(sensor, DEVICE_ORIENTATION,
> +				       sensor->hflip | (sensor->vflip << 1));
> +		break;
> +	case V4L2_CID_TEST_PATTERN:
> +		ret = vgxy61_update_patgen(sensor, ctrl->val);
> +		break;
> +	case V4L2_CID_HDR:
> +		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 - sensor->current_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 int vgxy61_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> +	struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
> +	int ret;
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_TEMPERATURE:
> +		ret = vgxy61_get_temp(sensor, &ctrl->val);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = {
> +	.g_volatile_ctrl = vgxy61_g_volatile_ctrl,
> +	.s_ctrl = vgxy61_s_ctrl,
> +};
> +
> +static const struct v4l2_ctrl_config vgxy61_hdr_ctrl = {
> +	.ops		= &vgxy61_ctrl_ops,
> +	.id		= V4L2_CID_HDR,
> +	.name		= "HDR mode",
> +	.type		= V4L2_CTRL_TYPE_MENU,
> +	.min		= 0,
> +	.max		= ARRAY_SIZE(vgxy61_hdr_modes) - 1,
> +	.def		= NO_HDR,
> +	.qmenu		= vgxy61_hdr_modes,
> +};
> +
> +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;
> +	struct v4l2_ctrl *ctrl;
> +	int ret;
> +
> +	v4l2_ctrl_handler_init(hdl, 16);
> +	mutex_init(&sensor->lock);

This should be initialized in probe(), it doesn't belong here.

> +	/* We can use our own mutex for the ctrl lock */
> +	hdl->lock = &sensor->lock;
> +	/* Standard controls */
> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, 0x0);
> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, 256);
> +	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_TEMPERATURE, -128, 128, 1, 0);
> +	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);
> +	v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL);
> +	ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, sensor->line_length, 1,
> +				 sensor->line_length - sensor->current_mode->width);
> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +	/* Custom controls */
> +	ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, ARRAY_SIZE(link_freq) - 1, 0,
> +				      link_freq);
> +	ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +	/* 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));
> +	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 - sensor->current_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;
> +	}
> +
> +	sensor->sd.ctrl_handler = hdl;
> +	return 0;
> +
> +free_ctrls:
> +	v4l2_ctrl_handler_free(hdl);
> +	mutex_destroy(&sensor->lock);
> +	return ret;
> +}
> +
> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = {
> +};

You can drop this.

> +
> +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 media_entity_operations vgxy61_subdev_entity_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +/* Set phy polarities */
> +static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, struct fwnode_handle *endpoint)
> +{
> +	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;
> +	int p, l;
> +	int ret;
> +	int i;
> +
> +	ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep);
> +	if (ret)
> +		goto error_alloc;
> +
> +	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 < 5; 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);
> +error_alloc:
> +
> +	return -EINVAL;
> +}
> +
> +static int vgxy61_configure(struct vgxy61_dev *sensor)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	int sensor_freq;
> +	unsigned int prediv;
> +	unsigned int mult;
> +	int ret;
> +
> +	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;
> +
> +	/* Cache line_length value */
> +	ret = vgxy61_read_reg16(sensor, DEVICE_LINE_LENGTH, &sensor->line_length);
> +	if (ret)
> +		return ret;
> +	/* Configure clocks */
> +	ret = vgxy61_write_reg32(sensor, DEVICE_EXT_CLOCK, sensor->clk_freq);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg(sensor, DEVICE_CLK_PLL_PREDIV, prediv);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg(sensor, DEVICE_CLK_SYS_PLL_MULT, mult);
> +	if (ret)
> +		return ret;
> +	/* Configure interface */
> +	ret = vgxy61_write_reg16(sensor, DEVICE_OIF_CTRL, sensor->oif_ctrl);
> +	if (ret)
> +		return ret;
> +	/* Disable pwm compression */
> +	ret = vgxy61_write_reg(sensor, DEVICE_FRAME_CONTENT_CTRL, 0);
> +	if (ret)
> +		return ret;
> +	/* Disable asil lines */
> +	ret = vgxy61_write_reg(sensor, DEVICE_BYPASS_CTRL, 4);
> +	if (ret)
> +		return ret;
> +	/* Set gpios polarity according to device tree value */
> +	ret = vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity);
> +	if (ret)
> +		return ret;
> +	/* Slave mode */
> +	ret = vgxy61_write_reg(sensor, DEVICE_VT_CTRL, sensor->slave_mode);
> +	if (ret)
> +		return ret;
> +	/* Set pattern generator solid to middle value */
> +	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GR, 0x800);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_R, 0x800);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_B, 0x800);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_LONG_DATA_GB, 0x800);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GR, 0x800);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_R, 0x800);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_B, 0x800);
> +	if (ret)
> +		return ret;
> +	ret = vgxy61_write_reg16(sensor, DEVICE_PATGEN_SHORT_DATA_GB, 0x800);
> +	if (ret)
> +		return ret;
> +
> +	dev_dbg(&client->dev, "clock prediv = %d\n", prediv);
> +	dev_dbg(&client->dev, "clock mult = %d\n", mult);
> +	dev_dbg(&client->dev, "data rate = %d mbps\n",
> +		sensor->data_rate_in_mbps);
> +
> +	return 0;
> +}
> +
> +static int vgxy61_patch(struct vgxy61_dev *sensor)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	u16 patch;
> +	int ret;
> +
> +	ret = vgxy61_write_array(sensor, DEVICE_FWPATCH_START_ADDR, sizeof(patch_array),
> +				 patch_array);
> +	if (ret)
> +		return ret;
> +
> +	ret = vgxy61_write_reg(sensor, DEVICE_STBY, 0x10);
> +	if (ret)
> +		return ret;
> +
> +	ret = vgxy61_poll_reg(sensor, DEVICE_STBY, 0, VGXY61_TIMEOUT_MS);
> +	if (ret)
> +		return ret;
> +
> +	ret = vgxy61_read_reg16(sensor, DEVICE_FWPATCH_REVISION, &patch);
> +	if (ret)
> +		return ret;
> +
> +	if (patch != (DEVICE_FWPATCH_REVISION_MAJOR << 12) +
> +		     (DEVICE_FWPATCH_REVISION_MINOR << 8) +
> +		     DEVICE_FWPATCH_REVISION_MICRO) {
> +		dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n",
> +			DEVICE_FWPATCH_REVISION_MAJOR,
> +			DEVICE_FWPATCH_REVISION_MINOR,
> +			DEVICE_FWPATCH_REVISION_MICRO,
> +			patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
> +		return -ENODEV;
> +	}
> +	dev_dbg(&client->dev, "patch %d.%d.%d applied\n",
> +		patch >> 12, (patch >> 8) & 0x0f, patch & 0xff);
> +
> +	return 0;
> +}
> +
> +static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor)
> +{
> +	struct i2c_client *client = sensor->i2c_client;
> +	u16 device_rev;
> +	int ret;
> +
> +	ret = vgxy61_read_reg16(sensor, DEVICE_REVISION, &device_rev);
> +	if (ret)
> +		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;
> +	u16 id = 0;
> +	int ret;
> +	u8 st;
> +
> +	ret = vgxy61_read_reg16(sensor, DEVICE_MODEL_ID_REG, &id);
> +	if (ret)
> +		return ret;
> +	if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) {
> +		dev_warn(&client->dev, "Unsupported sensor id %x\n", id);
> +		return -ENODEV;
> +	}
> +	dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", id);
> +	sensor->id = id;
> +
> +	ret = vgxy61_wait_state(sensor, SW_STBY, VGXY61_TIMEOUT_MS);
> +	if (ret)
> +		return ret;
> +
> +	ret = vgxy61_read_reg(sensor, DEVICE_NVM, &st);
> +	if (ret)
> +		return ret;
> +	if (st != NVM_OK)
> +		dev_warn(&client->dev, "Bad nvm state got %d\n", st);
> +
> +	/* Detect cut version */
> +	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 = vgxy61_get_regulators(sensor);
> +	if (ret) {
> +		dev_err(&client->dev, "failed to get regulators %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, 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;
> +	}
> +
> +	/* Apply reset sequence */
> +	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);

Do we really need to run detection every time the sensor is powered on ?

> +	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(VGXY61_NUM_SUPPLIES, 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(VGXY61_NUM_SUPPLIES, sensor->supplies);
> +	return 0;
> +}
> +
> +static int __maybe_unused vgxy61_suspend(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);
> +
> +	if (sensor->streaming)
> +		vgxy61_stream_disable(sensor);

This shoudn't happen. The sensor should be stopped by the driver on the
receiver side during system suspend. Same for resume.

> +
> +	return 0;
> +}
> +
> +static int __maybe_unused vgxy61_resume(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;
> +
> +	if (sensor->streaming) {
> +		ret = vgxy61_stream_enable(sensor);
> +		if (ret)
> +			goto error;
> +	}
> +
> +	return 0;
> +
> +error:
> +	vgxy61_stream_disable(sensor);
> +	sensor->streaming = false;
> +
> +	return ret;
> +}
> +
> +static void vgxy61_set_default_format(struct vgxy61_dev *sensor)
> +{
> +	if (sensor->id == VG5761_MODEL_ID) {

Would it make sense to have different compatible strings instead of
detecting the version automatically ? Not only could it speed up probe
if we could skip the detection, it would also allow not powering the
sensor up at probe time, which may be required in some cases (for
instance when a privacy LED is connected to the sensor power supply,
flashing the LED at probe time can be very confusing for users).

> +		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->current_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->current_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE];
> +		sensor->rot_term = VGX661_SHORT_ROT_TERM;
> +	} else {
> +		/* Should never happen */
> +		WARN_ON(true);
> +	}
> +	sensor->fmt.width = sensor->current_mode->width;
> +	sensor->fmt.height = sensor->current_mode->height;
> +	sensor->vblank = sensor->frame_length - sensor->current_mode->crop.height;
> +}
> +
> +static int vgxy61_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct fwnode_handle *endpoint;
> +	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->fmt.code = MEDIA_BUS_FMT_Y8_1X8;
> +	sensor->fmt.colorspace = V4L2_COLORSPACE_RAW;
> +	sensor->fmt.field = V4L2_FIELD_NONE;
> +	sensor->fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> +	sensor->fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
> +	sensor->fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;

Please implement the subdev .init_cfg() operation, and call it here.
There are several examples in existing drivers.

> +	sensor->frame_interval.numerator = 1;
> +	sensor->frame_interval.denominator = 60;
> +	sensor->hdr = NO_HDR;
> +	sensor->expo_long = 200;
> +	sensor->expo_short = 0;
> +	sensor->hflip = false;
> +	sensor->vflip = false;
> +	sensor->frame_length = VGXY61_FRAME_LENGTH_DEF;
> +
> +	endpoint = fwnode_graph_get_next_endpoint(of_fwnode_handle(dev->of_node), NULL);
> +	if (!endpoint) {
> +		dev_err(dev, "endpoint node not found\n");
> +		return -EINVAL;
> +	}
> +
> +	ret = vgxy61_tx_from_ep(sensor, endpoint);
> +	fwnode_handle_put(endpoint);
> +	if (ret) {
> +		dev_err(dev, "Failed to parse endpoint %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 = of_property_read_bool(dev->of_node, "invert-gpios-polarity");
> +	sensor->slave_mode = of_property_read_bool(dev->of_node, "slave-mode");
> +
> +	v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops);
> +	sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
> +	sensor->sd.entity.ops = &vgxy61_subdev_entity_ops;
> +	sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> +
> +	/* Request optional reset pin */
> +	sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> +
> +	ret = vgxy61_power_on(dev);
> +	if (ret)
> +		return ret;
> +
> +	vgxy61_set_default_format(sensor);
> +
> +	/* HDR mode */
> +	ret = vgxy61_update_hdr(sensor, sensor->hdr);
> +	if (ret)
> +		return ret;
> +
> +	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;
> +	}
> +
> +	ret = v4l2_async_register_subdev(&sensor->sd);
> +	if (ret) {
> +		dev_err(&client->dev, "async subdev register failed %d\n", ret);
> +		goto error_media_entity;
> +	}
> +
> +	/* Enable runtime PM and turn off the device */
> +	pm_runtime_set_active(dev);
> +	pm_runtime_enable(dev);
> +	pm_runtime_idle(dev);

You should do this before registering the subdev, as it can be accessed
from userspace right after being registered.

Please also use auto-suspend to avoid long delays when stopping and
restarting streaming quickly.

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

I'd drop this, or turn it into a debug message.

> +
> +	return 0;
> +
> +error_media_entity:
> +	media_entity_cleanup(&sensor->sd.entity);
> +error_handler_free:
> +	v4l2_ctrl_handler_free(sensor->sd.ctrl_handler);
> +	mutex_destroy(&sensor->lock);
> +error_power_off:
> +	vgxy61_power_off(dev);
> +
> +	return ret;
> +}
> +
> +static int 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);
> +
> +	return 0;
> +}
> +
> +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_SYSTEM_SLEEP_PM_OPS(vgxy61_suspend, vgxy61_resume)
> +	SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL)
> +};
> +
> +static struct i2c_driver vgxy61_i2c_driver = {
> +	.driver = {
> +		.name  = "st-vgxy61",
> +		.of_match_table = vgxy61_dt_ids,
> +		.pm = &vgxy61_pm_ops,
> +	},
> +	.probe_new = vgxy61_probe,
> +	.remove = vgxy61_remove,
> +};
> +
> +module_i2c_driver(vgxy61_i2c_driver);
> +
> +MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@xxxxxxxxxxx>");
> +MODULE_AUTHOR("Mickael Guene <mickael.guene@xxxxxx>");
> +MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@xxxxxxxxxxx>");
> +MODULE_DESCRIPTION("VGXY61 camera subdev driver");
> +MODULE_LICENSE("GPL v2");

-- 
Regards,

Laurent Pinchart



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

  Powered by Linux