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 Laurent,

On 22/04/2022 15:29, Laurent Pinchart wrote:
> Hi Benjamin,
> 
> On Fri, Apr 22, 2022 at 10:44:13AM +0200, Benjamin Mugnier wrote:
>> On 20/04/2022 18:48, Laurent Pinchart wrote:
>>> On Wed, Apr 20, 2022 at 03:01:24PM +0200, Benjamin Mugnier wrote:
>>>> On 15/04/2022 17:39, Laurent Pinchart wrote:
>>>>> 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.
>>>>>
>>>>
>>>> I don't make heavy usage of it so I will remove it.
>>>>
>>>>>> +
>>>>>> +#define DEVICE_MODEL_ID_REG				0x0000
>>>>>
>>>>> Please prefix macros with a driver-specific prefix. Many of them have a
>>>>> too generic name.
>>>>>
>>>>
>>>> I will change this to VGXY61_REG_* for registers, and simply VGXY61_* for others.
>>>>
>>>>>> +#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.
>>>>>
>>>>
>>>> I created two new files include/uapi/linux/st-vgxy61.h and
>>>> Documentation/userspace-api/media/drivers/st-vgxy61.rst.
>>>>
>>>> I'm not sure what you mean by separating the documentation and
>>>> definition. As there is a significant amount of code dependent on the
>>>> HDR mode I will keep the public header in this patch, and create a new
>>>> patch for the documentation. Tell me if there is any issue with this.
>>>
>>> I meant a patch that adds include/uapi/linux/st-vgxy61.h and
>>> Documentation/userspace-api/media/drivers/st-vgxy61.rst separate from
>>> the driver, but if it causes more issues than it helps review, you can
>>> combine them in a single patch.
>>
>> Ok, I initially thought about moving the relevant st-vgxy61.c hdr code
>> to a new patch, which will be quite complicated.
>> Only definition and documentation make no issue.
>>
>>>>>> +
>>>>>> +#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.
>>>>
>>>> This is a hard requirement for this sensor. This value can not be
>>>> modified and must be 402MHz.
>>>>
>>>> I see the ov9734.c does something similar. Is adding this value to the
>>>> device tree really mandatory?
>>>
>>> I suppose it's fine then. Sakari, what do you think ?
>>>
>>>>>> +};
>>>>>> +
>>>>>> +#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.
>>>>
>>>> All credit goes to Dave for pointing this in the first review ;)
>>>> As for the modes, in theory we could drop them completely, but this
>>>> will require some extra testing as the sensor can't really handle
>>>> every single mode freely.
>>>>
>>>> Could this be done in a second step?
>>>
>>> I'm a bit concerned it could then break userspace, if some applications
>>> start depending on a fixed list of modes. How difficult do you think
>>> this would be ?
>>
>> I mean if they use a fixed list, the upgrade will still allow them to
>> use this list right? As it will implement a superset of the resolution
>> list.
>>
>> Also if I understand correctly they will get this list dynamically
>> from the get_fmt call, hence once the full support is added the
>> application will update itself right?
> 
> The idea is that we could get rid of the modes list completely, and let
> the application configure everything through the formats and selection
> rectangles. Moving in that direction later may cause some regressions,
> for instance an application may set a mode that is not supported, with
> the driver picking the closest option now, and later it may get the
> exact resolution it requested. It's hard to know if this could break
> something.
> 
>> For the complexity I need to check various rules to make sure the
>> sensor will not be in a bad state. On top of my head the X and Y must
>> be even, binning must match exactly the format ratio, and certainly
>> other stuff I have not been confronted to yet. And of course testing.
> 
> Those rules don't seem to be too complicated. Testing will likely be the
> biggest time consumer :-)
> 

I will give it go for v3 then. If I run into issues I could still remove the find_nearest_size call and return an error if the exact size is not supported, but appart from the time consumming part this should go well.

>> Tell me what you think.
>>
>>>>>> +	},
>>>>>> +	{
>>>>>> +		.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 ?
>>>>
>>>> The sensor also supports analog subsampling on top of digital binning.
>>>> I did not implement it in the driver but I made the choice to
>>>> explicitly set the bin_mode for further development. For example the
>>>> line above could be:
>>>>
>>>>   .bin_mode = BIN_MODE_ANALOG_X4
>>>>
>>>> I can compute the factor (X2/X4), but not the analog/digital mode.
>>>> Hope this make sense.
>>>
>>> It thus indeed, but it then leads to the question of how to configure
>>> skipping and binning separately. The CCS driver registers two subdevs to
>>> make this possible. It increases the complexity, and I'm increasingly
>>> thinking that we should extend the V4L2 API to allow configure all the
>>> common parameters of camera sensors with a single subdev.
>>>
>>> Sakari, any opinion ?
>>>
>>>>>> +		.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.
>>>>
>>>> That sounds good, thank you. I will look into this.
>>>
>>> On the topic of register writes, you may also want to consider the *err
>>> argument to imx296_write(), it simplifies error handling (see how errors
>>> are handled in imx296_setup(), compared to checking the return value of
>>> every single write call).
>>>
>>>>>> +
>>>>>> +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 ?
>>>>
>>>> Yes, but it does not matter for the sensor. Are you thinking of an
>>>> issue about user space applications for instance?
>>>
>>> Yes, if the writes cross frame boundaries, we could have a frame with a
>>> weird exposure time, which could confuse ISP algorithms.
>>>
>>> Does the sensor support CSI-2 embedded data ?
>>
>> It does, and embed the exposures in it. So I guess the ISP algorithm
>> could check these and react accordingly.
> 
> For ISPs that can capture embedded data, that sounds good.
> 
>> As a side note the short exposure is only used for HDR modes, so as
>> long as the user is running in SDR there is no impact at all.
> 
> That can help.
> 
> I suppose we can improve this further later if/when needed. I'm just
> worried it could lead to issues that are difficult to debug.
> 
>>>>>> +
>>>>>> +	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().
>>>>
>>>> You are right, I will remove this.
>>>>
>>>>>> +
>>>>>> +	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 ?
>>>>
>>>> This specific rule guarantees the framelength < expo_time. If this is
>>>> not the case then the sensor will crash.
>>>>
>>>> I will add a bit more comment for this, and for other rules also.
>>>>
>>>>>> +	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 ?
>>>>
>>>> You're absolutely right. This works as of now because I guess my
>>>> regulators are used for something else and therefore do not go down,
>>>> but should not.
>>>>
>>>> This is tricky as I don't want to power up (and therefore patch) the
>>>> sensor every time I want to see the temperature, but maybe I won't
>>>> have the choice.
>>>>
>>>> For example every time I stop streaming in qv4l2, the temperature is read. This would result in this:
>>>> 	power off -> get_temp -> power on (+patch) -> read temp -> power off
>>>> Maybe I can just remove the temperature reading while off? Any other idea?
>>>
>>> I think returning an error when the sensor is off is fine. The main use
>>> cases for the temperature sensor are related to imaging.
>>>
>>>> Anyway this will depend on the 2/5 patch discussion thread result.
>>>>
>>>>>> +	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 ?
>>>>
>>>> Surely not, I will add a if (sensor->already_detected) guard for this
>>>> call to make it runs only once.
>>>
>>> It could also be done in probe().
>>>
>>>>>> +	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.
>>>>
>>>> Sorry I'm still learning the pm_runtime API and I'm not sure I got your point.
>>>> If the sensor is already stopped then I could just remove the whole suspend function? Same for resume.
>>>> I'm not sure when the suspend function is called by the framework.
>>>
>>> vgxy61_suspend() is called on system suspend. Disabling streaming here
>>> is problematic, as it's done independently from the CSI-2 receiver. It's
>>> even worse on resume, as if you resume the sensor first, the CSI-2 lanes
>>> will go to HS mode before the receiver is started, and the receiver will
>>> never be able to synchronized. That's why the CSI-2 receiver resume
>>> handler needs to restart streaming by calling the sensor .s_stream()
>>> operation. Same thing at suspend time. Most sensors will thus not need
>>> system suspend/resume handlers.
>>>
>>> This means that the CSI-2 receiver needs to be suspended before the
>>> sensor, and resumed after. You can achieve this with the device link
>>> API, calling device_link_add(csi2_dev, sensor_dev, DL_FLAG_STATELESS)
>>> in the CSI-2 receiver driver.
>>
>> Let me rephrase your comment just to make sure I got everything right.
>>
>> On system suspend, the CSI-2 receiver driver should already have
>> stopped the stream for me, so this is irrelevant.
>>
>> On resume, if by any chance I try to resume streaming before the CSI2
>> receiver is resuming things will go terribly wrong.
>>
>> So this means I should trust the CSI2 driver to have performed
>> device_link_add on its side, and can delete the vgxy61 resume and
>> suspend ops.
> 
> Correct.
> 
>> I have trouble understanding why my use case differs from the imx219
>> for instance, that afaik does the same thing.
> 
> Your use case doesn't differ, but we have lots of sensors drivers that
> have been developed over time while our understanding of power
> management evolved, and older drivers haven't been updated to the latest
> best practices. This results in lots of cargo-cult for new drivers too,
> it's not a great situation.
> 

I understand, thanks a lot for your explanations.

>>>>>> +
>>>>>> +	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).
>>>>
>>>> Your flashing LED example is a very good concern.
>>>>
>>>> The idea behind this is to ease the user life as he does not have to
>>>> know what model he is using.
>>>
>>> I agree with you, I've implemented the same in sensor drivers. One
>>> option may be to have a generic compatible string that would lead to
>>> model detection at probe time, and specific compatible strings that
>>> would skip it and avoid powering up the sensor. As long as we consider
>>> the requirements on the DT side and make sure to have a way forward
>>> without breaking backward compatibility, this can also be implemented
>>> later.
>>
>> This is a very good idea.
>>
>>>> For example we have several boards with either the VG5761 or VG5661
>>>> and are interchangeable without changing the device tree, which is
>>>> also better for us as it shrinks the device tree number to maintain.
>>>>
>>>> Also as a company we treat them the same way, hence the same driver.
>>>> I'm not hardly commited to keep this, but it indeed makes my life easier.
>>>>
>>>>>> +		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.
>>>>
>>>> I was not aware of autosuspend. Thanks a lot, I will look into this.
>>>>
>>>>>> +
>>>>>> +	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");
> 



[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