|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Driver for VGXY61 global shutter sensor family driver | 
|  | * | 
|  | * Copyright (C) 2022 STMicroelectronics SA | 
|  | */ | 
|  |  | 
|  | #include <linux/clk.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/gpio/consumer.h> | 
|  | #include <linux/i2c.h> | 
|  | #include <linux/iopoll.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/pm_runtime.h> | 
|  | #include <linux/regmap.h> | 
|  | #include <linux/regulator/consumer.h> | 
|  | #include <linux/units.h> | 
|  |  | 
|  | #include <asm/unaligned.h> | 
|  |  | 
|  | #include <media/mipi-csi2.h> | 
|  | #include <media/v4l2-async.h> | 
|  | #include <media/v4l2-cci.h> | 
|  | #include <media/v4l2-ctrls.h> | 
|  | #include <media/v4l2-device.h> | 
|  | #include <media/v4l2-event.h> | 
|  | #include <media/v4l2-fwnode.h> | 
|  | #include <media/v4l2-subdev.h> | 
|  |  | 
|  | #define VGXY61_REG_MODEL_ID				CCI_REG16_LE(0x0000) | 
|  | #define VG5661_MODEL_ID					0x5661 | 
|  | #define VG5761_MODEL_ID					0x5761 | 
|  | #define VGXY61_REG_REVISION				CCI_REG16_LE(0x0002) | 
|  | #define VGXY61_REG_FWPATCH_REVISION			CCI_REG16_LE(0x0014) | 
|  | #define VGXY61_REG_FWPATCH_START_ADDR			CCI_REG8(0x2000) | 
|  | #define VGXY61_REG_SYSTEM_FSM				CCI_REG8(0x0020) | 
|  | #define VGXY61_SYSTEM_FSM_SW_STBY			0x03 | 
|  | #define VGXY61_SYSTEM_FSM_STREAMING			0x04 | 
|  | #define VGXY61_REG_NVM					CCI_REG8(0x0023) | 
|  | #define VGXY61_NVM_OK					0x04 | 
|  | #define VGXY61_REG_STBY					CCI_REG8(0x0201) | 
|  | #define VGXY61_STBY_NO_REQ				0 | 
|  | #define VGXY61_STBY_REQ_TMP_READ			BIT(2) | 
|  | #define VGXY61_REG_STREAMING				CCI_REG8(0x0202) | 
|  | #define VGXY61_STREAMING_NO_REQ				0 | 
|  | #define VGXY61_STREAMING_REQ_STOP			BIT(0) | 
|  | #define VGXY61_STREAMING_REQ_START			BIT(1) | 
|  | #define VGXY61_REG_EXT_CLOCK				CCI_REG32_LE(0x0220) | 
|  | #define VGXY61_REG_CLK_PLL_PREDIV			CCI_REG8(0x0224) | 
|  | #define VGXY61_REG_CLK_SYS_PLL_MULT			CCI_REG8(0x0225) | 
|  | #define VGXY61_REG_GPIO_0_CTRL				CCI_REG8(0x0236) | 
|  | #define VGXY61_REG_GPIO_1_CTRL				CCI_REG8(0x0237) | 
|  | #define VGXY61_REG_GPIO_2_CTRL				CCI_REG8(0x0238) | 
|  | #define VGXY61_REG_GPIO_3_CTRL				CCI_REG8(0x0239) | 
|  | #define VGXY61_REG_SIGNALS_POLARITY_CTRL		CCI_REG8(0x023b) | 
|  | #define VGXY61_REG_LINE_LENGTH				CCI_REG16_LE(0x0300) | 
|  | #define VGXY61_REG_ORIENTATION				CCI_REG8(0x0302) | 
|  | #define VGXY61_REG_VT_CTRL				CCI_REG8(0x0304) | 
|  | #define VGXY61_REG_FORMAT_CTRL				CCI_REG8(0x0305) | 
|  | #define VGXY61_REG_OIF_CTRL				CCI_REG16_LE(0x0306) | 
|  | #define VGXY61_REG_OIF_ROI0_CTRL			CCI_REG8(0x030a) | 
|  | #define VGXY61_REG_ROI0_START_H				CCI_REG16_LE(0x0400) | 
|  | #define VGXY61_REG_ROI0_START_V				CCI_REG16_LE(0x0402) | 
|  | #define VGXY61_REG_ROI0_END_H				CCI_REG16_LE(0x0404) | 
|  | #define VGXY61_REG_ROI0_END_V				CCI_REG16_LE(0x0406) | 
|  | #define VGXY61_REG_PATGEN_CTRL				CCI_REG32_LE(0x0440) | 
|  | #define VGXY61_PATGEN_LONG_ENABLE			BIT(16) | 
|  | #define VGXY61_PATGEN_SHORT_ENABLE			BIT(0) | 
|  | #define VGXY61_PATGEN_LONG_TYPE_SHIFT			18 | 
|  | #define VGXY61_PATGEN_SHORT_TYPE_SHIFT			4 | 
|  | #define VGXY61_REG_FRAME_CONTENT_CTRL			CCI_REG8(0x0478) | 
|  | #define VGXY61_REG_COARSE_EXPOSURE_LONG			CCI_REG16_LE(0x0500) | 
|  | #define VGXY61_REG_COARSE_EXPOSURE_SHORT		CCI_REG16_LE(0x0504) | 
|  | #define VGXY61_REG_ANALOG_GAIN				CCI_REG8(0x0508) | 
|  | #define VGXY61_REG_DIGITAL_GAIN_LONG			CCI_REG16_LE(0x050a) | 
|  | #define VGXY61_REG_DIGITAL_GAIN_SHORT			CCI_REG16_LE(0x0512) | 
|  | #define VGXY61_REG_FRAME_LENGTH				CCI_REG16_LE(0x051a) | 
|  | #define VGXY61_REG_SIGNALS_CTRL				CCI_REG16_LE(0x0522) | 
|  | #define VGXY61_SIGNALS_GPIO_ID_SHIFT			4 | 
|  | #define VGXY61_REG_READOUT_CTRL				CCI_REG8(0x0530) | 
|  | #define VGXY61_REG_HDR_CTRL				CCI_REG8(0x0532) | 
|  | #define VGXY61_REG_PATGEN_LONG_DATA_GR			CCI_REG16_LE(0x092c) | 
|  | #define VGXY61_REG_PATGEN_LONG_DATA_R			CCI_REG16_LE(0x092e) | 
|  | #define VGXY61_REG_PATGEN_LONG_DATA_B			CCI_REG16_LE(0x0930) | 
|  | #define VGXY61_REG_PATGEN_LONG_DATA_GB			CCI_REG16_LE(0x0932) | 
|  | #define VGXY61_REG_PATGEN_SHORT_DATA_GR			CCI_REG16_LE(0x0950) | 
|  | #define VGXY61_REG_PATGEN_SHORT_DATA_R			CCI_REG16_LE(0x0952) | 
|  | #define VGXY61_REG_PATGEN_SHORT_DATA_B			CCI_REG16_LE(0x0954) | 
|  | #define VGXY61_REG_PATGEN_SHORT_DATA_GB			CCI_REG16_LE(0x0956) | 
|  | #define VGXY61_REG_BYPASS_CTRL				CCI_REG8(0x0a60) | 
|  |  | 
|  | #define VGX661_WIDTH					1464 | 
|  | #define VGX661_HEIGHT					1104 | 
|  | #define VGX761_WIDTH					1944 | 
|  | #define VGX761_HEIGHT					1204 | 
|  | #define VGX661_DEFAULT_MODE				1 | 
|  | #define VGX761_DEFAULT_MODE				1 | 
|  | #define VGX661_SHORT_ROT_TERM				93 | 
|  | #define VGX761_SHORT_ROT_TERM				90 | 
|  | #define VGXY61_EXPOS_ROT_TERM				66 | 
|  | #define VGXY61_WRITE_MULTIPLE_CHUNK_MAX			16 | 
|  | #define VGXY61_NB_GPIOS					4 | 
|  | #define VGXY61_NB_POLARITIES				5 | 
|  | #define VGXY61_FRAME_LENGTH_DEF				1313 | 
|  | #define VGXY61_MIN_FRAME_LENGTH				1288 | 
|  | #define VGXY61_MIN_EXPOSURE				10 | 
|  | #define VGXY61_HDR_LINEAR_RATIO				10 | 
|  | #define VGXY61_TIMEOUT_MS				500 | 
|  | #define VGXY61_MEDIA_BUS_FMT_DEF			MEDIA_BUS_FMT_Y8_1X8 | 
|  |  | 
|  | #define VGXY61_FWPATCH_REVISION_MAJOR			2 | 
|  | #define VGXY61_FWPATCH_REVISION_MINOR			0 | 
|  | #define VGXY61_FWPATCH_REVISION_MICRO			5 | 
|  |  | 
|  | static const u8 patch_array[] = { | 
|  | 0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45, | 
|  | 0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c, | 
|  | 0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0, | 
|  | 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1, | 
|  | 0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40, | 
|  | 0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0, | 
|  | 0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41, | 
|  | 0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80, | 
|  | 0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80, | 
|  | 0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0, | 
|  | 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c, | 
|  | 0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03, | 
|  | 0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, | 
|  | 0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0, | 
|  | 0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, | 
|  | 0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c, | 
|  | 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d, | 
|  | 0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84, | 
|  | 0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d, | 
|  | 0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02, | 
|  | 0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c, | 
|  | 0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1, | 
|  | 0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, | 
|  | 0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, | 
|  | 0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40, | 
|  | 0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0, | 
|  | 0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1, | 
|  | 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40, | 
|  | 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80, | 
|  | 0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80, | 
|  | 0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02, | 
|  | 0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00, | 
|  | 0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1, | 
|  | 0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1, | 
|  | 0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef, | 
|  | 0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53, | 
|  | 0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8, | 
|  | 0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55, | 
|  | 0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef, | 
|  | 0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c, | 
|  | 0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64, | 
|  | 0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0, | 
|  | 0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63, | 
|  | 0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c, | 
|  | 0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0, | 
|  | 0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c, | 
|  | 0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44, | 
|  | 0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04, | 
|  | 0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40, | 
|  | 0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54, | 
|  | 0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89, | 
|  | 0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00, | 
|  | 0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85, | 
|  | 0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83, | 
|  | 0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83, | 
|  | 0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42, | 
|  | 0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54, | 
|  | 0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54, | 
|  | 0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02, | 
|  | 0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0, | 
|  | 0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82, | 
|  | 0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70, | 
|  | 0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90, | 
|  | 0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00, | 
|  | 0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0, | 
|  | 0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00, | 
|  | 0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52, | 
|  | 0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00, | 
|  | }; | 
|  |  | 
|  | static const char * const vgxy61_test_pattern_menu[] = { | 
|  | "Disabled", | 
|  | "Solid", | 
|  | "Colorbar", | 
|  | "Gradbar", | 
|  | "Hgrey", | 
|  | "Vgrey", | 
|  | "Dgrey", | 
|  | "PN28", | 
|  | }; | 
|  |  | 
|  | static const char * const vgxy61_hdr_mode_menu[] = { | 
|  | "HDR linearize", | 
|  | "HDR substraction", | 
|  | "No HDR", | 
|  | }; | 
|  |  | 
|  | static const char * const vgxy61_supply_name[] = { | 
|  | "VCORE", | 
|  | "VDDIO", | 
|  | "VANA", | 
|  | }; | 
|  |  | 
|  | static const s64 link_freq[] = { | 
|  | /* | 
|  | * MIPI output freq is 804Mhz / 2, as it uses both rising edge and | 
|  | * falling edges to send data | 
|  | */ | 
|  | 402000000ULL | 
|  | }; | 
|  |  | 
|  | enum vgxy61_bin_mode { | 
|  | VGXY61_BIN_MODE_NORMAL, | 
|  | VGXY61_BIN_MODE_DIGITAL_X2, | 
|  | VGXY61_BIN_MODE_DIGITAL_X4, | 
|  | }; | 
|  |  | 
|  | enum vgxy61_hdr_mode { | 
|  | VGXY61_HDR_LINEAR, | 
|  | VGXY61_HDR_SUB, | 
|  | VGXY61_NO_HDR, | 
|  | }; | 
|  |  | 
|  | enum vgxy61_strobe_mode { | 
|  | VGXY61_STROBE_DISABLED, | 
|  | VGXY61_STROBE_LONG, | 
|  | VGXY61_STROBE_ENABLED, | 
|  | }; | 
|  |  | 
|  | struct vgxy61_mode_info { | 
|  | u32 width; | 
|  | u32 height; | 
|  | enum vgxy61_bin_mode bin_mode; | 
|  | struct v4l2_rect crop; | 
|  | }; | 
|  |  | 
|  | struct vgxy61_fmt_desc { | 
|  | u32 code; | 
|  | u8 bpp; | 
|  | u8 data_type; | 
|  | }; | 
|  |  | 
|  | static const struct vgxy61_fmt_desc vgxy61_supported_codes[] = { | 
|  | { | 
|  | .code = MEDIA_BUS_FMT_Y8_1X8, | 
|  | .bpp = 8, | 
|  | .data_type = MIPI_CSI2_DT_RAW8, | 
|  | }, | 
|  | { | 
|  | .code = MEDIA_BUS_FMT_Y10_1X10, | 
|  | .bpp = 10, | 
|  | .data_type = MIPI_CSI2_DT_RAW10, | 
|  | }, | 
|  | { | 
|  | .code = MEDIA_BUS_FMT_Y12_1X12, | 
|  | .bpp = 12, | 
|  | .data_type = MIPI_CSI2_DT_RAW12, | 
|  | }, | 
|  | { | 
|  | .code = MEDIA_BUS_FMT_Y14_1X14, | 
|  | .bpp = 14, | 
|  | .data_type = MIPI_CSI2_DT_RAW14, | 
|  | }, | 
|  | { | 
|  | .code = MEDIA_BUS_FMT_Y16_1X16, | 
|  | .bpp = 16, | 
|  | .data_type = MIPI_CSI2_DT_RAW16, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static const struct vgxy61_mode_info vgx661_mode_data[] = { | 
|  | { | 
|  | .width = VGX661_WIDTH, | 
|  | .height = VGX661_HEIGHT, | 
|  | .bin_mode = VGXY61_BIN_MODE_NORMAL, | 
|  | .crop = { | 
|  | .left = 0, | 
|  | .top = 0, | 
|  | .width = VGX661_WIDTH, | 
|  | .height = VGX661_HEIGHT, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | .width = 1280, | 
|  | .height = 720, | 
|  | .bin_mode = VGXY61_BIN_MODE_NORMAL, | 
|  | .crop = { | 
|  | .left = 92, | 
|  | .top = 192, | 
|  | .width = 1280, | 
|  | .height = 720, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | .width = 640, | 
|  | .height = 480, | 
|  | .bin_mode = VGXY61_BIN_MODE_DIGITAL_X2, | 
|  | .crop = { | 
|  | .left = 92, | 
|  | .top = 72, | 
|  | .width = 1280, | 
|  | .height = 960, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | .width = 320, | 
|  | .height = 240, | 
|  | .bin_mode = VGXY61_BIN_MODE_DIGITAL_X4, | 
|  | .crop = { | 
|  | .left = 92, | 
|  | .top = 72, | 
|  | .width = 1280, | 
|  | .height = 960, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static const struct vgxy61_mode_info vgx761_mode_data[] = { | 
|  | { | 
|  | .width = VGX761_WIDTH, | 
|  | .height = VGX761_HEIGHT, | 
|  | .bin_mode = VGXY61_BIN_MODE_NORMAL, | 
|  | .crop = { | 
|  | .left = 0, | 
|  | .top = 0, | 
|  | .width = VGX761_WIDTH, | 
|  | .height = VGX761_HEIGHT, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | .width = 1920, | 
|  | .height = 1080, | 
|  | .bin_mode = VGXY61_BIN_MODE_NORMAL, | 
|  | .crop = { | 
|  | .left = 12, | 
|  | .top = 62, | 
|  | .width = 1920, | 
|  | .height = 1080, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | .width = 1280, | 
|  | .height = 720, | 
|  | .bin_mode = VGXY61_BIN_MODE_NORMAL, | 
|  | .crop = { | 
|  | .left = 332, | 
|  | .top = 242, | 
|  | .width = 1280, | 
|  | .height = 720, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | .width = 640, | 
|  | .height = 480, | 
|  | .bin_mode = VGXY61_BIN_MODE_DIGITAL_X2, | 
|  | .crop = { | 
|  | .left = 332, | 
|  | .top = 122, | 
|  | .width = 1280, | 
|  | .height = 960, | 
|  | }, | 
|  | }, | 
|  | { | 
|  | .width = 320, | 
|  | .height = 240, | 
|  | .bin_mode = VGXY61_BIN_MODE_DIGITAL_X4, | 
|  | .crop = { | 
|  | .left = 332, | 
|  | .top = 122, | 
|  | .width = 1280, | 
|  | .height = 960, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | struct vgxy61_dev { | 
|  | struct i2c_client *i2c_client; | 
|  | struct regmap *regmap; | 
|  | struct v4l2_subdev sd; | 
|  | struct media_pad pad; | 
|  | struct regulator_bulk_data supplies[ARRAY_SIZE(vgxy61_supply_name)]; | 
|  | struct gpio_desc *reset_gpio; | 
|  | struct clk *xclk; | 
|  | u32 clk_freq; | 
|  | u16 id; | 
|  | u16 sensor_width; | 
|  | u16 sensor_height; | 
|  | u16 oif_ctrl; | 
|  | unsigned int nb_of_lane; | 
|  | u32 data_rate_in_mbps; | 
|  | u32 pclk; | 
|  | u16 line_length; | 
|  | u16 rot_term; | 
|  | bool gpios_polarity; | 
|  | /* Lock to protect all members below */ | 
|  | struct mutex lock; | 
|  | struct v4l2_ctrl_handler ctrl_handler; | 
|  | struct v4l2_ctrl *pixel_rate_ctrl; | 
|  | struct v4l2_ctrl *expo_ctrl; | 
|  | struct v4l2_ctrl *vblank_ctrl; | 
|  | struct v4l2_ctrl *vflip_ctrl; | 
|  | struct v4l2_ctrl *hflip_ctrl; | 
|  | bool streaming; | 
|  | struct v4l2_mbus_framefmt fmt; | 
|  | const struct vgxy61_mode_info *sensor_modes; | 
|  | unsigned int sensor_modes_nb; | 
|  | const struct vgxy61_mode_info *default_mode; | 
|  | const struct vgxy61_mode_info *current_mode; | 
|  | bool hflip; | 
|  | bool vflip; | 
|  | enum vgxy61_hdr_mode hdr; | 
|  | u16 expo_long; | 
|  | u16 expo_short; | 
|  | u16 expo_max; | 
|  | u16 expo_min; | 
|  | u16 vblank; | 
|  | u16 vblank_min; | 
|  | u16 frame_length; | 
|  | u16 digital_gain; | 
|  | u8 analog_gain; | 
|  | enum vgxy61_strobe_mode strobe_mode; | 
|  | u32 pattern; | 
|  | }; | 
|  |  | 
|  | static u8 get_bpp_by_code(__u32 code) | 
|  | { | 
|  | unsigned int i; | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(vgxy61_supported_codes); i++) { | 
|  | if (vgxy61_supported_codes[i].code == code) | 
|  | return vgxy61_supported_codes[i].bpp; | 
|  | } | 
|  | /* Should never happen */ | 
|  | WARN(1, "Unsupported code %d. default to 8 bpp", code); | 
|  | return 8; | 
|  | } | 
|  |  | 
|  | static u8 get_data_type_by_code(__u32 code) | 
|  | { | 
|  | unsigned int i; | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(vgxy61_supported_codes); i++) { | 
|  | if (vgxy61_supported_codes[i].code == code) | 
|  | return vgxy61_supported_codes[i].data_type; | 
|  | } | 
|  | /* Should never happen */ | 
|  | WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", | 
|  | code); | 
|  | return MIPI_CSI2_DT_RAW8; | 
|  | } | 
|  |  | 
|  | static void compute_pll_parameters_by_freq(u32 freq, u8 *prediv, u8 *mult) | 
|  | { | 
|  | const unsigned int predivs[] = {1, 2, 4}; | 
|  | unsigned int i; | 
|  |  | 
|  | /* | 
|  | * Freq range is [6Mhz-27Mhz] already checked. | 
|  | * Output of divider should be in [6Mhz-12Mhz[. | 
|  | */ | 
|  | for (i = 0; i < ARRAY_SIZE(predivs); i++) { | 
|  | *prediv = predivs[i]; | 
|  | if (freq / *prediv < 12 * HZ_PER_MHZ) | 
|  | break; | 
|  | } | 
|  | WARN_ON(i == ARRAY_SIZE(predivs)); | 
|  |  | 
|  | /* | 
|  | * Target freq is 804Mhz. Don't change this as it will impact image | 
|  | * quality. | 
|  | */ | 
|  | *mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq; | 
|  | } | 
|  |  | 
|  | static s32 get_pixel_rate(struct vgxy61_dev *sensor) | 
|  | { | 
|  | return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane, | 
|  | get_bpp_by_code(sensor->fmt.code)); | 
|  | } | 
|  |  | 
|  | static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd) | 
|  | { | 
|  | return container_of(sd, struct vgxy61_dev, sd); | 
|  | } | 
|  |  | 
|  | static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) | 
|  | { | 
|  | return &container_of(ctrl->handler, struct vgxy61_dev, | 
|  | ctrl_handler)->sd; | 
|  | } | 
|  |  | 
|  | static unsigned int get_chunk_size(struct vgxy61_dev *sensor) | 
|  | { | 
|  | struct i2c_adapter *adapter = sensor->i2c_client->adapter; | 
|  | int max_write_len = VGXY61_WRITE_MULTIPLE_CHUNK_MAX; | 
|  |  | 
|  | if (adapter->quirks && adapter->quirks->max_write_len) | 
|  | max_write_len = adapter->quirks->max_write_len - 2; | 
|  |  | 
|  | max_write_len = min(max_write_len, VGXY61_WRITE_MULTIPLE_CHUNK_MAX); | 
|  |  | 
|  | return max(max_write_len, 1); | 
|  | } | 
|  |  | 
|  | static int vgxy61_write_array(struct vgxy61_dev *sensor, u32 reg, | 
|  | unsigned int nb, const u8 *array) | 
|  | { | 
|  | const unsigned int chunk_size = get_chunk_size(sensor); | 
|  | int ret; | 
|  | unsigned int sz; | 
|  |  | 
|  | while (nb) { | 
|  | sz = min(nb, chunk_size); | 
|  | ret = regmap_bulk_write(sensor->regmap, CCI_REG_ADDR(reg), | 
|  | array, sz); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | nb -= sz; | 
|  | reg += sz; | 
|  | array += sz; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u32 reg, u8 poll_val, | 
|  | unsigned int timeout_ms) | 
|  | { | 
|  | const unsigned int loop_delay_ms = 10; | 
|  | u64 val; | 
|  | int ret; | 
|  |  | 
|  | return read_poll_timeout(cci_read, ret, | 
|  | ((ret < 0) || (val == poll_val)), | 
|  | loop_delay_ms * 1000, timeout_ms * 1000, | 
|  | false, sensor->regmap, reg, &val, NULL); | 
|  | } | 
|  |  | 
|  | static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state, | 
|  | unsigned int timeout_ms) | 
|  | { | 
|  | return vgxy61_poll_reg(sensor, VGXY61_REG_SYSTEM_FSM, state, | 
|  | timeout_ms); | 
|  | } | 
|  |  | 
|  | static int vgxy61_check_bw(struct vgxy61_dev *sensor) | 
|  | { | 
|  | /* | 
|  | * Simplification of time needed to send short packets and for the MIPI | 
|  | * to add transition times (EoT, LPS, and SoT packet delimiters) needed | 
|  | * by the protocol to go in low power between 2 packets of data. This | 
|  | * is a mipi IP constant for the sensor. | 
|  | */ | 
|  | const unsigned int mipi_margin = 1056; | 
|  | unsigned int binning_scale = sensor->current_mode->crop.height / | 
|  | sensor->current_mode->height; | 
|  | u8 bpp = get_bpp_by_code(sensor->fmt.code); | 
|  | unsigned int max_bit_per_line; | 
|  | unsigned int bit_per_line; | 
|  | u64 line_rate; | 
|  |  | 
|  | line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps * | 
|  | sensor->line_length; | 
|  | max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin; | 
|  | bit_per_line = (bpp * sensor->current_mode->width) / binning_scale; | 
|  |  | 
|  | return bit_per_line > max_bit_per_line ? -EINVAL : 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_apply_exposure(struct vgxy61_dev *sensor) | 
|  | { | 
|  | int ret = 0; | 
|  |  | 
|  | /* We first set expo to zero to avoid forbidden parameters couple */ | 
|  | cci_write(sensor->regmap, VGXY61_REG_COARSE_EXPOSURE_SHORT, 0, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_COARSE_EXPOSURE_LONG, | 
|  | sensor->expo_long, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_COARSE_EXPOSURE_SHORT, | 
|  | sensor->expo_short, &ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int vgxy61_get_regulators(struct vgxy61_dev *sensor) | 
|  | { | 
|  | unsigned int i; | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(vgxy61_supply_name); i++) | 
|  | sensor->supplies[i].supply = vgxy61_supply_name[i]; | 
|  |  | 
|  | return devm_regulator_bulk_get(&sensor->i2c_client->dev, | 
|  | ARRAY_SIZE(vgxy61_supply_name), | 
|  | sensor->supplies); | 
|  | } | 
|  |  | 
|  | static int vgxy61_apply_reset(struct vgxy61_dev *sensor) | 
|  | { | 
|  | gpiod_set_value_cansleep(sensor->reset_gpio, 0); | 
|  | usleep_range(5000, 10000); | 
|  | gpiod_set_value_cansleep(sensor->reset_gpio, 1); | 
|  | usleep_range(5000, 10000); | 
|  | gpiod_set_value_cansleep(sensor->reset_gpio, 0); | 
|  | usleep_range(40000, 100000); | 
|  | return vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY, | 
|  | VGXY61_TIMEOUT_MS); | 
|  | } | 
|  |  | 
|  | static void vgxy61_fill_framefmt(struct vgxy61_dev *sensor, | 
|  | const struct vgxy61_mode_info *mode, | 
|  | struct v4l2_mbus_framefmt *fmt, u32 code) | 
|  | { | 
|  | fmt->code = code; | 
|  | fmt->width = mode->width; | 
|  | fmt->height = mode->height; | 
|  | fmt->colorspace = V4L2_COLORSPACE_RAW; | 
|  | fmt->field = V4L2_FIELD_NONE; | 
|  | fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; | 
|  | fmt->quantization = V4L2_QUANTIZATION_DEFAULT; | 
|  | fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; | 
|  | } | 
|  |  | 
|  | static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, | 
|  | struct v4l2_mbus_framefmt *fmt, | 
|  | const struct vgxy61_mode_info **new_mode) | 
|  | { | 
|  | struct vgxy61_dev *sensor = to_vgxy61_dev(sd); | 
|  | const struct vgxy61_mode_info *mode; | 
|  | unsigned int index; | 
|  |  | 
|  | for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) { | 
|  | if (vgxy61_supported_codes[index].code == fmt->code) | 
|  | break; | 
|  | } | 
|  | if (index == ARRAY_SIZE(vgxy61_supported_codes)) | 
|  | index = 0; | 
|  |  | 
|  | mode = v4l2_find_nearest_size(sensor->sensor_modes, | 
|  | sensor->sensor_modes_nb, width, height, | 
|  | fmt->width, fmt->height); | 
|  | if (new_mode) | 
|  | *new_mode = mode; | 
|  |  | 
|  | vgxy61_fill_framefmt(sensor, mode, fmt, | 
|  | vgxy61_supported_codes[index].code); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_get_selection(struct v4l2_subdev *sd, | 
|  | struct v4l2_subdev_state *sd_state, | 
|  | struct v4l2_subdev_selection *sel) | 
|  | { | 
|  | struct vgxy61_dev *sensor = to_vgxy61_dev(sd); | 
|  |  | 
|  | switch (sel->target) { | 
|  | case V4L2_SEL_TGT_CROP: | 
|  | sel->r = sensor->current_mode->crop; | 
|  | return 0; | 
|  | case V4L2_SEL_TGT_NATIVE_SIZE: | 
|  | case V4L2_SEL_TGT_CROP_DEFAULT: | 
|  | case V4L2_SEL_TGT_CROP_BOUNDS: | 
|  | sel->r.top = 0; | 
|  | sel->r.left = 0; | 
|  | sel->r.width = sensor->sensor_width; | 
|  | sel->r.height = sensor->sensor_height; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd, | 
|  | struct v4l2_subdev_state *sd_state, | 
|  | struct v4l2_subdev_mbus_code_enum *code) | 
|  | { | 
|  | if (code->index >= ARRAY_SIZE(vgxy61_supported_codes)) | 
|  | return -EINVAL; | 
|  |  | 
|  | code->code = vgxy61_supported_codes[code->index].code; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_get_fmt(struct v4l2_subdev *sd, | 
|  | struct v4l2_subdev_state *sd_state, | 
|  | struct v4l2_subdev_format *format) | 
|  | { | 
|  | struct vgxy61_dev *sensor = to_vgxy61_dev(sd); | 
|  | struct v4l2_mbus_framefmt *fmt; | 
|  |  | 
|  | mutex_lock(&sensor->lock); | 
|  |  | 
|  | if (format->which == V4L2_SUBDEV_FORMAT_TRY) | 
|  | fmt = v4l2_subdev_state_get_format(sd_state, format->pad); | 
|  | else | 
|  | fmt = &sensor->fmt; | 
|  |  | 
|  | format->format = *fmt; | 
|  |  | 
|  | mutex_unlock(&sensor->lock); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static u16 vgxy61_get_vblank_min(struct vgxy61_dev *sensor, | 
|  | enum vgxy61_hdr_mode hdr) | 
|  | { | 
|  | u16 min_vblank =  VGXY61_MIN_FRAME_LENGTH - | 
|  | sensor->current_mode->crop.height; | 
|  | /* Ensure the first rule of thumb can't be negative */ | 
|  | u16 min_vblank_hdr =  VGXY61_MIN_EXPOSURE + sensor->rot_term + 1; | 
|  |  | 
|  | if (hdr != VGXY61_NO_HDR) | 
|  | return max(min_vblank, min_vblank_hdr); | 
|  | return min_vblank; | 
|  | } | 
|  |  | 
|  | static int vgxy61_enum_frame_size(struct v4l2_subdev *sd, | 
|  | struct v4l2_subdev_state *sd_state, | 
|  | struct v4l2_subdev_frame_size_enum *fse) | 
|  | { | 
|  | struct vgxy61_dev *sensor = to_vgxy61_dev(sd); | 
|  |  | 
|  | if (fse->index >= sensor->sensor_modes_nb) | 
|  | return -EINVAL; | 
|  |  | 
|  | fse->min_width = sensor->sensor_modes[fse->index].width; | 
|  | fse->max_width = fse->min_width; | 
|  | fse->min_height = sensor->sensor_modes[fse->index].height; | 
|  | fse->max_height = fse->min_height; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target) | 
|  | { | 
|  | sensor->analog_gain = target; | 
|  |  | 
|  | if (sensor->streaming) | 
|  | return cci_write(sensor->regmap, VGXY61_REG_ANALOG_GAIN, target, | 
|  | NULL); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_apply_digital_gain(struct vgxy61_dev *sensor, | 
|  | u32 digital_gain) | 
|  | { | 
|  | int ret = 0; | 
|  |  | 
|  | /* | 
|  | * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and | 
|  | * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all | 
|  | * four sub pixels. | 
|  | */ | 
|  | cci_write(sensor->regmap, VGXY61_REG_DIGITAL_GAIN_LONG, digital_gain, | 
|  | &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_DIGITAL_GAIN_SHORT, digital_gain, | 
|  | &ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target) | 
|  | { | 
|  | sensor->digital_gain = target; | 
|  |  | 
|  | if (sensor->streaming) | 
|  | return vgxy61_apply_digital_gain(sensor, sensor->digital_gain); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_apply_patgen(struct vgxy61_dev *sensor, u32 index) | 
|  | { | 
|  | static const u8 index2val[] = { | 
|  | 0x0, 0x1, 0x2, 0x3, 0x10, 0x11, 0x12, 0x13 | 
|  | }; | 
|  | u32 pattern = index2val[index]; | 
|  | u32 reg = (pattern << VGXY61_PATGEN_LONG_TYPE_SHIFT) | | 
|  | (pattern << VGXY61_PATGEN_SHORT_TYPE_SHIFT); | 
|  |  | 
|  | if (pattern) | 
|  | reg |= VGXY61_PATGEN_LONG_ENABLE | VGXY61_PATGEN_SHORT_ENABLE; | 
|  | return cci_write(sensor->regmap, VGXY61_REG_PATGEN_CTRL, reg, NULL); | 
|  | } | 
|  |  | 
|  | static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 pattern) | 
|  | { | 
|  | sensor->pattern = pattern; | 
|  |  | 
|  | if (sensor->streaming) | 
|  | return vgxy61_apply_patgen(sensor, sensor->pattern); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_apply_gpiox_strobe_mode(struct vgxy61_dev *sensor, | 
|  | enum vgxy61_strobe_mode mode, | 
|  | unsigned int idx) | 
|  | { | 
|  | static const u8 index2val[] = {0x0, 0x1, 0x3}; | 
|  | u16 mask, val; | 
|  |  | 
|  | mask = 0xf << (idx * VGXY61_SIGNALS_GPIO_ID_SHIFT); | 
|  | val = index2val[mode] << (idx * VGXY61_SIGNALS_GPIO_ID_SHIFT); | 
|  |  | 
|  | return cci_update_bits(sensor->regmap, VGXY61_REG_SIGNALS_CTRL, | 
|  | mask, val, NULL); | 
|  | } | 
|  |  | 
|  | static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, | 
|  | enum vgxy61_hdr_mode hdr) | 
|  | { | 
|  | unsigned int i; | 
|  | int ret; | 
|  |  | 
|  | switch (hdr) { | 
|  | case VGXY61_HDR_LINEAR: | 
|  | sensor->strobe_mode = VGXY61_STROBE_ENABLED; | 
|  | break; | 
|  | case VGXY61_HDR_SUB: | 
|  | case VGXY61_NO_HDR: | 
|  | sensor->strobe_mode = VGXY61_STROBE_LONG; | 
|  | break; | 
|  | default: | 
|  | /* Should never happen */ | 
|  | WARN_ON(true); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (!sensor->streaming) | 
|  | return 0; | 
|  |  | 
|  | for (i = 0; i < VGXY61_NB_GPIOS; i++) { | 
|  | ret = vgxy61_apply_gpiox_strobe_mode(sensor, | 
|  | sensor->strobe_mode, | 
|  | i); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, | 
|  | bool polarity) | 
|  | { | 
|  | int ret = 0; | 
|  |  | 
|  | if (sensor->streaming) | 
|  | return -EBUSY; | 
|  |  | 
|  | cci_write(sensor->regmap, VGXY61_REG_GPIO_0_CTRL, polarity << 1, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_GPIO_1_CTRL, polarity << 1, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_GPIO_2_CTRL, polarity << 1, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_GPIO_3_CTRL, polarity << 1, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_SIGNALS_POLARITY_CTRL, polarity, | 
|  | &ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static u32 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, | 
|  | unsigned int short_expo_ratio) | 
|  | { | 
|  | u32 first_rot_max_expo, second_rot_max_expo, third_rot_max_expo; | 
|  |  | 
|  | /* Apply sensor's rules of thumb */ | 
|  | /* | 
|  | * Short exposure + height must be less than frame length to avoid bad | 
|  | * pixel line at the botom of the image | 
|  | */ | 
|  | first_rot_max_expo = | 
|  | ((sensor->frame_length - sensor->current_mode->crop.height - | 
|  | sensor->rot_term) * short_expo_ratio) - 1; | 
|  |  | 
|  | /* | 
|  | * Total exposition time must be less than frame length to avoid sensor | 
|  | * crash | 
|  | */ | 
|  | second_rot_max_expo = | 
|  | (((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * | 
|  | short_expo_ratio) / (short_expo_ratio + 1)) - 1; | 
|  |  | 
|  | /* | 
|  | * Short exposure times 71 must be less than frame length to avoid | 
|  | * sensor crash | 
|  | */ | 
|  | third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio; | 
|  |  | 
|  | /* Take the minimum from all rules */ | 
|  | return min(min(first_rot_max_expo, second_rot_max_expo), | 
|  | third_rot_max_expo); | 
|  | } | 
|  |  | 
|  | static int vgxy61_update_exposure(struct vgxy61_dev *sensor, u16 new_expo_long, | 
|  | enum vgxy61_hdr_mode hdr) | 
|  | { | 
|  | struct i2c_client *client = sensor->i2c_client; | 
|  | u16 new_expo_short = 0; | 
|  | u16 expo_short_max = 0; | 
|  | u16 expo_long_min = VGXY61_MIN_EXPOSURE; | 
|  | u16 expo_long_max = 0; | 
|  |  | 
|  | /* Compute short exposure according to hdr mode and long exposure */ | 
|  | switch (hdr) { | 
|  | case VGXY61_HDR_LINEAR: | 
|  | /* | 
|  | * Take ratio into account for minimal exposures in | 
|  | * VGXY61_HDR_LINEAR | 
|  | */ | 
|  | expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO; | 
|  | new_expo_long = max(expo_long_min, new_expo_long); | 
|  |  | 
|  | expo_long_max = | 
|  | vgxy61_get_expo_long_max(sensor, | 
|  | VGXY61_HDR_LINEAR_RATIO); | 
|  | expo_short_max = (expo_long_max + | 
|  | (VGXY61_HDR_LINEAR_RATIO / 2)) / | 
|  | VGXY61_HDR_LINEAR_RATIO; | 
|  | new_expo_short = (new_expo_long + | 
|  | (VGXY61_HDR_LINEAR_RATIO / 2)) / | 
|  | VGXY61_HDR_LINEAR_RATIO; | 
|  | break; | 
|  | case VGXY61_HDR_SUB: | 
|  | new_expo_long = max(expo_long_min, new_expo_long); | 
|  |  | 
|  | expo_long_max = vgxy61_get_expo_long_max(sensor, 1); | 
|  | /* Short and long are the same in VGXY61_HDR_SUB */ | 
|  | expo_short_max = expo_long_max; | 
|  | new_expo_short = new_expo_long; | 
|  | break; | 
|  | case VGXY61_NO_HDR: | 
|  | new_expo_long = max(expo_long_min, new_expo_long); | 
|  |  | 
|  | /* | 
|  | * As short expo is 0 here, only the second rule of thumb | 
|  | * applies, see vgxy61_get_expo_long_max for more | 
|  | */ | 
|  | expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM; | 
|  | break; | 
|  | default: | 
|  | /* Should never happen */ | 
|  | WARN_ON(true); | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* If this happens, something is wrong with formulas */ | 
|  | WARN_ON(expo_long_min > expo_long_max); | 
|  |  | 
|  | if (new_expo_long > expo_long_max) { | 
|  | dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n", | 
|  | new_expo_long, expo_long_max); | 
|  | new_expo_long = expo_long_max; | 
|  | new_expo_short = expo_short_max; | 
|  | } | 
|  |  | 
|  | sensor->expo_long = new_expo_long; | 
|  | sensor->expo_short = new_expo_short; | 
|  | sensor->expo_max = expo_long_max; | 
|  | sensor->expo_min = expo_long_min; | 
|  |  | 
|  | if (sensor->streaming) | 
|  | return vgxy61_apply_exposure(sensor); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_apply_framelength(struct vgxy61_dev *sensor) | 
|  | { | 
|  | return cci_write(sensor->regmap, VGXY61_REG_FRAME_LENGTH, | 
|  | sensor->frame_length, NULL); | 
|  | } | 
|  |  | 
|  | static int vgxy61_update_vblank(struct vgxy61_dev *sensor, u16 vblank, | 
|  | enum vgxy61_hdr_mode hdr) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr); | 
|  | sensor->vblank = max(sensor->vblank_min, vblank); | 
|  | sensor->frame_length = sensor->current_mode->crop.height + | 
|  | sensor->vblank; | 
|  |  | 
|  | /* Update exposure according to vblank */ | 
|  | ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | if (sensor->streaming) | 
|  | return vgxy61_apply_framelength(sensor); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_apply_hdr(struct vgxy61_dev *sensor, | 
|  | enum vgxy61_hdr_mode index) | 
|  | { | 
|  | static const u8 index2val[] = {0x1, 0x4, 0xa}; | 
|  |  | 
|  | return cci_write(sensor->regmap, VGXY61_REG_HDR_CTRL, index2val[index], | 
|  | NULL); | 
|  | } | 
|  |  | 
|  | static int vgxy61_update_hdr(struct vgxy61_dev *sensor, | 
|  | enum vgxy61_hdr_mode index) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | /* | 
|  | * vblank and short exposure change according to HDR mode, do it first | 
|  | * as it can violate sensors 'rule of thumbs' and therefore will require | 
|  | * to change the long exposure. | 
|  | */ | 
|  | ret = vgxy61_update_vblank(sensor, sensor->vblank, index); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | /* Update strobe mode according to HDR */ | 
|  | ret = vgxy61_update_gpios_strobe_mode(sensor, index); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | sensor->hdr = index; | 
|  |  | 
|  | if (sensor->streaming) | 
|  | return vgxy61_apply_hdr(sensor, sensor->hdr); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_apply_settings(struct vgxy61_dev *sensor) | 
|  | { | 
|  | int ret; | 
|  | unsigned int i; | 
|  |  | 
|  | ret = vgxy61_apply_hdr(sensor, sensor->hdr); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = vgxy61_apply_framelength(sensor); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = vgxy61_apply_exposure(sensor); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = cci_write(sensor->regmap, VGXY61_REG_ANALOG_GAIN, | 
|  | sensor->analog_gain, NULL); | 
|  | if (ret) | 
|  | return ret; | 
|  | ret = vgxy61_apply_digital_gain(sensor, sensor->digital_gain); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = cci_write(sensor->regmap, VGXY61_REG_ORIENTATION, | 
|  | sensor->hflip | (sensor->vflip << 1), NULL); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = vgxy61_apply_patgen(sensor, sensor->pattern); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | for (i = 0; i < VGXY61_NB_GPIOS; i++) { | 
|  | ret = vgxy61_apply_gpiox_strobe_mode(sensor, | 
|  | sensor->strobe_mode, i); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_stream_enable(struct vgxy61_dev *sensor) | 
|  | { | 
|  | struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); | 
|  | const struct v4l2_rect *crop = &sensor->current_mode->crop; | 
|  | int ret = 0; | 
|  |  | 
|  | ret = vgxy61_check_bw(sensor); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = pm_runtime_resume_and_get(&client->dev); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | cci_write(sensor->regmap, VGXY61_REG_FORMAT_CTRL, | 
|  | get_bpp_by_code(sensor->fmt.code), &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_OIF_ROI0_CTRL, | 
|  | get_data_type_by_code(sensor->fmt.code), &ret); | 
|  |  | 
|  | cci_write(sensor->regmap, VGXY61_REG_READOUT_CTRL, | 
|  | sensor->current_mode->bin_mode, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_ROI0_START_H, crop->left, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_ROI0_END_H, | 
|  | crop->left + crop->width - 1, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_ROI0_START_V, crop->top, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_ROI0_END_V, | 
|  | crop->top + crop->height - 1, &ret); | 
|  | if (ret) | 
|  | goto err_rpm_put; | 
|  |  | 
|  | ret = vgxy61_apply_settings(sensor); | 
|  | if (ret) | 
|  | goto err_rpm_put; | 
|  |  | 
|  | ret = cci_write(sensor->regmap, VGXY61_REG_STREAMING, | 
|  | VGXY61_STREAMING_REQ_START, NULL); | 
|  | if (ret) | 
|  | goto err_rpm_put; | 
|  |  | 
|  | ret = vgxy61_poll_reg(sensor, VGXY61_REG_STREAMING, | 
|  | VGXY61_STREAMING_NO_REQ, VGXY61_TIMEOUT_MS); | 
|  | if (ret) | 
|  | goto err_rpm_put; | 
|  |  | 
|  | ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_STREAMING, | 
|  | VGXY61_TIMEOUT_MS); | 
|  | if (ret) | 
|  | goto err_rpm_put; | 
|  |  | 
|  | /* vflip and hflip cannot change during streaming */ | 
|  | __v4l2_ctrl_grab(sensor->vflip_ctrl, true); | 
|  | __v4l2_ctrl_grab(sensor->hflip_ctrl, true); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | err_rpm_put: | 
|  | pm_runtime_put(&client->dev); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int vgxy61_stream_disable(struct vgxy61_dev *sensor) | 
|  | { | 
|  | struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); | 
|  | int ret; | 
|  |  | 
|  | ret = cci_write(sensor->regmap, VGXY61_REG_STREAMING, | 
|  | VGXY61_STREAMING_REQ_STOP, NULL); | 
|  | if (ret) | 
|  | goto err_str_dis; | 
|  |  | 
|  | ret = vgxy61_poll_reg(sensor, VGXY61_REG_STREAMING, | 
|  | VGXY61_STREAMING_NO_REQ, 2000); | 
|  | if (ret) | 
|  | goto err_str_dis; | 
|  |  | 
|  | ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY, | 
|  | VGXY61_TIMEOUT_MS); | 
|  | if (ret) | 
|  | goto err_str_dis; | 
|  |  | 
|  | __v4l2_ctrl_grab(sensor->vflip_ctrl, false); | 
|  | __v4l2_ctrl_grab(sensor->hflip_ctrl, false); | 
|  |  | 
|  | err_str_dis: | 
|  | if (ret) | 
|  | WARN(1, "Can't disable stream"); | 
|  | pm_runtime_put(&client->dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable) | 
|  | { | 
|  | struct vgxy61_dev *sensor = to_vgxy61_dev(sd); | 
|  | int ret = 0; | 
|  |  | 
|  | mutex_lock(&sensor->lock); | 
|  |  | 
|  | ret = enable ? vgxy61_stream_enable(sensor) : | 
|  | vgxy61_stream_disable(sensor); | 
|  | if (!ret) | 
|  | sensor->streaming = enable; | 
|  |  | 
|  | mutex_unlock(&sensor->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int vgxy61_set_fmt(struct v4l2_subdev *sd, | 
|  | struct v4l2_subdev_state *sd_state, | 
|  | struct v4l2_subdev_format *format) | 
|  | { | 
|  | struct vgxy61_dev *sensor = to_vgxy61_dev(sd); | 
|  | const struct vgxy61_mode_info *new_mode; | 
|  | struct v4l2_mbus_framefmt *fmt; | 
|  | int ret; | 
|  |  | 
|  | mutex_lock(&sensor->lock); | 
|  |  | 
|  | if (sensor->streaming) { | 
|  | ret = -EBUSY; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode); | 
|  | if (ret) | 
|  | goto out; | 
|  |  | 
|  | if (format->which == V4L2_SUBDEV_FORMAT_TRY) { | 
|  | fmt = v4l2_subdev_state_get_format(sd_state, 0); | 
|  | *fmt = format->format; | 
|  | } else if (sensor->current_mode != new_mode || | 
|  | sensor->fmt.code != format->format.code) { | 
|  | fmt = &sensor->fmt; | 
|  | *fmt = format->format; | 
|  |  | 
|  | sensor->current_mode = new_mode; | 
|  |  | 
|  | /* Reset vblank and framelength to default */ | 
|  | ret = vgxy61_update_vblank(sensor, | 
|  | VGXY61_FRAME_LENGTH_DEF - | 
|  | new_mode->crop.height, | 
|  | sensor->hdr); | 
|  |  | 
|  | /* Update controls to reflect new mode */ | 
|  | __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, | 
|  | get_pixel_rate(sensor)); | 
|  | __v4l2_ctrl_modify_range(sensor->vblank_ctrl, | 
|  | sensor->vblank_min, | 
|  | 0xffff - new_mode->crop.height, | 
|  | 1, sensor->vblank); | 
|  | __v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank); | 
|  | __v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min, | 
|  | sensor->expo_max, 1, | 
|  | sensor->expo_long); | 
|  | } | 
|  |  | 
|  | out: | 
|  | mutex_unlock(&sensor->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int vgxy61_init_state(struct v4l2_subdev *sd, | 
|  | struct v4l2_subdev_state *sd_state) | 
|  | { | 
|  | struct vgxy61_dev *sensor = to_vgxy61_dev(sd); | 
|  | struct v4l2_subdev_format fmt = { 0 }; | 
|  |  | 
|  | vgxy61_fill_framefmt(sensor, sensor->current_mode, &fmt.format, | 
|  | VGXY61_MEDIA_BUS_FMT_DEF); | 
|  |  | 
|  | return vgxy61_set_fmt(sd, sd_state, &fmt); | 
|  | } | 
|  |  | 
|  | static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl) | 
|  | { | 
|  | struct v4l2_subdev *sd = ctrl_to_sd(ctrl); | 
|  | struct vgxy61_dev *sensor = to_vgxy61_dev(sd); | 
|  | const struct vgxy61_mode_info *cur_mode = sensor->current_mode; | 
|  | int ret; | 
|  |  | 
|  | switch (ctrl->id) { | 
|  | case V4L2_CID_EXPOSURE: | 
|  | ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr); | 
|  | ctrl->val = sensor->expo_long; | 
|  | break; | 
|  | case V4L2_CID_ANALOGUE_GAIN: | 
|  | ret = vgxy61_update_analog_gain(sensor, ctrl->val); | 
|  | break; | 
|  | case V4L2_CID_DIGITAL_GAIN: | 
|  | ret = vgxy61_update_digital_gain(sensor, ctrl->val); | 
|  | break; | 
|  | case V4L2_CID_VFLIP: | 
|  | case V4L2_CID_HFLIP: | 
|  | if (sensor->streaming) { | 
|  | ret = -EBUSY; | 
|  | break; | 
|  | } | 
|  | if (ctrl->id == V4L2_CID_VFLIP) | 
|  | sensor->vflip = ctrl->val; | 
|  | if (ctrl->id == V4L2_CID_HFLIP) | 
|  | sensor->hflip = ctrl->val; | 
|  | ret = 0; | 
|  | break; | 
|  | case V4L2_CID_TEST_PATTERN: | 
|  | ret = vgxy61_update_patgen(sensor, ctrl->val); | 
|  | break; | 
|  | case V4L2_CID_HDR_SENSOR_MODE: | 
|  | ret = vgxy61_update_hdr(sensor, ctrl->val); | 
|  | /* Update vblank and exposure controls to match new hdr */ | 
|  | __v4l2_ctrl_modify_range(sensor->vblank_ctrl, | 
|  | sensor->vblank_min, | 
|  | 0xffff - cur_mode->crop.height, | 
|  | 1, sensor->vblank); | 
|  | __v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min, | 
|  | sensor->expo_max, 1, | 
|  | sensor->expo_long); | 
|  | break; | 
|  | case V4L2_CID_VBLANK: | 
|  | ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr); | 
|  | /* Update exposure control to match new vblank */ | 
|  | __v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min, | 
|  | sensor->expo_max, 1, | 
|  | sensor->expo_long); | 
|  | break; | 
|  | default: | 
|  | ret = -EINVAL; | 
|  | break; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = { | 
|  | .s_ctrl = vgxy61_s_ctrl, | 
|  | }; | 
|  |  | 
|  | static int vgxy61_init_controls(struct vgxy61_dev *sensor) | 
|  | { | 
|  | const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops; | 
|  | struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler; | 
|  | const struct vgxy61_mode_info *cur_mode = sensor->current_mode; | 
|  | struct v4l2_fwnode_device_properties props; | 
|  | struct v4l2_ctrl *ctrl; | 
|  | int ret; | 
|  |  | 
|  | v4l2_ctrl_handler_init(hdl, 16); | 
|  | /* We can use our own mutex for the ctrl lock */ | 
|  | hdl->lock = &sensor->lock; | 
|  | v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, | 
|  | sensor->analog_gain); | 
|  | v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, | 
|  | sensor->digital_gain); | 
|  | v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, | 
|  | ARRAY_SIZE(vgxy61_test_pattern_menu) - 1, | 
|  | 0, 0, vgxy61_test_pattern_menu); | 
|  | ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, | 
|  | sensor->line_length, 1, | 
|  | sensor->line_length - cur_mode->width); | 
|  | if (ctrl) | 
|  | ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; | 
|  | ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, | 
|  | ARRAY_SIZE(link_freq) - 1, 0, link_freq); | 
|  | if (ctrl) | 
|  | ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; | 
|  | v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_HDR_SENSOR_MODE, | 
|  | ARRAY_SIZE(vgxy61_hdr_mode_menu) - 1, 0, | 
|  | VGXY61_NO_HDR, vgxy61_hdr_mode_menu); | 
|  |  | 
|  | /* | 
|  | * Keep a pointer to these controls as we need to update them when | 
|  | * setting the format | 
|  | */ | 
|  | sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, | 
|  | V4L2_CID_PIXEL_RATE, 1, | 
|  | INT_MAX, 1, | 
|  | get_pixel_rate(sensor)); | 
|  | if (sensor->pixel_rate_ctrl) | 
|  | sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; | 
|  | sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, | 
|  | sensor->expo_min, | 
|  | sensor->expo_max, 1, | 
|  | sensor->expo_long); | 
|  | sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, | 
|  | sensor->vblank_min, | 
|  | 0xffff - cur_mode->crop.height, | 
|  | 1, sensor->vblank); | 
|  | sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, | 
|  | 0, 1, 1, sensor->vflip); | 
|  | sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, | 
|  | 0, 1, 1, sensor->hflip); | 
|  |  | 
|  | if (hdl->error) { | 
|  | ret = hdl->error; | 
|  | goto free_ctrls; | 
|  | } | 
|  |  | 
|  | ret = v4l2_fwnode_device_parse(&sensor->i2c_client->dev, &props); | 
|  | if (ret) | 
|  | goto free_ctrls; | 
|  |  | 
|  | ret = v4l2_ctrl_new_fwnode_properties(hdl, ops, &props); | 
|  | if (ret) | 
|  | goto free_ctrls; | 
|  |  | 
|  | sensor->sd.ctrl_handler = hdl; | 
|  | return 0; | 
|  |  | 
|  | free_ctrls: | 
|  | v4l2_ctrl_handler_free(hdl); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static const struct v4l2_subdev_core_ops vgxy61_core_ops = { | 
|  | .subscribe_event = v4l2_ctrl_subdev_subscribe_event, | 
|  | .unsubscribe_event = v4l2_event_subdev_unsubscribe, | 
|  | }; | 
|  |  | 
|  | static const struct v4l2_subdev_video_ops vgxy61_video_ops = { | 
|  | .s_stream = vgxy61_s_stream, | 
|  | }; | 
|  |  | 
|  | static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = { | 
|  | .enum_mbus_code = vgxy61_enum_mbus_code, | 
|  | .get_fmt = vgxy61_get_fmt, | 
|  | .set_fmt = vgxy61_set_fmt, | 
|  | .get_selection = vgxy61_get_selection, | 
|  | .enum_frame_size = vgxy61_enum_frame_size, | 
|  | }; | 
|  |  | 
|  | static const struct v4l2_subdev_ops vgxy61_subdev_ops = { | 
|  | .core = &vgxy61_core_ops, | 
|  | .video = &vgxy61_video_ops, | 
|  | .pad = &vgxy61_pad_ops, | 
|  | }; | 
|  |  | 
|  | static const struct v4l2_subdev_internal_ops vgxy61_internal_ops = { | 
|  | .init_state = vgxy61_init_state, | 
|  | }; | 
|  |  | 
|  | static const struct media_entity_operations vgxy61_subdev_entity_ops = { | 
|  | .link_validate = v4l2_subdev_link_validate, | 
|  | }; | 
|  |  | 
|  | static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, | 
|  | struct fwnode_handle *handle) | 
|  | { | 
|  | struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; | 
|  | struct i2c_client *client = sensor->i2c_client; | 
|  | u32 log2phy[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0}; | 
|  | u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0}; | 
|  | int polarities[VGXY61_NB_POLARITIES] = {0, 0, 0, 0, 0}; | 
|  | int l_nb; | 
|  | unsigned int p, l, i; | 
|  | int ret; | 
|  |  | 
|  | ret = v4l2_fwnode_endpoint_alloc_parse(handle, &ep); | 
|  | if (ret) | 
|  | return -EINVAL; | 
|  |  | 
|  | l_nb = ep.bus.mipi_csi2.num_data_lanes; | 
|  | if (l_nb != 1 && l_nb != 2 && l_nb != 4) { | 
|  | dev_err(&client->dev, "invalid data lane number %d\n", l_nb); | 
|  | goto error_ep; | 
|  | } | 
|  |  | 
|  | /* Build log2phy, phy2log and polarities from ep info */ | 
|  | log2phy[0] = ep.bus.mipi_csi2.clock_lane; | 
|  | phy2log[log2phy[0]] = 0; | 
|  | for (l = 1; l < l_nb + 1; l++) { | 
|  | log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1]; | 
|  | phy2log[log2phy[l]] = l; | 
|  | } | 
|  | /* | 
|  | * Then fill remaining slots for every physical slot to have something | 
|  | * valid for hardware stuff. | 
|  | */ | 
|  | for (p = 0; p < VGXY61_NB_POLARITIES; p++) { | 
|  | if (phy2log[p] != ~0) | 
|  | continue; | 
|  | phy2log[p] = l; | 
|  | log2phy[l] = p; | 
|  | l++; | 
|  | } | 
|  | for (l = 0; l < l_nb + 1; l++) | 
|  | polarities[l] = ep.bus.mipi_csi2.lane_polarities[l]; | 
|  |  | 
|  | if (log2phy[0] != 0) { | 
|  | dev_err(&client->dev, "clk lane must be map to physical lane 0\n"); | 
|  | goto error_ep; | 
|  | } | 
|  | sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) + | 
|  | (polarities[3] << 12) + ((phy2log[3] - 1) << 10) + | 
|  | (polarities[2] <<  9) + ((phy2log[2] - 1) <<  7) + | 
|  | (polarities[1] <<  6) + ((phy2log[1] - 1) <<  4) + | 
|  | (polarities[0] <<  3) + | 
|  | l_nb; | 
|  | sensor->nb_of_lane = l_nb; | 
|  |  | 
|  | dev_dbg(&client->dev, "tx uses %d lanes", l_nb); | 
|  | for (i = 0; i < VGXY61_NB_POLARITIES; i++) { | 
|  | dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]); | 
|  | dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]); | 
|  | dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]); | 
|  | } | 
|  | dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl); | 
|  |  | 
|  | v4l2_fwnode_endpoint_free(&ep); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | error_ep: | 
|  | v4l2_fwnode_endpoint_free(&ep); | 
|  |  | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static int vgxy61_configure(struct vgxy61_dev *sensor) | 
|  | { | 
|  | u32 sensor_freq; | 
|  | u8 prediv, mult; | 
|  | u64 line_length; | 
|  | int ret = 0; | 
|  |  | 
|  | compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult); | 
|  | sensor_freq = (mult * sensor->clk_freq) / prediv; | 
|  | /* Frequency to data rate is 1:1 ratio for MIPI */ | 
|  | sensor->data_rate_in_mbps = sensor_freq; | 
|  | /* Video timing ISP path (pixel clock)  requires 804/5 mhz = 160 mhz */ | 
|  | sensor->pclk = sensor_freq / 5; | 
|  |  | 
|  | cci_read(sensor->regmap, VGXY61_REG_LINE_LENGTH, &line_length, &ret); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | sensor->line_length = (u16)line_length; | 
|  | cci_write(sensor->regmap, VGXY61_REG_EXT_CLOCK, sensor->clk_freq, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_CLK_PLL_PREDIV, prediv, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_CLK_SYS_PLL_MULT, mult, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_OIF_CTRL, sensor->oif_ctrl, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_FRAME_CONTENT_CTRL, 0, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_BYPASS_CTRL, 4, &ret); | 
|  | if (ret) | 
|  | return ret; | 
|  | vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity); | 
|  | /* Set pattern generator solid to middle value */ | 
|  | cci_write(sensor->regmap, VGXY61_REG_PATGEN_LONG_DATA_GR, 0x800, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_PATGEN_LONG_DATA_R, 0x800, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_PATGEN_LONG_DATA_B, 0x800, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_PATGEN_LONG_DATA_GB, 0x800, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_PATGEN_SHORT_DATA_GR, 0x800, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_PATGEN_SHORT_DATA_R, 0x800, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_PATGEN_SHORT_DATA_B, 0x800, &ret); | 
|  | cci_write(sensor->regmap, VGXY61_REG_PATGEN_SHORT_DATA_GB, 0x800, &ret); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_patch(struct vgxy61_dev *sensor) | 
|  | { | 
|  | struct i2c_client *client = sensor->i2c_client; | 
|  | u64 patch; | 
|  | int ret; | 
|  |  | 
|  | ret = vgxy61_write_array(sensor, VGXY61_REG_FWPATCH_START_ADDR, | 
|  | sizeof(patch_array), patch_array); | 
|  | cci_write(sensor->regmap, VGXY61_REG_STBY, 0x10, &ret); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = vgxy61_poll_reg(sensor, VGXY61_REG_STBY, 0, VGXY61_TIMEOUT_MS); | 
|  | cci_read(sensor->regmap, VGXY61_REG_FWPATCH_REVISION, &patch, &ret); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | if (patch != (VGXY61_FWPATCH_REVISION_MAJOR << 12) + | 
|  | (VGXY61_FWPATCH_REVISION_MINOR << 8) + | 
|  | VGXY61_FWPATCH_REVISION_MICRO) { | 
|  | dev_err(&client->dev, | 
|  | "bad patch version expected %d.%d.%d got %u.%u.%u\n", | 
|  | VGXY61_FWPATCH_REVISION_MAJOR, | 
|  | VGXY61_FWPATCH_REVISION_MINOR, | 
|  | VGXY61_FWPATCH_REVISION_MICRO, | 
|  | (u16)patch >> 12, ((u16)patch >> 8) & 0x0f, (u16)patch & 0xff); | 
|  | return -ENODEV; | 
|  | } | 
|  | dev_dbg(&client->dev, "patch %u.%u.%u applied\n", | 
|  | (u16)patch >> 12, ((u16)patch >> 8) & 0x0f, (u16)patch & 0xff); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor) | 
|  | { | 
|  | struct i2c_client *client = sensor->i2c_client; | 
|  | u64 device_rev; | 
|  | int ret; | 
|  |  | 
|  | ret = cci_read(sensor->regmap, VGXY61_REG_REVISION, &device_rev, NULL); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | switch (device_rev >> 8) { | 
|  | case 0xA: | 
|  | dev_dbg(&client->dev, "Cut1 detected\n"); | 
|  | dev_err(&client->dev, "Cut1 not supported by this driver\n"); | 
|  | return -ENODEV; | 
|  | case 0xB: | 
|  | dev_dbg(&client->dev, "Cut2 detected\n"); | 
|  | return 0; | 
|  | case 0xC: | 
|  | dev_dbg(&client->dev, "Cut3 detected\n"); | 
|  | return 0; | 
|  | default: | 
|  | dev_err(&client->dev, "Unable to detect cut version\n"); | 
|  | return -ENODEV; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int vgxy61_detect(struct vgxy61_dev *sensor) | 
|  | { | 
|  | struct i2c_client *client = sensor->i2c_client; | 
|  | u64 st, id = 0; | 
|  | int ret; | 
|  |  | 
|  | ret = cci_read(sensor->regmap, VGXY61_REG_MODEL_ID, &id, NULL); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) { | 
|  | dev_warn(&client->dev, "Unsupported sensor id %x\n", (u16)id); | 
|  | return -ENODEV; | 
|  | } | 
|  | dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", (u16)id); | 
|  | sensor->id = id; | 
|  |  | 
|  | ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY, | 
|  | VGXY61_TIMEOUT_MS); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = cci_read(sensor->regmap, VGXY61_REG_NVM, &st, NULL); | 
|  | if (ret < 0) | 
|  | return st; | 
|  | if (st != VGXY61_NVM_OK) | 
|  | dev_warn(&client->dev, "Bad nvm state got %u\n", (u8)st); | 
|  |  | 
|  | ret = vgxy61_detect_cut_version(sensor); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Power/clock management functions */ | 
|  | static int vgxy61_power_on(struct device *dev) | 
|  | { | 
|  | struct i2c_client *client = to_i2c_client(dev); | 
|  | struct v4l2_subdev *sd = i2c_get_clientdata(client); | 
|  | struct vgxy61_dev *sensor = to_vgxy61_dev(sd); | 
|  | int ret; | 
|  |  | 
|  | ret = regulator_bulk_enable(ARRAY_SIZE(vgxy61_supply_name), | 
|  | sensor->supplies); | 
|  | if (ret) { | 
|  | dev_err(&client->dev, "failed to enable regulators %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = clk_prepare_enable(sensor->xclk); | 
|  | if (ret) { | 
|  | dev_err(&client->dev, "failed to enable clock %d\n", ret); | 
|  | goto disable_bulk; | 
|  | } | 
|  |  | 
|  | if (sensor->reset_gpio) { | 
|  | ret = vgxy61_apply_reset(sensor); | 
|  | if (ret) { | 
|  | dev_err(&client->dev, "sensor reset failed %d\n", ret); | 
|  | goto disable_clock; | 
|  | } | 
|  | } | 
|  |  | 
|  | ret = vgxy61_detect(sensor); | 
|  | if (ret) { | 
|  | dev_err(&client->dev, "sensor detect failed %d\n", ret); | 
|  | goto disable_clock; | 
|  | } | 
|  |  | 
|  | ret = vgxy61_patch(sensor); | 
|  | if (ret) { | 
|  | dev_err(&client->dev, "sensor patch failed %d\n", ret); | 
|  | goto disable_clock; | 
|  | } | 
|  |  | 
|  | ret = vgxy61_configure(sensor); | 
|  | if (ret) { | 
|  | dev_err(&client->dev, "sensor configuration failed %d\n", ret); | 
|  | goto disable_clock; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | disable_clock: | 
|  | clk_disable_unprepare(sensor->xclk); | 
|  | disable_bulk: | 
|  | regulator_bulk_disable(ARRAY_SIZE(vgxy61_supply_name), | 
|  | sensor->supplies); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int vgxy61_power_off(struct device *dev) | 
|  | { | 
|  | struct i2c_client *client = to_i2c_client(dev); | 
|  | struct v4l2_subdev *sd = i2c_get_clientdata(client); | 
|  | struct vgxy61_dev *sensor = to_vgxy61_dev(sd); | 
|  |  | 
|  | clk_disable_unprepare(sensor->xclk); | 
|  | regulator_bulk_disable(ARRAY_SIZE(vgxy61_supply_name), | 
|  | sensor->supplies); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void vgxy61_fill_sensor_param(struct vgxy61_dev *sensor) | 
|  | { | 
|  | if (sensor->id == VG5761_MODEL_ID) { | 
|  | sensor->sensor_width = VGX761_WIDTH; | 
|  | sensor->sensor_height = VGX761_HEIGHT; | 
|  | sensor->sensor_modes = vgx761_mode_data; | 
|  | sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data); | 
|  | sensor->default_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE]; | 
|  | sensor->rot_term = VGX761_SHORT_ROT_TERM; | 
|  | } else if (sensor->id == VG5661_MODEL_ID) { | 
|  | sensor->sensor_width = VGX661_WIDTH; | 
|  | sensor->sensor_height = VGX661_HEIGHT; | 
|  | sensor->sensor_modes = vgx661_mode_data; | 
|  | sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data); | 
|  | sensor->default_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE]; | 
|  | sensor->rot_term = VGX661_SHORT_ROT_TERM; | 
|  | } else { | 
|  | /* Should never happen */ | 
|  | WARN_ON(true); | 
|  | } | 
|  | sensor->current_mode = sensor->default_mode; | 
|  | } | 
|  |  | 
|  | static int vgxy61_probe(struct i2c_client *client) | 
|  | { | 
|  | struct device *dev = &client->dev; | 
|  | struct fwnode_handle *handle; | 
|  | struct vgxy61_dev *sensor; | 
|  | int ret; | 
|  |  | 
|  | sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); | 
|  | if (!sensor) | 
|  | return -ENOMEM; | 
|  |  | 
|  | sensor->i2c_client = client; | 
|  | sensor->streaming = false; | 
|  | sensor->hdr = VGXY61_NO_HDR; | 
|  | sensor->expo_long = 200; | 
|  | sensor->expo_short = 0; | 
|  | sensor->hflip = false; | 
|  | sensor->vflip = false; | 
|  | sensor->analog_gain = 0; | 
|  | sensor->digital_gain = 256; | 
|  |  | 
|  | sensor->regmap = devm_cci_regmap_init_i2c(client, 16); | 
|  | if (IS_ERR(sensor->regmap)) { | 
|  | ret = PTR_ERR(sensor->regmap); | 
|  | return dev_err_probe(dev, ret, "Failed to init regmap\n"); | 
|  | } | 
|  |  | 
|  | handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); | 
|  | if (!handle) { | 
|  | dev_err(dev, "handle node not found\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | ret = vgxy61_tx_from_ep(sensor, handle); | 
|  | fwnode_handle_put(handle); | 
|  | if (ret) { | 
|  | dev_err(dev, "Failed to parse handle %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | sensor->xclk = devm_clk_get(dev, NULL); | 
|  | if (IS_ERR(sensor->xclk)) { | 
|  | dev_err(dev, "failed to get xclk\n"); | 
|  | return PTR_ERR(sensor->xclk); | 
|  | } | 
|  | sensor->clk_freq = clk_get_rate(sensor->xclk); | 
|  | if (sensor->clk_freq < 6 * HZ_PER_MHZ || | 
|  | sensor->clk_freq > 27 * HZ_PER_MHZ) { | 
|  | dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n", | 
|  | sensor->clk_freq / HZ_PER_MHZ); | 
|  | return -EINVAL; | 
|  | } | 
|  | sensor->gpios_polarity = | 
|  | device_property_read_bool(dev, "st,strobe-gpios-polarity"); | 
|  |  | 
|  | v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops); | 
|  | sensor->sd.internal_ops = &vgxy61_internal_ops; | 
|  | sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | | 
|  | V4L2_SUBDEV_FL_HAS_EVENTS; | 
|  | sensor->pad.flags = MEDIA_PAD_FL_SOURCE; | 
|  | sensor->sd.entity.ops = &vgxy61_subdev_entity_ops; | 
|  | sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; | 
|  |  | 
|  | sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", | 
|  | GPIOD_OUT_HIGH); | 
|  |  | 
|  | ret = vgxy61_get_regulators(sensor); | 
|  | if (ret) { | 
|  | dev_err(&client->dev, "failed to get regulators %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = vgxy61_power_on(dev); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | vgxy61_fill_sensor_param(sensor); | 
|  | vgxy61_fill_framefmt(sensor, sensor->current_mode, &sensor->fmt, | 
|  | VGXY61_MEDIA_BUS_FMT_DEF); | 
|  |  | 
|  | mutex_init(&sensor->lock); | 
|  |  | 
|  | ret = vgxy61_update_hdr(sensor, sensor->hdr); | 
|  | if (ret) | 
|  | goto error_power_off; | 
|  |  | 
|  | ret = vgxy61_init_controls(sensor); | 
|  | if (ret) { | 
|  | dev_err(&client->dev, "controls initialization failed %d\n", | 
|  | ret); | 
|  | goto error_power_off; | 
|  | } | 
|  |  | 
|  | ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); | 
|  | if (ret) { | 
|  | dev_err(&client->dev, "pads init failed %d\n", ret); | 
|  | goto error_handler_free; | 
|  | } | 
|  |  | 
|  | /* Enable runtime PM and turn off the device */ | 
|  | pm_runtime_set_active(dev); | 
|  | pm_runtime_enable(dev); | 
|  | pm_runtime_idle(dev); | 
|  |  | 
|  | ret = v4l2_async_register_subdev(&sensor->sd); | 
|  | if (ret) { | 
|  | dev_err(&client->dev, "async subdev register failed %d\n", ret); | 
|  | goto error_pm_runtime; | 
|  | } | 
|  |  | 
|  | pm_runtime_set_autosuspend_delay(&client->dev, 1000); | 
|  | pm_runtime_use_autosuspend(&client->dev); | 
|  |  | 
|  | dev_dbg(&client->dev, "vgxy61 probe successfully\n"); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | error_pm_runtime: | 
|  | pm_runtime_disable(&client->dev); | 
|  | pm_runtime_set_suspended(&client->dev); | 
|  | media_entity_cleanup(&sensor->sd.entity); | 
|  | error_handler_free: | 
|  | v4l2_ctrl_handler_free(sensor->sd.ctrl_handler); | 
|  | error_power_off: | 
|  | mutex_destroy(&sensor->lock); | 
|  | vgxy61_power_off(dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void vgxy61_remove(struct i2c_client *client) | 
|  | { | 
|  | struct v4l2_subdev *sd = i2c_get_clientdata(client); | 
|  | struct vgxy61_dev *sensor = to_vgxy61_dev(sd); | 
|  |  | 
|  | v4l2_async_unregister_subdev(&sensor->sd); | 
|  | mutex_destroy(&sensor->lock); | 
|  | media_entity_cleanup(&sensor->sd.entity); | 
|  |  | 
|  | pm_runtime_disable(&client->dev); | 
|  | if (!pm_runtime_status_suspended(&client->dev)) | 
|  | vgxy61_power_off(&client->dev); | 
|  | pm_runtime_set_suspended(&client->dev); | 
|  | } | 
|  |  | 
|  | static const struct of_device_id vgxy61_dt_ids[] = { | 
|  | { .compatible = "st,st-vgxy61" }, | 
|  | { /* sentinel */ } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, vgxy61_dt_ids); | 
|  |  | 
|  | static const struct dev_pm_ops vgxy61_pm_ops = { | 
|  | SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL) | 
|  | }; | 
|  |  | 
|  | static struct i2c_driver vgxy61_i2c_driver = { | 
|  | .driver = { | 
|  | .name  = "st-vgxy61", | 
|  | .of_match_table = vgxy61_dt_ids, | 
|  | .pm = &vgxy61_pm_ops, | 
|  | }, | 
|  | .probe = vgxy61_probe, | 
|  | .remove = vgxy61_remove, | 
|  | }; | 
|  |  | 
|  | module_i2c_driver(vgxy61_i2c_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>"); | 
|  | MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>"); | 
|  | MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>"); | 
|  | MODULE_DESCRIPTION("VGXY61 camera subdev driver"); | 
|  | MODULE_LICENSE("GPL"); |