// 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 <linux/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(0 x0000)
#define VG5661_MODEL_ID 0 x5661
#define VG5761_MODEL_ID 0 x5761
#define VGXY61_REG_REVISION CCI_REG16_LE(0 x0002)
#define VGXY61_REG_FWPATCH_REVISION CCI_REG16_LE(0 x0014)
#define VGXY61_REG_FWPATCH_START_ADDR CCI_REG8(0 x2000)
#define VGXY61_REG_SYSTEM_FSM CCI_REG8(0 x0020)
#define VGXY61_SYSTEM_FSM_SW_STBY 0 x03
#define VGXY61_SYSTEM_FSM_STREAMING 0 x04
#define VGXY61_REG_NVM CCI_REG8(0 x0023)
#define VGXY61_NVM_OK 0 x04
#define VGXY61_REG_STBY CCI_REG8(0 x0201)
#define VGXY61_STBY_NO_REQ 0
#define VGXY61_STBY_REQ_TMP_READ BIT(2 )
#define VGXY61_REG_STREAMING CCI_REG8(0 x0202)
#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(0 x0220)
#define VGXY61_REG_CLK_PLL_PREDIV CCI_REG8(0 x0224)
#define VGXY61_REG_CLK_SYS_PLL_MULT CCI_REG8(0 x0225)
#define VGXY61_REG_GPIO_0_CTRL CCI_REG8(0 x0236)
#define VGXY61_REG_GPIO_1_CTRL CCI_REG8(0 x0237)
#define VGXY61_REG_GPIO_2_CTRL CCI_REG8(0 x0238)
#define VGXY61_REG_GPIO_3_CTRL CCI_REG8(0 x0239)
#define VGXY61_REG_SIGNALS_POLARITY_CTRL CCI_REG8(0 x023b)
#define VGXY61_REG_LINE_LENGTH CCI_REG16_LE(0 x0300)
#define VGXY61_REG_ORIENTATION CCI_REG8(0 x0302)
#define VGXY61_REG_VT_CTRL CCI_REG8(0 x0304)
#define VGXY61_REG_FORMAT_CTRL CCI_REG8(0 x0305)
#define VGXY61_REG_OIF_CTRL CCI_REG16_LE(0 x0306)
#define VGXY61_REG_OIF_ROI0_CTRL CCI_REG8(0 x030a)
#define VGXY61_REG_ROI0_START_H CCI_REG16_LE(0 x0400)
#define VGXY61_REG_ROI0_START_V CCI_REG16_LE(0 x0402)
#define VGXY61_REG_ROI0_END_H CCI_REG16_LE(0 x0404)
#define VGXY61_REG_ROI0_END_V CCI_REG16_LE(0 x0406)
#define VGXY61_REG_PATGEN_CTRL CCI_REG32_LE(0 x0440)
#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(0 x0478)
#define VGXY61_REG_COARSE_EXPOSURE_LONG CCI_REG16_LE(0 x0500)
#define VGXY61_REG_COARSE_EXPOSURE_SHORT CCI_REG16_LE(0 x0504)
#define VGXY61_REG_ANALOG_GAIN CCI_REG8(0 x0508)
#define VGXY61_REG_DIGITAL_GAIN_LONG CCI_REG16_LE(0 x050a)
#define VGXY61_REG_DIGITAL_GAIN_SHORT CCI_REG16_LE(0 x0512)
#define VGXY61_REG_FRAME_LENGTH CCI_REG16_LE(0 x051a)
#define VGXY61_REG_SIGNALS_CTRL CCI_REG16_LE(0 x0522)
#define VGXY61_SIGNALS_GPIO_ID_SHIFT 4
#define VGXY61_REG_READOUT_CTRL CCI_REG8(0 x0530)
#define VGXY61_REG_HDR_CTRL CCI_REG8(0 x0532)
#define VGXY61_REG_PATGEN_LONG_DATA_GR CCI_REG16_LE(0 x092c)
#define VGXY61_REG_PATGEN_LONG_DATA_R CCI_REG16_LE(0 x092e)
#define VGXY61_REG_PATGEN_LONG_DATA_B CCI_REG16_LE(0 x0930)
#define VGXY61_REG_PATGEN_LONG_DATA_GB CCI_REG16_LE(0 x0932)
#define VGXY61_REG_PATGEN_SHORT_DATA_GR CCI_REG16_LE(0 x0950)
#define VGXY61_REG_PATGEN_SHORT_DATA_R CCI_REG16_LE(0 x0952)
#define VGXY61_REG_PATGEN_SHORT_DATA_B CCI_REG16_LE(0 x0954)
#define VGXY61_REG_PATGEN_SHORT_DATA_GB CCI_REG16_LE(0 x0956)
#define VGXY61_REG_BYPASS_CTRL CCI_REG8(0 x0a60)
#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[] = {
0 xbf, 0 x00, 0 x05, 0 x20, 0 x06, 0 x01, 0 xe0, 0 xe0, 0 x04, 0 x80, 0 xe6, 0 x45,
0 xed, 0 x6f, 0 xfe, 0 xff, 0 x14, 0 x80, 0 x1f, 0 x84, 0 x10, 0 x42, 0 x05, 0 x7c,
0 x01, 0 xc4, 0 x1e, 0 x80, 0 xb6, 0 x42, 0 x00, 0 xe0, 0 x1e, 0 x82, 0 x1e, 0 xc0,
0 x93, 0 xdd, 0 xc3, 0 xc1, 0 x0c, 0 x04, 0 x00, 0 xfa, 0 x86, 0 x0d, 0 x70, 0 xe1,
0 x04, 0 x98, 0 x15, 0 x00, 0 x28, 0 xe0, 0 x14, 0 x02, 0 x08, 0 xfc, 0 x15, 0 x40,
0 x28, 0 xe0, 0 x98, 0 x58, 0 xe0, 0 xef, 0 x04, 0 x98, 0 x0e, 0 x04, 0 x00, 0 xf0,
0 x15, 0 x00, 0 x28, 0 xe0, 0 x19, 0 xc8, 0 x15, 0 x40, 0 x28, 0 xe0, 0 xc6, 0 x41,
0 xfc, 0 xe0, 0 x14, 0 x80, 0 x1f, 0 x84, 0 x14, 0 x02, 0 xa0, 0 xfc, 0 x1e, 0 x80,
0 x14, 0 x80, 0 x14, 0 x02, 0 x80, 0 xfb, 0 x14, 0 x02, 0 xe0, 0 xfc, 0 x1e, 0 x80,
0 x14, 0 xc0, 0 x1f, 0 x84, 0 x14, 0 x02, 0 xa4, 0 xfc, 0 x1e, 0 xc0, 0 x14, 0 xc0,
0 x14, 0 x02, 0 x80, 0 xfb, 0 x14, 0 x02, 0 xe4, 0 xfc, 0 x1e, 0 xc0, 0 x0c, 0 x0c,
0 x00, 0 xf2, 0 x93, 0 xdd, 0 x86, 0 x00, 0 xf8, 0 xe0, 0 x04, 0 x80, 0 xc6, 0 x03,
0 x70, 0 xe1, 0 x0e, 0 x84, 0 x93, 0 xdd, 0 xc3, 0 xc1, 0 x0c, 0 x04, 0 x00, 0 xfa,
0 x6b, 0 x80, 0 x06, 0 x40, 0 x6c, 0 xe1, 0 x04, 0 x80, 0 x09, 0 x00, 0 xe0, 0 xe0,
0 x0b, 0 xa1, 0 x95, 0 x84, 0 x05, 0 x0c, 0 x1c, 0 xe0, 0 x86, 0 x02, 0 xf9, 0 x60,
0 xe0, 0 xcf, 0 x78, 0 x6e, 0 x80, 0 xef, 0 x25, 0 x0c, 0 x18, 0 xe0, 0 x05, 0 x4c,
0 x1c, 0 xe0, 0 x86, 0 x02, 0 xf9, 0 x60, 0 xe0, 0 xcf, 0 x0b, 0 x84, 0 xd8, 0 x6d,
0 x80, 0 xef, 0 x05, 0 x4c, 0 x18, 0 xe0, 0 x04, 0 xd8, 0 x0b, 0 xa5, 0 x95, 0 x84,
0 x05, 0 x0c, 0 x2c, 0 xe0, 0 x06, 0 x02, 0 x01, 0 x60, 0 xe0, 0 xce, 0 x18, 0 x6d,
0 x80, 0 xef, 0 x25, 0 x0c, 0 x30, 0 xe0, 0 x05, 0 x4c, 0 x2c, 0 xe0, 0 x06, 0 x02,
0 x01, 0 x60, 0 xe0, 0 xce, 0 x0b, 0 x84, 0 x78, 0 x6c, 0 x80, 0 xef, 0 x05, 0 x4c,
0 x30, 0 xe0, 0 x0c, 0 x0c, 0 x00, 0 xf2, 0 x93, 0 xdd, 0 x46, 0 x01, 0 x70, 0 xe1,
0 x08, 0 x80, 0 x0b, 0 xa1, 0 x08, 0 x5c, 0 x00, 0 xda, 0 x06, 0 x01, 0 x68, 0 xe1,
0 x04, 0 x80, 0 x4a, 0 x40, 0 x84, 0 xe0, 0 x08, 0 x5c, 0 x00, 0 x9a, 0 x06, 0 x01,
0 xe0, 0 xe0, 0 x04, 0 x80, 0 x15, 0 x00, 0 x60, 0 xe0, 0 x19, 0 xc4, 0 x15, 0 x40,
0 x60, 0 xe0, 0 x15, 0 x00, 0 x78, 0 xe0, 0 x19, 0 xc4, 0 x15, 0 x40, 0 x78, 0 xe0,
0 x93, 0 xdd, 0 xc3, 0 xc1, 0 x46, 0 x01, 0 x70, 0 xe1, 0 x08, 0 x80, 0 x0b, 0 xa1,
0 x08, 0 x5c, 0 x00, 0 xda, 0 x06, 0 x01, 0 x68, 0 xe1, 0 x04, 0 x80, 0 x4a, 0 x40,
0 x84, 0 xe0, 0 x08, 0 x5c, 0 x00, 0 x9a, 0 x06, 0 x01, 0 xe0, 0 xe0, 0 x14, 0 x80,
0 x25, 0 x02, 0 x54, 0 xe0, 0 x29, 0 xc4, 0 x25, 0 x42, 0 x54, 0 xe0, 0 x24, 0 x80,
0 x35, 0 x04, 0 x6c, 0 xe0, 0 x39, 0 xc4, 0 x35, 0 x44, 0 x6c, 0 xe0, 0 x25, 0 x02,
0 x64, 0 xe0, 0 x29, 0 xc4, 0 x25, 0 x42, 0 x64, 0 xe0, 0 x04, 0 x80, 0 x15, 0 x00,
0 x7c, 0 xe0, 0 x19, 0 xc4, 0 x15, 0 x40, 0 x7c, 0 xe0, 0 x93, 0 xdd, 0 xc3, 0 xc1,
0 x4c, 0 x04, 0 x7c, 0 xfa, 0 x86, 0 x40, 0 x98, 0 xe0, 0 x14, 0 x80, 0 x1b, 0 xa1,
0 x06, 0 x00, 0 x00, 0 xc0, 0 x08, 0 x42, 0 x38, 0 xdc, 0 x08, 0 x64, 0 xa0, 0 xef,
0 x86, 0 x42, 0 x3c, 0 xe0, 0 x68, 0 x49, 0 x80, 0 xef, 0 x6b, 0 x80, 0 x78, 0 x53,
0 xc8, 0 xef, 0 xc6, 0 x54, 0 x6c, 0 xe1, 0 x7b, 0 x80, 0 xb5, 0 x14, 0 x0c, 0 xf8,
0 x05, 0 x14, 0 x14, 0 xf8, 0 x1a, 0 xac, 0 x8a, 0 x80, 0 x0b, 0 x90, 0 x38, 0 x55,
0 x80, 0 xef, 0 x1a, 0 xae, 0 x17, 0 xc2, 0 x03, 0 x82, 0 x88, 0 x65, 0 x80, 0 xef,
0 x1b, 0 x80, 0 x0b, 0 x8e, 0 x68, 0 x65, 0 x80, 0 xef, 0 x9b, 0 x80, 0 x0b, 0 x8c,
0 x08, 0 x65, 0 x80, 0 xef, 0 x6b, 0 x80, 0 x0b, 0 x92, 0 x1b, 0 x8c, 0 x98, 0 x64,
0 x80, 0 xef, 0 x1a, 0 xec, 0 x9b, 0 x80, 0 x0b, 0 x90, 0 x95, 0 x54, 0 x10, 0 xe0,
0 xa8, 0 x53, 0 x80, 0 xef, 0 x1a, 0 xee, 0 x17, 0 xc2, 0 x03, 0 x82, 0 xf8, 0 x63,
0 x80, 0 xef, 0 x1b, 0 x80, 0 x0b, 0 x8e, 0 xd8, 0 x63, 0 x80, 0 xef, 0 x1b, 0 x8c,
0 x68, 0 x63, 0 x80, 0 xef, 0 x6b, 0 x80, 0 x0b, 0 x92, 0 x65, 0 x54, 0 x14, 0 xe0,
0 x08, 0 x65, 0 x84, 0 xef, 0 x68, 0 x63, 0 x80, 0 xef, 0 x7b, 0 x80, 0 x0b, 0 x8c,
0 xa8, 0 x64, 0 x84, 0 xef, 0 x08, 0 x63, 0 x80, 0 xef, 0 x14, 0 xe8, 0 x46, 0 x44,
0 x94, 0 xe1, 0 x24, 0 x88, 0 x4a, 0 x4e, 0 x04, 0 xe0, 0 x14, 0 xea, 0 x1a, 0 x04,
0 x08, 0 xe0, 0 x0a, 0 x40, 0 x84, 0 xed, 0 x0c, 0 x04, 0 x00, 0 xe2, 0 x4a, 0 x40,
0 x04, 0 xe0, 0 x19, 0 x16, 0 xc0, 0 xe0, 0 x0a, 0 x40, 0 x84, 0 xed, 0 x21, 0 x54,
0 x60, 0 xe0, 0 x0c, 0 x04, 0 x00, 0 xe2, 0 x1b, 0 xa5, 0 x0e, 0 xea, 0 x01, 0 x89,
0 x21, 0 x54, 0 x64, 0 xe0, 0 x7e, 0 xe8, 0 x65, 0 x82, 0 x1b, 0 xa7, 0 x26, 0 x00,
0 x00, 0 x80, 0 xa5, 0 x82, 0 x1b, 0 xa9, 0 x65, 0 x82, 0 x1b, 0 xa3, 0 x01, 0 x85,
0 x16, 0 x00, 0 x00, 0 xc0, 0 x01, 0 x54, 0 x04, 0 xf8, 0 x06, 0 xaa, 0 x01, 0 x83,
0 x06, 0 xa8, 0 x65, 0 x81, 0 x06, 0 xa8, 0 x01, 0 x54, 0 x04, 0 xf8, 0 x01, 0 x83,
0 x06, 0 xaa, 0 x09, 0 x14, 0 x18, 0 xf8, 0 x0b, 0 xa1, 0 x05, 0 x84, 0 xc6, 0 x42,
0 xd4, 0 xe0, 0 x14, 0 x84, 0 x01, 0 x83, 0 x01, 0 x54, 0 x60, 0 xe0, 0 x01, 0 x54,
0 x64, 0 xe0, 0 x0b, 0 x02, 0 x90, 0 xe0, 0 x10, 0 x02, 0 x90, 0 xe5, 0 x01, 0 x54,
0 x88, 0 xe0, 0 xb5, 0 x81, 0 xc6, 0 x40, 0 xd4, 0 xe0, 0 x14, 0 x80, 0 x0b, 0 x02,
0 xe0, 0 xe4, 0 x10, 0 x02, 0 x31, 0 x66, 0 x02, 0 xc0, 0 x01, 0 x54, 0 x88, 0 xe0,
0 x1a, 0 x84, 0 x29, 0 x14, 0 x10, 0 xe0, 0 x1c, 0 xaa, 0 x2b, 0 xa1, 0 xf5, 0 x82,
0 x25, 0 x14, 0 x10, 0 xf8, 0 x2b, 0 x04, 0 xa8, 0 xe0, 0 x20, 0 x44, 0 x0d, 0 x70,
0 x03, 0 xc0, 0 x2b, 0 xa1, 0 x04, 0 x00, 0 x80, 0 x9a, 0 x02, 0 x40, 0 x84, 0 x90,
0 x03, 0 x54, 0 x04, 0 x80, 0 x4c, 0 x0c, 0 x7c, 0 xf2, 0 x93, 0 xdd, 0 x00, 0 x00,
0 x02, 0 xa9, 0 x00, 0 x00, 0 x64, 0 x4a, 0 x40, 0 x00, 0 x08, 0 x2d, 0 x58, 0 xe0,
0 xa8, 0 x98, 0 x40, 0 x00, 0 x28, 0 x07, 0 x34, 0 xe0, 0 x05, 0 xb9, 0 x00, 0 x00,
0 x28, 0 x00, 0 x41, 0 x05, 0 x88, 0 x00, 0 x41, 0 x3c, 0 x98, 0 x00, 0 x41, 0 x52,
0 x04, 0 x01, 0 x41, 0 x79, 0 x3c, 0 x01, 0 x41, 0 x6a, 0 x3d, 0 xfe, 0 x00, 0 x00,
};
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
*/
402000000 ULL
};
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[] = {
0 x0, 0 x1, 0 x2, 0 x3, 0 x10, 0 x11, 0 x12, 0 x13
};
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[] = {0 x0, 0 x1, 0 x3};
u16 mask, val;
mask = 0 xf << (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 min3(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[] = {0 x1, 0 x4, 0 xa};
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,
0 xffff - 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,
0 xffff - 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 , 0 x1c, 1 ,
sensor->analog_gain);
v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0 , 0 xfff, 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,
0 xffff - 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, 0 x800, &ret);
cci_write(sensor->regmap, VGXY61_REG_PATGEN_LONG_DATA_R, 0 x800, &ret);
cci_write(sensor->regmap, VGXY61_REG_PATGEN_LONG_DATA_B, 0 x800, &ret);
cci_write(sensor->regmap, VGXY61_REG_PATGEN_LONG_DATA_GB, 0 x800, &ret);
cci_write(sensor->regmap, VGXY61_REG_PATGEN_SHORT_DATA_GR, 0 x800, &ret);
cci_write(sensor->regmap, VGXY61_REG_PATGEN_SHORT_DATA_R, 0 x800, &ret);
cci_write(sensor->regmap, VGXY61_REG_PATGEN_SHORT_DATA_B, 0 x800, &ret);
cci_write(sensor->regmap, VGXY61_REG_PATGEN_SHORT_DATA_GB, 0 x800, &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, 0 x10, &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 ) & 0 x0f, (u16)patch & 0 xff);
return -ENODEV;
}
dev_dbg(&client->dev, "patch %u.%u.%u applied\n" ,
(u16)patch >> 12 , ((u16)patch >> 8 ) & 0 x0f, (u16)patch & 0 xff);
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 0 xA:
dev_dbg(&client->dev, "Cut1 detected\n" );
dev_err(&client->dev, "Cut1 not supported by this driver\n" );
return -ENODEV;
case 0 xB:
dev_dbg(&client->dev, "Cut2 detected\n" );
return 0 ;
case 0 xC:
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 ret;
if (st != VGXY61_NVM_OK)
dev_warn(&client->dev, "Bad nvm state got %u\n" , (u8)st);
ret = vgxy61_detect_cut_version(sensor);
if (ret)
return ret;
return 0 ;
}
/* Power/clock management functions */
static int vgxy61_power_on(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
int ret;
ret = regulator_bulk_enable(ARRAY_SIZE(vgxy61_supply_name),
sensor->supplies);
if (ret) {
dev_err(&client->dev, "failed to enable regulators %d\n" , ret);
return ret;
}
ret = clk_prepare_enable(sensor->xclk);
if (ret) {
dev_err(&client->dev, "failed to enable clock %d\n" , ret);
goto disable_bulk;
}
if (sensor->reset_gpio) {
ret = vgxy61_apply_reset(sensor);
if (ret) {
dev_err(&client->dev, "sensor reset failed %d\n" , ret);
goto disable_clock;
}
}
ret = vgxy61_detect(sensor);
if (ret) {
dev_err(&client->dev, "sensor detect failed %d\n" , ret);
goto disable_clock;
}
ret = vgxy61_patch(sensor);
if (ret) {
dev_err(&client->dev, "sensor patch failed %d\n" , ret);
goto disable_clock;
}
ret = vgxy61_configure(sensor);
if (ret) {
dev_err(&client->dev, "sensor configuration failed %d\n" , ret);
goto disable_clock;
}
return 0 ;
disable_clock:
clk_disable_unprepare(sensor->xclk);
disable_bulk:
regulator_bulk_disable(ARRAY_SIZE(vgxy61_supply_name),
sensor->supplies);
return ret;
}
static int vgxy61_power_off(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
clk_disable_unprepare(sensor->xclk);
regulator_bulk_disable(ARRAY_SIZE(vgxy61_supply_name),
sensor->supplies);
return 0 ;
}
static void vgxy61_fill_sensor_param(struct vgxy61_dev *sensor)
{
if (sensor->id == VG5761_MODEL_ID) {
sensor->sensor_width = VGX761_WIDTH;
sensor->sensor_height = VGX761_HEIGHT;
sensor->sensor_modes = vgx761_mode_data;
sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data);
sensor->default_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE];
sensor->rot_term = VGX761_SHORT_ROT_TERM;
} else if (sensor->id == VG5661_MODEL_ID) {
sensor->sensor_width = VGX661_WIDTH;
sensor->sensor_height = VGX661_HEIGHT;
sensor->sensor_modes = vgx661_mode_data;
sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data);
sensor->default_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE];
sensor->rot_term = VGX661_SHORT_ROT_TERM;
} else {
/* Should never happen */
WARN_ON(true );
}
sensor->current_mode = sensor->default_mode;
}
static int vgxy61_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct fwnode_handle *handle;
struct vgxy61_dev *sensor;
int ret;
sensor = devm_kzalloc(dev, sizeof (*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
sensor->i2c_client = client;
sensor->streaming = false ;
sensor->hdr = VGXY61_NO_HDR;
sensor->expo_long = 200 ;
sensor->expo_short = 0 ;
sensor->hflip = false ;
sensor->vflip = false ;
sensor->analog_gain = 0 ;
sensor->digital_gain = 256 ;
sensor->regmap = devm_cci_regmap_init_i2c(client, 16 );
if (IS_ERR(sensor->regmap)) {
ret = PTR_ERR(sensor->regmap);
return dev_err_probe(dev, ret, "Failed to init regmap\n" );
}
handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0 , 0 , 0 );
if (!handle) {
dev_err(dev, "handle node not found\n" );
return -EINVAL;
}
ret = vgxy61_tx_from_ep(sensor, handle);
fwnode_handle_put(handle);
if (ret) {
dev_err(dev, "Failed to parse handle %d\n" , ret);
return ret;
}
sensor->xclk = devm_clk_get(dev, NULL);
if (IS_ERR(sensor->xclk)) {
dev_err(dev, "failed to get xclk\n" );
return PTR_ERR(sensor->xclk);
}
sensor->clk_freq = clk_get_rate(sensor->xclk);
if (sensor->clk_freq < 6 * HZ_PER_MHZ ||
sensor->clk_freq > 27 * HZ_PER_MHZ) {
dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n" ,
sensor->clk_freq / HZ_PER_MHZ);
return -EINVAL;
}
sensor->gpios_polarity =
device_property_read_bool(dev, "st,strobe-gpios-polarity" );
v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops);
sensor->sd.internal_ops = &vgxy61_internal_ops;
sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
V4L2_SUBDEV_FL_HAS_EVENTS;
sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
sensor->sd.entity.ops = &vgxy61_subdev_entity_ops;
sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset" ,
GPIOD_OUT_HIGH);
ret = vgxy61_get_regulators(sensor);
if (ret) {
dev_err(&client->dev, "failed to get regulators %d\n" , ret);
return ret;
}
ret = vgxy61_power_on(dev);
if (ret)
return ret;
vgxy61_fill_sensor_param(sensor);
vgxy61_fill_framefmt(sensor, sensor->current_mode, &sensor->fmt,
VGXY61_MEDIA_BUS_FMT_DEF);
mutex_init(&sensor->lock);
ret = vgxy61_update_hdr(sensor, sensor->hdr);
if (ret)
goto error_power_off;
ret = vgxy61_init_controls(sensor);
if (ret) {
dev_err(&client->dev, "controls initialization failed %d\n" ,
ret);
goto error_power_off;
}
ret = media_entity_pads_init(&sensor->sd.entity, 1 , &sensor->pad);
if (ret) {
dev_err(&client->dev, "pads init failed %d\n" , ret);
goto error_handler_free;
}
/* Enable runtime PM and turn off the device */
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_runtime_idle(dev);
ret = v4l2_async_register_subdev(&sensor->sd);
if (ret) {
dev_err(&client->dev, "async subdev register failed %d\n" , ret);
goto error_pm_runtime;
}
pm_runtime_set_autosuspend_delay(&client->dev, 1000 );
pm_runtime_use_autosuspend(&client->dev);
dev_dbg(&client->dev, "vgxy61 probe successfully\n" );
return 0 ;
error_pm_runtime:
pm_runtime_disable(&client->dev);
pm_runtime_set_suspended(&client->dev);
media_entity_cleanup(&sensor->sd.entity);
error_handler_free:
v4l2_ctrl_handler_free(sensor->sd.ctrl_handler);
error_power_off:
mutex_destroy(&sensor->lock);
vgxy61_power_off(dev);
return ret;
}
static void vgxy61_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct vgxy61_dev *sensor = to_vgxy61_dev(sd);
v4l2_async_unregister_subdev(&sensor->sd);
mutex_destroy(&sensor->lock);
media_entity_cleanup(&sensor->sd.entity);
pm_runtime_disable(&client->dev);
if (!pm_runtime_status_suspended(&client->dev))
vgxy61_power_off(&client->dev);
pm_runtime_set_suspended(&client->dev);
}
static const struct of_device_id vgxy61_dt_ids[] = {
{ .compatible = "st,st-vgxy61" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, vgxy61_dt_ids);
static const struct dev_pm_ops vgxy61_pm_ops = {
SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL)
};
static struct i2c_driver vgxy61_i2c_driver = {
.driver = {
.name = "vgxy61" ,
.of_match_table = vgxy61_dt_ids,
.pm = &vgxy61_pm_ops,
},
.probe = vgxy61_probe,
.remove = vgxy61_remove,
};
module_i2c_driver(vgxy61_i2c_driver);
MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>" );
MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>" );
MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>" );
MODULE_DESCRIPTION("VGXY61 camera subdev driver" );
MODULE_LICENSE("GPL" );
Messung V0.5 in Prozent C=94 H=94 G=93
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-06-08)
¤
*© Formatika GbR, Deutschland