Hi Dave, Thank you for your answers. On 11/03/2022 12:57, Dave Stevenson wrote: > Hi Benjamin > > On Fri, 11 Mar 2022 at 10:56, Benjamin Mugnier > <benjamin.mugnier@xxxxxxxxxxx> wrote: >> >> Hi Dave, >> >> Thanks for your review. Here are my comments and some questions. >> >> On 10/03/2022 16:21, Dave Stevenson wrote: >>> Hi Benjamin and Kieran >>> >>> On Thu, 10 Mar 2022 at 13:52, Kieran Bingham >>> <kieran.bingham@xxxxxxxxxxxxxxxx> wrote: >>>> >>>> Hi Benjamin, >>>> >>>> Thank you for the patch - >>>> >>>> Quoting Benjamin Mugnier (2022-03-10 13:32:55) >>>>> 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 >>>> >>>> I haven't reviewed the driver directly yet, but I have a script which >>>> looks for key requirements for libcamera. >>>> (https://git.linuxtv.org/libcamera.git/tree/Documentation/sensor_driver_requirements.rst) >>>> >>>> >>>> Media Controller Support: >>>> - V4L2_SUBDEV_FL_HAS_DEVNODE : found >>>> - MEDIA_ENT_F_CAM_SENSOR : found >>>> >>>> Mandatory Controls: >>>> - V4L2_CID_EXPOSURE : found >>>> - V4L2_CID_HBLANK : -------- >>>> - V4L2_CID_VBLANK : -------- >>>> - V4L2_CID_PIXEL_RATE : found >>>> >>>> Selection Controls (will become mandatory): >>>> - V4L2_SEL_TGT_CROP_DEFAULT : -------- >>>> - V4L2_SEL_TGT_CROP : -------- >>>> - V4L2_SEL_TGT_CROP_BOUNDS : -------- >>>> - .get_selection : -------- >>>> >>>> Optional Controls: >>>> - V4L2_CID_TEST_PATTERN : found >>>> - V4L2_CID_GAIN : -------- >>>> - V4L2_CID_ANALOGUE_GAIN : found >>>> - V4L2_CID_CAMERA_SENSOR_ROTATION : -------- >>>> - V4L2_CID_CAMERA_ORIENTATION : -------- >>>> >>>> >>>> The key missing pieces are HBLANK/VBLANK and the .get_selection API. Is >>>> it easy/feasible to add these? >>> >>> It's documented that HBLANK/VBLANK are the correct parameters to use >>> for raw image sensors [1]. >>> >>> It looks like register DEVICE_FRAME_LENGTH is mode->height + >>> V4L2_CID_VBLANK, and DEVICE_LINE_LENGTH is mode->width + >>> V4L2_CID_HBLANK (appears to be treated as read only) >>> Most other sensors will adjust the max value of V4L2_CID_EXPOSURE >>> based on the frame length. >>> >>> [1] https://www.kernel.org/doc/html/latest/driver-api/media/camera-sensor.html#frame-interval-configuration >>> >>> I've only done a quick review on the rest. >>> >>>> -- >>>> Regards >>>> >>>> Kieran >>>> >>>> >>>>> >>>>> Signed-off-by: Benjamin Mugnier <benjamin.mugnier@xxxxxxxxxxx> >>>>> --- >>>>> drivers/media/i2c/Kconfig | 11 + >>>>> drivers/media/i2c/Makefile | 1 + >>>>> drivers/media/i2c/st-vgxy61.c | 1919 +++++++++++++++++++++++++++++++++ >>>>> 3 files changed, 1931 insertions(+) >>>>> create mode 100644 drivers/media/i2c/st-vgxy61.c >>>>> >>>>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig >>>>> index fe66093b8849..e6e9c1f19c09 100644 >>>>> --- a/drivers/media/i2c/Kconfig >>>>> +++ b/drivers/media/i2c/Kconfig >>>>> @@ -1454,6 +1454,17 @@ config VIDEO_S5C73M3 >>>>> This is a V4L2 sensor driver for Samsung S5C73M3 >>>>> 8 Mpixel camera. >>>>> >>>>> +config VIDEO_ST_VGXY61 >>>>> + tristate "ST VGXY61 sensor support" >>>>> + depends on OF >>>>> + depends on GPIOLIB && VIDEO_V4L2 && 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. >>>>> + >>>>> endmenu >>>>> >>>>> menu "Lens drivers" >>>>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile >>>>> index f6b80ef6f41d..0190b5edfa24 100644 >>>>> --- a/drivers/media/i2c/Makefile >>>>> +++ b/drivers/media/i2c/Makefile >>>>> @@ -138,4 +138,5 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o >>>>> obj-$(CONFIG_VIDEO_RDACM20) += rdacm20.o >>>>> obj-$(CONFIG_VIDEO_RDACM21) += rdacm21.o >>>>> obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o >>>>> +obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o >>>>> obj-$(CONFIG_SDR_MAX2175) += max2175.o >>>>> diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c >>>>> new file mode 100644 >>>>> index 000000000000..d3d816189f86 >>>>> --- /dev/null >>>>> +++ b/drivers/media/i2c/st-vgxy61.c >>>>> @@ -0,0 +1,1919 @@ >>>>> +// 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/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 >>>>> + >>>>> +#define DEVICE_MODEL_ID_REG 0x0000 >>>>> +#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 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_STROBE_LONG_START_DELAY 0x0528 >>>>> +#define DEVICE_STROBE_LONG_END_DELAY 0x052a >>>>> +#define DEVICE_STROBE_SHORT_START_DELAY 0x052c >>>>> +#define DEVICE_STROBE_SHORT_END_DELAY 0x052e >>>>> +#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_BASE | 0x1004) >>>>> +#define V4L2_CID_GPIOS_STROBE_LONG_START_DELAY (V4L2_CID_USER_BASE | 0x1019) >>>>> +#define V4L2_CID_GPIOS_STROBE_LONG_END_DELAY (V4L2_CID_USER_BASE | 0x101a) >>>>> +#define V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY (V4L2_CID_USER_BASE | 0x101b) >>>>> +#define V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY (V4L2_CID_USER_BASE | 0x101c) >>>>> +#define V4L2_CID_TEMPERATURE (V4L2_CID_USER_BASE | 0x1020) >>> >>> The strobe and temperature controls are equally valid for other >>> sensors, so IMHO ought to be added as standard controls. >>> >> >> Agreed for the temperature, I will add it as a standard control in v2. >> As for the strobing I am not sure, these are controls to set the strobing delay for long exposure and short exposure (if in HDR mode), not to enable or disable the strobbing. I could not find anything like this in the codebase so I guess this is pretty unique to this sensor? > > I'm not sure I follow what the controls are doing then. "GPIOs LEDs > strobing" and the name V4L2_CID_GPIOS_STROBE_* implies to me that it > is an output GPIO that is asserted during the short/long exposure > period with an optional delay. > Many sensors have a GPIO that is asserted during the exposure period, > often with an optional delay. Whilst they don't necessarily support > HDR and therefore have two outputs, there are going to be a number > with commonality. > External GPIO control is another area that seems to be under-served > within V4L2. A bit of discussion to sort a generic set of controls > would be useful. > > If they are custom to your sensor, then you really want to define a > block of CID values for your sensor in > https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L152 > to avoid something else using the same numbers. > > Unless they are totally critical to the function of this sensor, you > could drop them for this initial driver, and then the discussion about > the correct way to support them can come later. > Yes this is linked to the strobe GPIO that is asserted during the exposure period, but not directly: These are I2C registers where you write the timings you want the GPIO to strobe, and the sensor CPU is responsible to strobe fot this period of time. I don't hanldes the GPIOS directly in the driver. There is another register to tell what GPIOS you want to strobe with but as they are dedicated to strobing I did not make them configurable. This makes me think that the "GPIO" naming scheme was maybe not a really good naming. Tell me if you need additional info. You are right this is not critical for the initial driver, and adds more complexity than it solves problems. So I will remove these. Thanks for the block of CID values advice, I will reserve some space as I need it for the HDR control. >>>>> + >>>>> +#define DEVICE_FWPATCH_REVISION_MAJOR 2 >>>>> +#define DEVICE_FWPATCH_REVISION_MINOR 0 >>>>> +#define DEVICE_FWPATCH_REVISION_MICRO 5 >>>>> + >>>>> +#define WRITE_MULTIPLE_CHUNK_MAX 16 >>>>> +#define NB_GPIOS 4 >>>>> +#define NB_POLARITIES 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 >>>>> +}; >>>>> + >>>>> +#define VGXY61_NUM_SUPPLIES ARRAY_SIZE(vgxy61_supply_name) >>>>> + >>>>> +/* Macro to convert index to 8.8 fixed point gain */ >>>>> +#define I2FP(i) ((u32)(8192.0 / (32 - (i)))) >>>>> +/* Array of possibles analog gains in 8.8 fixed point */ >>>>> +static const u16 analog_gains[] = { >>>>> + I2FP(0), I2FP(1), I2FP(2), I2FP(3), I2FP(4), I2FP(5), I2FP(6), I2FP(7), I2FP(8), I2FP(9), >>>>> + I2FP(10), I2FP(11), I2FP(12), I2FP(13), I2FP(14), I2FP(15), I2FP(16), >>>>> +}; >>>>> + >>>>> +enum bin_mode { >>>>> + BIN_MODE_NORMAL, >>>>> + BIN_MODE_DIGITAL_X2, >>>>> + BIN_MODE_DIGITAL_X4, >>>>> + BIN_MODE_ANALOG_SUB_X2, >>>>> + BIN_MODE_ANALOG_SUB_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; >>>>> +}; >>>>> + >>>>> +static const u32 vgxy61_supported_codes[] = { >>>>> + MEDIA_BUS_FMT_SGBRG8_1X8, >>>>> + MEDIA_BUS_FMT_SGBRG10_1X10, >>>>> + MEDIA_BUS_FMT_SGBRG12_1X12, >>>>> + MEDIA_BUS_FMT_SGBRG14_1X14, >>>>> + MEDIA_BUS_FMT_SGBRG16_1X16 >>>>> +}; >>>>> + >>>>> +const int vgx761_sensor_frame_rates[] = {75, 60, 30, 15, 10, 5, 2}; >>>>> +const int vgx661_sensor_frame_rates[] = {60, 30, 15, 10, 5, 2}; >>>>> + >>>>> +static const struct vgxy61_mode_info vgx661_mode_data[] = { >>>>> + {1464, 1104, BIN_MODE_NORMAL}, >>>>> + {1280, 720, BIN_MODE_NORMAL}, >>>>> + { 640, 480, BIN_MODE_DIGITAL_X2}, >>>>> + { 320, 240, BIN_MODE_DIGITAL_X4}, >>>>> +}; >>>>> + >>>>> +static const struct vgxy61_mode_info vgx761_mode_data[] = { >>>>> + {1944, 1204, BIN_MODE_NORMAL}, >>>>> + {1920, 1080, BIN_MODE_NORMAL}, >>>>> + {1280, 720, BIN_MODE_NORMAL}, >>>>> + { 640, 480, BIN_MODE_DIGITAL_X2}, >>>>> + { 320, 240, BIN_MODE_DIGITAL_X4}, >>>>> +}; >>>>> + >>>>> +struct gpios_ctrls { >>>>> + struct v4l2_ctrl *long_start; >>>>> + struct v4l2_ctrl *long_end; >>>>> + struct v4l2_ctrl *short_start; >>>>> + struct v4l2_ctrl *short_end; >>>>> +}; >>>>> + >>>>> +struct vgxy61_ctrls { >>>>> + struct v4l2_ctrl_handler handler; >>>>> + struct v4l2_ctrl *exposure; >>>>> + struct v4l2_ctrl *analog_gain; >>>>> + struct v4l2_ctrl *digital_gain; >>>>> + struct gpios_ctrls gpios; >>>>> + struct v4l2_ctrl *vflip; >>>>> + struct v4l2_ctrl *hflip; >>>>> + struct v4l2_ctrl *patgen; >>>>> + struct v4l2_ctrl *hdr; >>>>> + struct v4l2_ctrl *pixel_rate; >>>>> + struct v4l2_ctrl *link_freq; >>> >>> No need to store analog_gain, digital_gain, vflip, hflip, patgen, hdr, >>> or link_freq as they are never programmatically altered. The s_ctrl >>> handler is given the struct v4l2_ctrl, and where you need it again >>> you've copied the value into the main struct vgxy61_dev. >>> >> >> Definetly. I just need to keep the pixel_rate ctrl to change its value in the set_fmt function. >> I will remove all the others, and put the pixel_rate control directly in the vgxy61 structure. >> >>>>> + struct v4l2_ctrl *temp; >>>>> +}; >>>>> + >>>>> +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 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 vgxy61_ctrls ctrls; >>>>> + 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; >>>>> + const int *sensor_rates; >>>>> + int sensor_rates_nb; >>>>> + struct v4l2_fract frame_interval; >>>>> + bool hflip; >>>>> + bool vflip; >>>>> + enum hdr hdr; >>>>> + int expo_long; >>>>> + int expo_short; >>>>> +}; >>>>> + >>>>> +static u8 get_bpp_by_code(__u32 code) >>>>> +{ >>>>> + switch (code) { >>>>> + case MEDIA_BUS_FMT_SGBRG8_1X8: >>>>> + return 8; >>>>> + case MEDIA_BUS_FMT_SGBRG10_1X10: >>>>> + return 10; >>>>> + case MEDIA_BUS_FMT_SGBRG12_1X12: >>>>> + return 12; >>>>> + case MEDIA_BUS_FMT_SGBRG14_1X14: >>>>> + return 14; >>>>> + case MEDIA_BUS_FMT_SGBRG16_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_SGBRG8_1X8: >>>>> + return MIPI_CSI2_DT_RAW8; >>>>> + case MEDIA_BUS_FMT_SGBRG10_1X10: >>>>> + return MIPI_CSI2_DT_RAW10; >>>>> + case MEDIA_BUS_FMT_SGBRG12_1X12: >>>>> + return MIPI_CSI2_DT_RAW12; >>>>> + case MEDIA_BUS_FMT_SGBRG14_1X14: >>>>> + return MIPI_CSI2_DT_RAW14; >>>>> + case MEDIA_BUS_FMT_SGBRG16_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; >>>>> + } >>>>> +} >>>>> + >>>>> +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, ctrls.handler)->sd; >>>>> +} >>>>> + >>>>> +static int get_chunk_size(struct vgxy61_dev *sensor) >>>>> +{ >>>>> + struct i2c_adapter *adapter = sensor->i2c_client->adapter; >>>>> + int max_write_len = 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, 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[WRITE_MULTIPLE_CHUNK_MAX + 2]; >>>>> + int i; >>>>> + int ret; >>>>> + >>>>> + if (len > 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)); >>>>> +} >>>>> + >>>>> +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u16 reg, u8 poll_val) >>>>> +{ >>>>> + const int loop_delay_ms = 10; >>>>> + const int timeout_ms = 500; >>>>> + 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) >>>>> +{ >>>>> + return vgxy61_poll_reg(sensor, DEVICE_SYSTEM_FSM, state); >>>>> +} >>>>> + >>>>> +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 = 1 << sensor->current_mode->bin_mode; >>>>> + 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; >>>>> + >>>>> + 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) >>>>> +{ >>>>> + u16 frame_length; >>>>> + >>>>> + frame_length = sensor->pclk / (sensor->line_length * sensor->frame_interval.denominator); >>>>> + >>>>> + return vgxy61_write_reg16(sensor, DEVICE_FRAME_LENGTH, 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); >>>>> +} >>>>> + >>>>> +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 */ >>>>> + do { >>>>> + if (mode->width <= fmt->width && mode->height <= fmt->height) >>>>> + break; >>>>> + } while ((++mode)->width); >>>>> + if (!mode->width) >>>>> + mode--; >>> >>> Use v4l2_find_nearest_size? >>> >> >> Sure. >> >>>>> + >>>>> + *new_mode = mode; >>>>> + fmt->code = vgxy61_supported_codes[index]; >>>>> + fmt->width = mode->width; >>>>> + fmt->height = mode->height; >>>>> + fmt->colorspace = V4L2_COLORSPACE_SRGB; >>> >>> Is it? Most Bayer sensors are V4L2_COLORSPACE_RAW. >>> What about ycbcr_enc, quantization, and xfer_func? >>> >> >> Yes it should be RAW, but iirc I had some issue with the RAW colorspace. I will test it again in order to change it. >> I will add missing fields too. >> >> >>>>> + fmt->field = V4L2_FIELD_NONE; >>>>> + >>>>> + return 0; >>>>> +} >>>>> + >>>>> +static int vgxy61_stream_enable(struct vgxy61_dev *sensor) >>>>> +{ >>>>> + int center_x = sensor->sensor_width / 2; >>>>> + int center_y = sensor->sensor_height / 2; >>>>> + int scale = 1 << sensor->current_mode->bin_mode; >>>>> + int width = sensor->current_mode->width * scale; >>>>> + int height = sensor->current_mode->height * scale; >>>>> + int ret; >>>>> + >>>>> + ret = vgxy61_check_bw(sensor); >>>>> + if (ret) >>>>> + return ret; >>>>> + >>>>> + /* Configure sensor */ >>>>> + ret = vgxy61_write_reg(sensor, DEVICE_FORMAT_CTRL, get_bpp_by_code(sensor->fmt.code)); >>>>> + if (ret) >>>>> + return ret; >>>>> + ret = vgxy61_write_reg(sensor, DEVICE_OIF_ROI0_CTRL, >>>>> + get_data_type_by_code(sensor->fmt.code)); >>>>> + if (ret) >>>>> + return ret; >>>>> + >>>>> + ret = vgxy61_write_reg(sensor, DEVICE_READOUT_CTRL, sensor->current_mode->bin_mode); >>>>> + if (ret) >>>>> + return ret; >>>>> + ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_H, center_x - width / 2); >>>>> + if (ret) >>>>> + return ret; >>>>> + ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_H, center_x + width / 2 - 1); >>>>> + if (ret) >>>>> + return ret; >>>>> + ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_START_V, center_y - height / 2); >>>>> + if (ret) >>>>> + return ret; >>>>> + ret = vgxy61_write_reg16(sensor, DEVICE_ROI0_END_V, center_y + height / 2 - 1); >>>>> + if (ret) >>>>> + return ret; >>> >>> Use the selection API to allow the crop region to be configured? >>> Avoids only supporting centre cropping. >>> >> >> Yes, as we already mentionned in the other thread this is required by libcamera anyway. Two birds one stone. > > There are a couple of bits to the selection API. > > libcamera's mainly after it for read-only information on the crop > applied by each mode, and the total sensor size. This allows things > like lens shading tables to be appropriately cropped to match the > sensor mode. > > There is also a s_selection call, so that could allow totally > free-form cropping of anywhere from the sensor. This isn't a > requirement. I was only observing that you were computing the start > and end values, therefore it seemed like a potential extension. > Many other sensor drivers have a small table of the registers that are > different for each mode rather than computing the registers. > Thank you for the explaination, I will look into this. >>>>> + >>>>> + ret = set_frame_rate(sensor); >>>>> + if (ret) >>>>> + return ret; >>>>> + >>>>> + ret = apply_exposure(sensor); >>>>> + if (ret) >>>>> + return ret; >>>>> + >>>>> + /* Start streaming */ >>>>> + ret = vgxy61_write_reg(sensor, DEVICE_STREAMING, REQ_START_STREAMING); >>>>> + if (ret) >>>>> + return ret; >>>>> + >>>>> + ret = vgxy61_poll_reg(sensor, DEVICE_STREAMING, REQ_NO_REQUEST); >>>>> + if (ret) >>>>> + return ret; >>>>> + >>>>> + ret = vgxy61_wait_state(sensor, STREAMING); >>>>> + if (ret) >>>>> + return ret; >>>>> + >>>>> + return 0; >>>>> +} >>>>> + >>>>> +static int vgxy61_stream_disable(struct vgxy61_dev *sensor) >>>>> +{ >>>>> + 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); >>>>> + if (ret) >>>>> + return ret; >>>>> + >>>>> + ret = vgxy61_wait_state(sensor, SW_STBY); >>>>> + if (ret) >>>>> + return ret; >>>>> + >>>>> + 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; >>>>> + >>>>> + 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_g_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi) >>>>> +{ >>>>> + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); >>>>> + >>>>> + mutex_lock(&sensor->lock); >>>>> + fi->interval = sensor->frame_interval; >>>>> + mutex_unlock(&sensor->lock); >>>>> + >>>>> + return 0; >>>>> +} >>>>> + >>>>> +static int vgxy61_s_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi) >>>>> +{ >>>>> + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); >>>>> + struct i2c_client *client = sensor->i2c_client; >>>>> + u64 req_int, err, min_err = ~0ULL; >>>>> + u64 test_int; >>>>> + int i = 0; >>>>> + int ret; >>>>> + >>>>> + if (fi->interval.denominator == 0) >>>>> + return -EINVAL; >>>>> + >>>>> + mutex_lock(&sensor->lock); >>>>> + >>>>> + if (sensor->streaming) { >>>>> + ret = -EBUSY; >>>>> + goto out; >>>>> + } >>>>> + >>>>> + dev_dbg(&client->dev, "%s request %d/%d\n", __func__, >>>>> + fi->interval.numerator, fi->interval.denominator); >>>>> + /* Find nearest period */ >>>>> + req_int = div64_u64((u64)(fi->interval.numerator * 10000), fi->interval.denominator); >>>>> + for (i = 0; i < sensor->sensor_rates_nb; i++) { >>>>> + test_int = div64_u64((u64)10000, sensor->sensor_rates[i]); >>>>> + err = abs(test_int - req_int); >>>>> + if (err < min_err) { >>>>> + fi->interval.numerator = 1; >>>>> + fi->interval.denominator = sensor->sensor_rates[i]; >>>>> + min_err = err; >>>>> + } >>>>> + } >>>>> + sensor->frame_interval = fi->interval; >>>>> + dev_dbg(&client->dev, "%s set %d/%d\n", __func__, >>>>> + fi->interval.numerator, fi->interval.denominator); >>>>> + >>>>> + ret = 0; >>>>> +out: >>>>> + 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); >>>>> + >>>>> + 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); >>>>> + else >>>>> + fmt = &sensor->fmt; >>>>> + *fmt = format->format; >>>>> + sensor->current_mode = new_mode; >>>>> + /* Update pixel rate control to reflect new mode */ >>>>> + __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, get_pixel_rate(sensor)); >>>>> + >>>>> +out: >>>>> + mutex_unlock(&sensor->lock); >>>>> + >>>>> + return ret; >>>>> +} >>>>> + >>>>> +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_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, >>>>> + struct v4l2_subdev_frame_interval_enum *fie) >>>>> +{ >>>>> + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); >>>>> + const struct vgxy61_mode_info *mode = sensor->sensor_modes; >>>>> + >>>>> + if (fie->index >= sensor->sensor_rates_nb) >>>>> + return -EINVAL; >>>>> + do { >>>>> + if (mode->width == fie->width && mode->height == fie->height) >>>>> + break; >>>>> + } while ((++mode)->width); >>>>> + if (!mode->width) >>>>> + return -EINVAL; >>>>> + >>>>> + fie->interval.numerator = 1; >>>>> + fie->interval.denominator = sensor->sensor_rates[fie->index]; >>>>> + >>>>> + return 0; >>>>> +} >>>>> + >>>>> +static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target) >>>>> +{ >>>>> + struct i2c_client *client = sensor->i2c_client; >>>>> + unsigned int idx; >>>>> + int ret; >>>>> + >>>>> + /* Find smallest analog gains which is above or equal to target gain */ >>>>> + for (idx = 0; idx < ARRAY_SIZE(analog_gains); idx++) { >>>>> + if (analog_gains[idx] >= target) >>>>> + break; >>>>> + } >>> >>> Why? The units of V4L2_CID_ANALOGUE_GAIN are not specified, so more >>> typically it is a raw register write. >>> >>> >> >> The output analog gain is 32/(32-idx). This code maps a gain to its matching register value. >> The goal is to allow setting the control using a "real world gain" value. I could perform a raw register write ranging from 1 to 16. It would clean the code but would not be representative of the real gain. >> Maybe I am just overthinking it though. > > Most other sensors make it a raw register value, and you get a > userspace conversion function, and they do vary between sensors. > > In the Raspberry Pi libcamera IPA, you get the cam_helper functions to > provide the conversions to/from gain code > eg https://git.linuxtv.org/libcamera.git/tree/src/ipa/raspberrypi/cam_helper_imx219.cpp#n67 > https://git.linuxtv.org/libcamera.git/tree/src/ipa/raspberrypi/cam_helper_ov5647.cpp#n45 > That is perfect for this use case. I will modify to add a raw value, and add the conversion in my sensor's cam_helper on libcamera side. >>>>> + /* Cap to maximum gain if no matching gain found */ >>>>> + if (idx == ARRAY_SIZE(analog_gains)) >>>>> + idx--; >>>>> + >>>>> + /* Apply gain */ >>>>> + ret = vgxy61_write_reg(sensor, DEVICE_ANALOG_GAIN, idx); >>>>> + if (ret) >>>>> + return ret; >>>>> + >>>>> + dev_dbg(&client->dev, "Target analog gain: 0x%04x\n", target); >>>>> + dev_dbg(&client->dev, " Set analog gain: 0x%04x\n", analog_gains[idx]); >>>>> + >>>>> + 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; >>>>> + 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}; >>>>> + u16 reg; >>>>> + int ret; >>>>> + >>>>> + ret = vgxy61_read_reg16(sensor, DEVICE_SIGNALS_CTRL, ®); >>>>> + 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 < 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 find_max_long_exposure(struct vgxy61_dev *sensor, int frame_length, int short_expo_ratio) >>>>> +{ >>>>> + int first_rot_max_expo; >>>>> + int second_rot_max_expo; >>>>> + >>>>> + /* >>>>> + * Apply first rule of thumb: >>>>> + * frame_length < short_line_nb + sensor->sensor_height + sensor->rot_term >>>>> + * with short_line_nb = long_line_nb / short_expo_ratio >>>>> + */ >>>>> + first_rot_max_expo = ((frame_length - sensor->sensor_height - sensor->rot_term) * >>>>> + short_expo_ratio) - 1; >>>>> + >>>>> + /* >>>>> + * Apply second rule of thumb >>>>> + * frame_length < short_line_nb + long_line_nb + VGXY61_EXPOS_ROT_TERM >>>>> + * with short_line_nb = long_line_nb / short_expo_ratio >>>>> + */ >>>>> + second_rot_max_expo = (((frame_length - VGXY61_EXPOS_ROT_TERM) * short_expo_ratio) / >>>>> + (short_expo_ratio + 1)) - 1; >>>>> + >>>>> + /* Take the minimum of both rules */ >>>>> + return min(first_rot_max_expo, second_rot_max_expo); >>>>> +} >>>>> + >>>>> +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, int long_expo_line_nb, enum hdr hdr, >>>>> + bool clamp) >>>>> +{ >>>>> + struct i2c_client *client = sensor->i2c_client; >>>>> + int max_long_expo; >>>>> + int max_short_expo = 0; >>>>> + int short_expo_line_nb = 0; >>>>> + /* We use a constant ratio of 10 for linear HDR mode */ >>>>> + int hdr_linear_ratio = 10; >>>>> + u16 frame_length; >>>>> + int ret; >>>>> + >>>>> + ret = vgxy61_read_reg16(sensor, DEVICE_FRAME_LENGTH, &frame_length); >>>>> + if (ret) >>>>> + return ret; >>>>> + >>>>> + /* Long and short integration times must not be less than 10 lines */ >>>>> + long_expo_line_nb = max(10, long_expo_line_nb); >>>>> + long_expo_line_nb = min_t(int, frame_length, long_expo_line_nb); >>>>> + >>>>> + /* Compute short exposure according to hdr mode and long exposure */ >>>>> + switch (hdr) { >>>>> + case HDR_LINEAR: >>>>> + max_long_expo = find_max_long_exposure(sensor, frame_length, hdr_linear_ratio); >>>>> + max_short_expo = (max_long_expo + (hdr_linear_ratio / 2)) / hdr_linear_ratio; >>>>> + short_expo_line_nb = (long_expo_line_nb + (hdr_linear_ratio / 2)) / >>>>> + hdr_linear_ratio; >>>>> + break; >>>>> + case HDR_SUB: >>>>> + max_long_expo = find_max_long_exposure(sensor, frame_length, 1); >>>>> + max_short_expo = max_long_expo; >>>>> + short_expo_line_nb = long_expo_line_nb; >>>>> + break; >>>>> + case NO_HDR: >>>>> + /* >>>>> + * As short expo is 0 here, only the second rule of thumb applies, see >>>>> + * find_max_long_exposure for more >>>>> + */ >>>>> + max_long_expo = frame_length - VGXY61_EXPOS_ROT_TERM; >>>>> + break; >>>>> + default: >>>>> + /* Should never happen */ >>>>> + WARN_ON(true); >>>>> + break; >>>>> + } >>>>> + >>>>> + if (long_expo_line_nb > max_long_expo) { >>>>> + if (!clamp) { >>>>> + dev_err(&client->dev, "Exposure %d too high (max for this hdr mode %d)\n", >>>>> + long_expo_line_nb, max_long_expo); >>>>> + return -EINVAL; >>>>> + } >>>>> + dev_warn(&client->dev, "Exposure %d too high for this hdr mode, clamping to %d\n", >>>>> + long_expo_line_nb, max_long_expo); >>>>> + long_expo_line_nb = max_long_expo; >>>>> + short_expo_line_nb = max_short_expo; >>>>> + } >>>>> + >>>>> + dev_dbg(&client->dev, "frame_length %d, long_expo_line_nb %d, short_expo_line_nb %d", >>>>> + frame_length, long_expo_line_nb, short_expo_line_nb); >>>>> + >>>>> + /* Apply exposure */ >>>>> + sensor->expo_long = long_expo_line_nb; >>>>> + sensor->expo_short = short_expo_line_nb; >>>>> + >>>>> + if (sensor->streaming) >>>>> + return apply_exposure(sensor); >>>>> + return 0; >>>>> +} >>>>> + >>>>> +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, u32 index) >>>>> +{ >>>>> + const u8 index2val[] = {0x1, 0x4, 0xa}; >>>>> + int ret; >>>>> + >>>>> + /* >>>>> + * Short exposure changes 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_exposure(sensor, sensor->expo_long, index, true); >>>>> + 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_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); >>>>> + if (ret) >>>>> + return ret; >>>>> + ret = vgxy61_poll_reg(sensor, DEVICE_STBY, STBY_NO_REQ); >>>>> + 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, false); >>>>> + 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); >>>>> + break; >>>>> + case V4L2_CID_GPIOS_STROBE_LONG_START_DELAY: >>>>> + ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_START_DELAY, ctrl->val); >>>>> + break; >>>>> + case V4L2_CID_GPIOS_STROBE_LONG_END_DELAY: >>>>> + ret = vgxy61_write_reg(sensor, DEVICE_STROBE_LONG_END_DELAY, ctrl->val); >>>>> + break; >>>>> + case V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY: >>>>> + ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_START_DELAY, ctrl->val); >>>>> + break; >>>>> + case V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY: >>>>> + ret = vgxy61_write_reg(sensor, DEVICE_STROBE_SHORT_END_DELAY, ctrl->val); >>>>> + 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 temperature; >>>>> + int ret; >>>>> + >>>>> + switch (ctrl->id) { >>>>> + case V4L2_CID_TEMPERATURE: >>>>> + ret = vgxy61_get_temp(sensor, &temperature); >>>>> + if (ret) >>>>> + break; >>>>> + ret = __v4l2_ctrl_s_ctrl(ctrl, temperature); >>> >>> Do you need to call __v4l2_ctrl_s_ctrl instead of just updating >>> ctrl->val? There is no s_ctrl handler for the control as you've >>> declared it as read only. >>> >> >> Sure. >> >>>>> + 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 const struct v4l2_ctrl_config vgxy61_strobe_long_start_delay = { >>>>> + .ops = &vgxy61_ctrl_ops, >>>>> + .id = V4L2_CID_GPIOS_STROBE_LONG_START_DELAY, >>>>> + .name = "Long strobe mode start delay in lines", >>>>> + .type = V4L2_CTRL_TYPE_INTEGER, >>>>> + .min = -128, >>>>> + .max = 127, >>>>> + .step = 1, >>>>> + .def = 0, >>>>> + .flags = 0, >>>>> +}; >>>>> + >>>>> +static const struct v4l2_ctrl_config vgxy61_strobe_long_end_delay = { >>>>> + .ops = &vgxy61_ctrl_ops, >>>>> + .id = V4L2_CID_GPIOS_STROBE_LONG_END_DELAY, >>>>> + .name = "Long strobe mode end delay in lines", >>>>> + .type = V4L2_CTRL_TYPE_INTEGER, >>>>> + .min = -128, >>>>> + .max = 127, >>>>> + .step = 1, >>>>> + .def = 0, >>>>> + .flags = 0, >>>>> +}; >>>>> + >>>>> +static const struct v4l2_ctrl_config vgxy61_strobe_short_start_delay = { >>>>> + .ops = &vgxy61_ctrl_ops, >>>>> + .id = V4L2_CID_GPIOS_STROBE_SHORT_START_DELAY, >>>>> + .name = "Short strobe mode start delay in lines", >>>>> + .type = V4L2_CTRL_TYPE_INTEGER, >>>>> + .min = -128, >>>>> + .max = 127, >>>>> + .step = 1, >>>>> + .def = 0, >>>>> + .flags = 0, >>>>> +}; >>>>> + >>>>> +static const struct v4l2_ctrl_config vgxy61_strobe_short_end_delay = { >>>>> + .ops = &vgxy61_ctrl_ops, >>>>> + .id = V4L2_CID_GPIOS_STROBE_SHORT_END_DELAY, >>>>> + .name = "Short strobe mode end delay in lines", >>>>> + .type = V4L2_CTRL_TYPE_INTEGER, >>>>> + .min = -128, >>>>> + .max = 127, >>>>> + .step = 1, >>>>> + .def = 0, >>>>> + .flags = 0, >>>>> +}; >>>>> + >>>>> +static const struct v4l2_ctrl_config vgxy61_temp_ctrl = { >>>>> + .ops = &vgxy61_ctrl_ops, >>>>> + .id = V4L2_CID_TEMPERATURE, >>>>> + .name = "Temperature in °C", >>>>> + .type = V4L2_CTRL_TYPE_INTEGER, >>>>> + .min = -128, >>>>> + .max = 128, >>>>> + .step = 1, >>>>> +}; >>>>> + >>>>> +static int vgxy61_init_controls(struct vgxy61_dev *sensor) >>>>> +{ >>>>> + const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops; >>>>> + struct vgxy61_ctrls *ctrls = &sensor->ctrls; >>>>> + struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler; >>>>> + int ret; >>>>> + >>>>> + v4l2_ctrl_handler_init(hdl, 16); >>>>> + /* We can use our own mutex for the ctrl lock */ >>>>> + hdl->lock = &sensor->lock; >>>>> + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 10, 0xffff, 1, >>>>> + sensor->expo_long); >>>>> + /* This is 8.8 fixed point value */ >>>>> + ctrls->analog_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x3fff, 1, >>>>> + 0x0); >>>>> + /* This is 8.8 fixed point value */ >>>>> + ctrls->digital_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, >>>>> + 0x100); >>>>> + ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, sensor->vflip); >>>>> + ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, sensor->hflip); >>>>> + ctrls->patgen = 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); >>>>> + ctrls->hdr = v4l2_ctrl_new_custom(hdl, &vgxy61_hdr_ctrl, NULL); >>>>> + ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, >>>>> + get_pixel_rate(sensor)); >>>>> + ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; >>>>> + ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, >>>>> + ARRAY_SIZE(link_freq) - 1, 0, link_freq); >>>>> + ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; >>>>> + /* Gpios ctrls */ >>>>> + ctrls->gpios.long_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_start_delay, NULL); >>>>> + ctrls->gpios.long_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_long_end_delay, NULL); >>>>> + ctrls->gpios.short_start = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_start_delay, >>>>> + NULL); >>>>> + ctrls->gpios.short_end = v4l2_ctrl_new_custom(hdl, &vgxy61_strobe_short_end_delay, NULL); >>>>> + /* Temperature ctrl */ >>>>> + ctrls->temp = v4l2_ctrl_new_custom(hdl, &vgxy61_temp_ctrl, NULL); >>>>> + ctrls->temp->flags |= V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY; >>>>> + >>>>> + if (hdl->error) { >>>>> + ret = hdl->error; >>>>> + goto free_ctrls; >>>>> + } >>>>> + >>>>> + sensor->sd.ctrl_handler = hdl; >>>>> + return 0; >>>>> + >>>>> +free_ctrls: >>>>> + v4l2_ctrl_handler_free(hdl); >>>>> + return ret; >>>>> +} >>>>> + >>>>> +static const struct v4l2_subdev_core_ops vgxy61_core_ops = { >>>>> +}; >>>>> + >>>>> +static const struct v4l2_subdev_video_ops vgxy61_video_ops = { >>>>> + .s_stream = vgxy61_s_stream, >>>>> + .g_frame_interval = vgxy61_g_frame_interval, >>>>> + .s_frame_interval = vgxy61_s_frame_interval, >>>>> +}; >>>>> + >>>>> +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, >>>>> + .enum_frame_size = vgxy61_enum_frame_size, >>>>> + .enum_frame_interval = vgxy61_enum_frame_interval, >>>>> +}; >>>>> + >>>>> +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[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0}; >>>>> + u32 phy2log[NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0}; >>>>> + int polarities[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 < 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; >>>>> + /* HDR mode */ >>>>> + ret = vgxy61_update_hdr(sensor, sensor->hdr); >>>>> + 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); >>>>> + 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_info(&client->dev, "Cut1 detected\n"); >>>>> + dev_err(&client->dev, "Cut1 not supported by this driver\n"); >>>>> + return -ENODEV; >>>>> + case 0xB: >>>>> + dev_info(&client->dev, "Cut2 detected\n"); >>>>> + return 0; >>>>> + case 0xC: >>>>> + dev_info(&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); >>>>> + >>>>> + if (id == VG5761_MODEL_ID) { >>>>> + sensor->sensor_width = VGX761_WIDTH; >>>>> + sensor->sensor_height = VGX761_HEIGHT; >>>>> + sensor->sensor_modes = vgx761_mode_data; >>>>> + sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data); >>>>> + sensor->current_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE]; >>>>> + sensor->rot_term = VGX761_SHORT_ROT_TERM; >>>>> + sensor->sensor_rates = vgx761_sensor_frame_rates; >>>>> + sensor->sensor_rates_nb = ARRAY_SIZE(vgx761_sensor_frame_rates); >>>>> + } else if (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; >>>>> + sensor->sensor_rates = vgx661_sensor_frame_rates; >>>>> + sensor->sensor_rates_nb = ARRAY_SIZE(vgx661_sensor_frame_rates); >>>>> + } >>>>> + >>>>> + ret = vgxy61_wait_state(sensor, SW_STBY); >>>>> + 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; >>>>> +} >>>>> + >>>>> +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_SGBRG8_1X8; >>>>> + sensor->fmt.field = V4L2_FIELD_NONE; >>>>> + sensor->fmt.colorspace = V4L2_COLORSPACE_SRGB; >>> >>> ycbcr_enc, quantization, and xfer_func too. >>> >> >> Ok. >> >>>>> + 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; >>>>> + >>>>> + 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, "xclk"); >>>>> + 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 < 6000000 || sensor->clk_freq > 27000000) { >>>>> + dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %d Hz\n", >>>>> + sensor->clk_freq); >>>>> + 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; >>>>> + >>>>> + ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); >>>>> + if (ret) { >>>>> + dev_err(&client->dev, "pads init failed %d\n", ret); >>>>> + return ret; >>>>> + } >>>>> + >>>>> + /* Request optional reset pin */ >>>>> + sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); >>>>> + >>>>> + ret = vgxy61_get_regulators(sensor); >>>>> + if (ret) { >>>>> + dev_err(&client->dev, "failed to get regulators %d\n", ret); >>>>> + goto entity_cleanup; >>>>> + } >>>>> + >>>>> + ret = regulator_bulk_enable(VGXY61_NUM_SUPPLIES, sensor->supplies); >>>>> + if (ret) { >>>>> + dev_err(&client->dev, "failed to enable regulators %d\n", ret); >>>>> + goto entity_cleanup; >>>>> + } >>>>> + >>>>> + ret = clk_prepare_enable(sensor->xclk); >>>>> + if (ret) { >>>>> + dev_err(&client->dev, "failed to enable clock %d\n", ret); >>>>> + goto disable_bulk; >>>>> + } >>>>> + >>>>> + mutex_init(&sensor->lock); >>>>> + >>>>> + /* 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); >>>>> + if (ret) { >>>>> + dev_err(&client->dev, "sensor detect failed %d\n", ret); >>>>> + goto disable_clock; >>>>> + } >>>>> + sensor->fmt.width = sensor->current_mode->width; >>>>> + sensor->fmt.height = sensor->current_mode->height; >>>>> + >>>>> + 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; >>>>> + } >>>>> + >>>>> + ret = vgxy61_init_controls(sensor); >>>>> + if (ret) { >>>>> + dev_err(&client->dev, "controls initialization failed %d\n", ret); >>>>> + goto disable_clock; >>>>> + } >>>>> + >>>>> + ret = v4l2_async_register_subdev(&sensor->sd); >>>>> + if (ret) { >>>>> + dev_err(&client->dev, "async subdev register failed %d\n", ret); >>>>> + goto disable_clock; >>>>> + } >>>>> + >>>>> + dev_info(&client->dev, "vgxy61 probe successfully\n"); >>>>> + >>>>> + return 0; >>>>> + >>>>> +disable_clock: >>>>> + clk_disable_unprepare(sensor->xclk); >>>>> +disable_bulk: >>>>> + regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies); >>>>> +entity_cleanup: >>>>> + mutex_destroy(&sensor->lock); >>>>> + media_entity_cleanup(&sensor->sd.entity); >>>>> + 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); >>>>> + clk_disable_unprepare(sensor->xclk); >>>>> + mutex_destroy(&sensor->lock); >>>>> + media_entity_cleanup(&sensor->sd.entity); >>>>> + regulator_bulk_disable(VGXY61_NUM_SUPPLIES, sensor->supplies); >>>>> + >>>>> + return 0; >>>>> +} >>>>> + >>>>> +static const struct of_device_id vgxy61_dt_ids[] = { >>>>> + { .compatible = "st,st-vgxy61" }, >>>>> + { /* sentinel */ } >>>>> +}; >>>>> +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids); >>>>> + >>>>> +static struct i2c_driver vgxy61_i2c_driver = { >>>>> + .driver = { >>>>> + .name = "st-vgxy61", >>>>> + .of_match_table = vgxy61_dt_ids, >>> >>> Support for pm_runtime? Then again the sensor appears to be powered up >>> in probe and never powered down again. >>> >>> Dave >>> >> >> The power management is handled by the internal CPU. When in SW_STBY the consumption is as its lowest. >> Therefore sensor does not provide any power up/down register access, only the optional reset pin you saw in the probe. >> I am not familiar with pm_runtime yet, I think it is not necessary in this case. > > It sounds like a more complex sensor than many. Certainly with that > binary blob to program you don't want to have to send that more often > than necessary. It just looks a little odd requesting all the > regulators and clock, and then just enabling them permanently. > I don't claim to be a pm_runtime expert, just noting that many sensors > make use of it. Agreed. I saw in Documentation/driver-api/media/camera-sensor.rst linked by Sakari in the other thread that all sensors should use pm_runtime as you mentionned. I will look into pm_runtime API and come back in case you troubles. Regards, Benjamin > > Cheers. > Dave > >>>>> + }, >>>>> + .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"); >>>>> -- >>>>> 2.25.1 >>>>>