From 96646f3a5a5dd32232c337cf468830113be4e48c Mon Sep 17 00:00:00 2001 From: Fritz Koenig Date: Wed, 29 May 2024 15:22:24 -0700 Subject: Documentation: media: Fix v4l2_ctrl_vp8_frame struct Description and control were out of sync. Signed-off-by: Fritz Koenig Signed-off-by: Hans Verkuil --- Documentation/userspace-api/media/v4l/ext-ctrls-codec-stateless.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Documentation/userspace-api') diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-codec-stateless.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-codec-stateless.rst index 786127b1e206..22bde00d42df 100644 --- a/Documentation/userspace-api/media/v4l/ext-ctrls-codec-stateless.rst +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-codec-stateless.rst @@ -971,8 +971,8 @@ FWHT Flags - ``horizontal_scale`` - Horizontal scaling factor. * - __u8 - - ``vertical_scaling factor`` - - Vertical scale. + - ``vertical_scale`` + - Vertical scaling factor. * - __u8 - ``version`` - Bitstream version. -- cgit v1.2.3 From 62096c48394b49e326056a62780fb67b60edde91 Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Mon, 6 May 2024 17:49:16 +0900 Subject: media: v4l2-ctrls: Add average QP control Add a control V4L2_CID_MPEG_VIDEO_AVERAGE_QP to report the average QP value of the current encoded frame. The value applies to the last dequeued capture buffer. Signed-off-by: Ming Qian Reviewed-by: Hans Verkuil Signed-off-by: Sebastian Fricke Signed-off-by: Hans Verkuil --- Documentation/userspace-api/media/v4l/ext-ctrls-codec.rst | 14 ++++++++++++++ drivers/media/v4l2-core/v4l2-ctrls-defs.c | 5 +++++ include/uapi/linux/v4l2-controls.h | 2 ++ 3 files changed, 21 insertions(+) (limited to 'Documentation/userspace-api') diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-codec.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-codec.rst index 2a165ae063fb..4a379bd9e3fb 100644 --- a/Documentation/userspace-api/media/v4l/ext-ctrls-codec.rst +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-codec.rst @@ -1653,6 +1653,20 @@ enum v4l2_mpeg_video_h264_hierarchical_coding_type - Quantization parameter for a P frame for FWHT. Valid range: from 1 to 31. +``V4L2_CID_MPEG_VIDEO_AVERAGE_QP (integer)`` + This read-only control returns the average QP value of the currently + encoded frame. The value applies to the last dequeued capture buffer + (VIDIOC_DQBUF). Its valid range depends on the encoding format and parameters. + For H264, its valid range is from 0 to 51. + For HEVC, its valid range is from 0 to 51 for 8 bit and + from 0 to 63 for 10 bit. + For H263 and MPEG4, its valid range is from 1 to 31. + For VP8, its valid range is from 0 to 127. + For VP9, its valid range is from 0 to 255. + If the codec's MIN_QP and MAX_QP are set, then the QP will meet both requirements. + Codecs need to always use the specified range, rather then a HW custom range. + Applicable to encoders + .. raw:: latex \normalsize diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index 8696eb1cdd61..1ea52011247a 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -970,6 +970,7 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_MPEG_VIDEO_LTR_COUNT: return "LTR Count"; case V4L2_CID_MPEG_VIDEO_FRAME_LTR_INDEX: return "Frame LTR Index"; case V4L2_CID_MPEG_VIDEO_USE_LTR_FRAMES: return "Use LTR Frames"; + case V4L2_CID_MPEG_VIDEO_AVERAGE_QP: return "Average QP Value"; case V4L2_CID_FWHT_I_FRAME_QP: return "FWHT I-Frame QP Value"; case V4L2_CID_FWHT_P_FRAME_QP: return "FWHT P-Frame QP Value"; @@ -1507,6 +1508,10 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, *max = 0xffffffffffffLL; *step = 1; break; + case V4L2_CID_MPEG_VIDEO_AVERAGE_QP: + *type = V4L2_CTRL_TYPE_INTEGER; + *flags |= V4L2_CTRL_FLAG_READ_ONLY; + break; case V4L2_CID_PIXEL_RATE: *type = V4L2_CTRL_TYPE_INTEGER64; *flags |= V4L2_CTRL_FLAG_READ_ONLY; diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 99c3f5e99da7..974fd254e573 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -898,6 +898,8 @@ enum v4l2_mpeg_video_av1_level { V4L2_MPEG_VIDEO_AV1_LEVEL_7_3 = 23 }; +#define V4L2_CID_MPEG_VIDEO_AVERAGE_QP (V4L2_CID_CODEC_BASE + 657) + /* MPEG-class control IDs specific to the CX2341x driver as defined by V4L2 */ #define V4L2_CID_CODEC_CX2341X_BASE (V4L2_CTRL_CLASS_CODEC | 0x1000) #define V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE (V4L2_CID_CODEC_CX2341X_BASE+0) -- cgit v1.2.3 From 15765ff7147e9ca45079c1a5e37c35052d57381a Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Wed, 12 Jun 2024 10:21:14 +0200 Subject: media: i2c: vgxy61: Fix device name Rename 'st-vgxy61' to 'vgxy61', dropping the vendor prefix to follow the same naming scheme as the vast majority of device drivers. The device tree binding does not fall into binding rename exceptions and therefore must not be changed. Keep its legacy name. Signed-off-by: Benjamin Mugnier Acked-by: Krzysztof Kozlowski Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../userspace-api/media/drivers/index.rst | 2 +- .../userspace-api/media/drivers/st-vgxy61.rst | 25 - .../userspace-api/media/drivers/vgxy61.rst | 25 + MAINTAINERS | 6 +- drivers/media/i2c/Kconfig | 2 +- drivers/media/i2c/Makefile | 2 +- drivers/media/i2c/st-vgxy61.c | 1895 -------------------- drivers/media/i2c/vgxy61.c | 1895 ++++++++++++++++++++ 8 files changed, 1926 insertions(+), 1926 deletions(-) delete mode 100644 Documentation/userspace-api/media/drivers/st-vgxy61.rst create mode 100644 Documentation/userspace-api/media/drivers/vgxy61.rst delete mode 100644 drivers/media/i2c/st-vgxy61.c create mode 100644 drivers/media/i2c/vgxy61.c (limited to 'Documentation/userspace-api') diff --git a/Documentation/userspace-api/media/drivers/index.rst b/Documentation/userspace-api/media/drivers/index.rst index 2252063593bf..d706cb47b112 100644 --- a/Documentation/userspace-api/media/drivers/index.rst +++ b/Documentation/userspace-api/media/drivers/index.rst @@ -35,6 +35,6 @@ For more details see the file COPYING in the source distribution of Linux. max2175 npcm-video omap3isp-uapi - st-vgxy61 thp7312 uvcvideo + vgxy61 diff --git a/Documentation/userspace-api/media/drivers/st-vgxy61.rst b/Documentation/userspace-api/media/drivers/st-vgxy61.rst deleted file mode 100644 index 17ac15afa77c..000000000000 --- a/Documentation/userspace-api/media/drivers/st-vgxy61.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 - -ST VGXY61 camera sensor driver -============================== - -The ST VGXY61 driver implements the following controls: - -``V4L2_CID_HDR_SENSOR_MODE`` -------------------------------- - Change the sensor HDR mode. A HDR picture is obtained by merging two - captures of the same scene using two different exposure periods. - -.. flat-table:: - :header-rows: 0 - :stub-columns: 0 - :widths: 1 4 - - * - HDR linearize - - The merger outputs a long exposure capture as long as it is not - saturated. - * - HDR subtraction - - This involves subtracting the short exposure frame from the long - exposure frame. - * - No HDR - - This mode is used for standard dynamic range (SDR) exposures. diff --git a/Documentation/userspace-api/media/drivers/vgxy61.rst b/Documentation/userspace-api/media/drivers/vgxy61.rst new file mode 100644 index 000000000000..17ac15afa77c --- /dev/null +++ b/Documentation/userspace-api/media/drivers/vgxy61.rst @@ -0,0 +1,25 @@ +.. SPDX-License-Identifier: GPL-2.0 + +ST VGXY61 camera sensor driver +============================== + +The ST VGXY61 driver implements the following controls: + +``V4L2_CID_HDR_SENSOR_MODE`` +------------------------------- + Change the sensor HDR mode. A HDR picture is obtained by merging two + captures of the same scene using two different exposure periods. + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + :widths: 1 4 + + * - HDR linearize + - The merger outputs a long exposure capture as long as it is not + saturated. + * - HDR subtraction + - This involves subtracting the short exposure frame from the long + exposure frame. + * - No HDR + - This mode is used for standard dynamic range (SDR) exposures. diff --git a/MAINTAINERS b/MAINTAINERS index 9caf669234f2..313624f0bd7e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21212,8 +21212,8 @@ L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml -F: Documentation/userspace-api/media/drivers/st-vgxy61.rst -F: drivers/media/i2c/st-vgxy61.c +F: Documentation/userspace-api/media/drivers/vgxy61.rst +F: drivers/media/i2c/vgxy61.c ST VL53L0X ToF RANGER(I2C) IIO DRIVER M: Song Qiang @@ -23549,7 +23549,7 @@ F: drivers/media/i2c/mt* F: drivers/media/i2c/og* F: drivers/media/i2c/ov* F: drivers/media/i2c/s5* -F: drivers/media/i2c/st-vgxy61.c +F: drivers/media/i2c/vgxy61.c VF610 NAND DRIVER M: Stefan Agner diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 5498128773c7..f336b622e5b8 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -670,7 +670,7 @@ config VIDEO_S5K6A3 This is a V4L2 sensor driver for Samsung S5K6A3 raw camera sensor. -config VIDEO_ST_VGXY61 +config VIDEO_VGXY61 tristate "ST VGXY61 sensor support" select V4L2_CCI_I2C depends on OF && GPIOLIB diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index d1403e3273ca..c36f5d1d7f18 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -127,7 +127,6 @@ obj-$(CONFIG_VIDEO_SAA717X) += saa717x.o obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o -obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o obj-$(CONFIG_VIDEO_TC358743) += tc358743.o obj-$(CONFIG_VIDEO_TC358746) += tc358746.o obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o @@ -151,6 +150,7 @@ obj-$(CONFIG_VIDEO_TW9910) += tw9910.o obj-$(CONFIG_VIDEO_UDA1342) += uda1342.o obj-$(CONFIG_VIDEO_UPD64031A) += upd64031a.o obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o +obj-$(CONFIG_VIDEO_VGXY61) += vgxy61.o obj-$(CONFIG_VIDEO_VP27SMPX) += vp27smpx.o obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o obj-$(CONFIG_VIDEO_WM8739) += wm8739.o diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c deleted file mode 100644 index b9e7c57027b1..000000000000 --- a/drivers/media/i2c/st-vgxy61.c +++ /dev/null @@ -1,1895 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Driver for VGXY61 global shutter sensor family driver - * - * Copyright (C) 2022 STMicroelectronics SA - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#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 "); -MODULE_AUTHOR("Mickael Guene "); -MODULE_AUTHOR("Sylvain Petinot "); -MODULE_DESCRIPTION("VGXY61 camera subdev driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/vgxy61.c b/drivers/media/i2c/vgxy61.c new file mode 100644 index 000000000000..30378e962016 --- /dev/null +++ b/drivers/media/i2c/vgxy61.c @@ -0,0 +1,1895 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for VGXY61 global shutter sensor family driver + * + * Copyright (C) 2022 STMicroelectronics SA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = "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 "); +MODULE_AUTHOR("Mickael Guene "); +MODULE_AUTHOR("Sylvain Petinot "); +MODULE_DESCRIPTION("VGXY61 camera subdev driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From dd6aa1e1de44e9bb4b0f5d96dbf3f84d1e3f35b1 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Wed, 26 Jun 2024 20:14:31 +0200 Subject: media: uapi: pixfmt-luma: Document MIPI CSI-2 packing The Y10P, Y12P and Y14P format variants are packed according to the RAW10, RAW12 and RAW14 formats as defined by the MIPI CSI-2 specification. Document it. Signed-off-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Reviewed-by: Naushir Patuck Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- Documentation/userspace-api/media/v4l/pixfmt-yuv-luma.rst | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'Documentation/userspace-api') diff --git a/Documentation/userspace-api/media/v4l/pixfmt-yuv-luma.rst b/Documentation/userspace-api/media/v4l/pixfmt-yuv-luma.rst index b3c5779521d8..f02e6cf3516a 100644 --- a/Documentation/userspace-api/media/v4l/pixfmt-yuv-luma.rst +++ b/Documentation/userspace-api/media/v4l/pixfmt-yuv-luma.rst @@ -209,3 +209,7 @@ are often referred to as greyscale formats. For Y012 and Y12 formats, Y012 places its data in the 12 high bits, with padding zeros in the 4 low bits, in contrast to the Y12 format, which has its padding located in the most significant bits of the 16 bit word. + + The 'P' variations of the Y10, Y12 and Y14 formats are packed according to + the RAW10, RAW12 and RAW14 packing scheme as defined by the MIPI CSI-2 + specification. -- cgit v1.2.3 From d1741141d03f8f5535adaf5a2d5000da3be9fe90 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Wed, 26 Jun 2024 20:14:32 +0200 Subject: media: uapi: Add a pixel format for BGR48 and RGB48 Add BGR48 and RGB48 16-bit per component image formats. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Naushir Patuck Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../userspace-api/media/v4l/pixfmt-rgb.rst | 54 ++++++++++++++++++++++ drivers/media/v4l2-core/v4l2-common.c | 2 + drivers/media/v4l2-core/v4l2-ioctl.c | 2 + include/uapi/linux/videodev2.h | 2 + 4 files changed, 60 insertions(+) (limited to 'Documentation/userspace-api') diff --git a/Documentation/userspace-api/media/v4l/pixfmt-rgb.rst b/Documentation/userspace-api/media/v4l/pixfmt-rgb.rst index b71b80d634d6..5ed4d62df909 100644 --- a/Documentation/userspace-api/media/v4l/pixfmt-rgb.rst +++ b/Documentation/userspace-api/media/v4l/pixfmt-rgb.rst @@ -996,6 +996,60 @@ arranged in little endian order. \normalsize +16 Bits Per Component +===================== + +These formats store an RGB triplet in six bytes, with 16 bits per component +stored in memory in little endian byte order. They are named based on the order +of the RGB components as stored in memory. For instance, RGB48 stores R\ +:sub:`7:0` and R\ :sub:`15:8` in bytes 0 and 1 respectively. This differs from +the DRM format nomenclature that instead uses the order of components as seen in +the 48-bits little endian word. + +.. raw:: latex + + \small + +.. flat-table:: RGB Formats With 16 Bits Per Component + :header-rows: 1 + + * - Identifier + - Code + - Byte 0 + - Byte 1 + - Byte 2 + - Byte 3 + - Byte 4 + - Byte 5 + + * .. _V4L2-PIX-FMT-BGR48: + + - ``V4L2_PIX_FMT_BGR48`` + - 'BGR6' + + - B\ :sub:`7-0` + - B\ :sub:`15-8` + - G\ :sub:`7-0` + - G\ :sub:`15-8` + - R\ :sub:`7-0` + - R\ :sub:`15-8` + + * .. _V4L2-PIX-FMT-RGB48: + + - ``V4L2_PIX_FMT_RGB48`` + - 'RGB6' + + - R\ :sub:`7-0` + - R\ :sub:`15-8` + - G\ :sub:`7-0` + - G\ :sub:`15-8` + - B\ :sub:`7-0` + - B\ :sub:`15-8` + +.. raw:: latex + + \normalsize + Deprecated RGB Formats ====================== diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index 4165c815faef..0a2f4f0d0a07 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -253,6 +253,8 @@ const struct v4l2_format_info *v4l2_format_info(u32 format) { .format = V4L2_PIX_FMT_RGB555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_BGR666, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_BGR48_12, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_BGR48, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_RGB48, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_ABGR64_12, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_RGBA1010102, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_RGBX1010102, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 4c76d17b4629..ee97643ff3a7 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1307,6 +1307,8 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_RGBX1010102: descr = "32-bit RGBX 10-10-10-2"; break; case V4L2_PIX_FMT_RGBA1010102: descr = "32-bit RGBA 10-10-10-2"; break; case V4L2_PIX_FMT_ARGB2101010: descr = "32-bit ARGB 2-10-10-10"; break; + case V4L2_PIX_FMT_BGR48: descr = "48-bit BGR 16-16-16"; break; + case V4L2_PIX_FMT_RGB48: descr = "48-bit RGB 16-16-16"; break; case V4L2_PIX_FMT_BGR48_12: descr = "12-bit Depth BGR"; break; case V4L2_PIX_FMT_ABGR64_12: descr = "12-bit Depth BGRA"; break; case V4L2_PIX_FMT_GREY: descr = "8-bit Greyscale"; break; diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index fe6b67e83751..dd6876380fe3 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -582,6 +582,8 @@ struct v4l2_pix_format { /* RGB formats (6 or 8 bytes per pixel) */ #define V4L2_PIX_FMT_BGR48_12 v4l2_fourcc('B', '3', '1', '2') /* 48 BGR 12-bit per component */ +#define V4L2_PIX_FMT_BGR48 v4l2_fourcc('B', 'G', 'R', '6') /* 48 BGR 16-bit per component */ +#define V4L2_PIX_FMT_RGB48 v4l2_fourcc('R', 'G', 'B', '6') /* 48 RGB 16-bit per component */ #define V4L2_PIX_FMT_ABGR64_12 v4l2_fourcc('B', '4', '1', '2') /* 64 BGRA 12-bit per component */ /* Grey formats */ -- cgit v1.2.3 From 8f6c2202222faffa0959929d8cd9090254a60831 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Wed, 26 Jun 2024 20:14:34 +0200 Subject: media: uapi: Add meta pixel format for PiSP BE config Add format description for the PiSP Back End configuration parameter buffer. Signed-off-by: Jacopo Mondi Reviewed-by: Naushir Patuck Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../userspace-api/media/v4l/meta-formats.rst | 1 + .../userspace-api/media/v4l/metafmt-pisp-be.rst | 56 ++++++++++++++++++++++ drivers/media/v4l2-core/v4l2-ioctl.c | 1 + include/uapi/linux/videodev2.h | 3 ++ 4 files changed, 61 insertions(+) create mode 100644 Documentation/userspace-api/media/v4l/metafmt-pisp-be.rst (limited to 'Documentation/userspace-api') diff --git a/Documentation/userspace-api/media/v4l/meta-formats.rst b/Documentation/userspace-api/media/v4l/meta-formats.rst index c23aac823d2c..c6e56b5888bc 100644 --- a/Documentation/userspace-api/media/v4l/meta-formats.rst +++ b/Documentation/userspace-api/media/v4l/meta-formats.rst @@ -15,6 +15,7 @@ These formats are used for the :ref:`metadata` interface only. metafmt-d4xx metafmt-generic metafmt-intel-ipu3 + metafmt-pisp-be metafmt-rkisp1 metafmt-uvc metafmt-vivid diff --git a/Documentation/userspace-api/media/v4l/metafmt-pisp-be.rst b/Documentation/userspace-api/media/v4l/metafmt-pisp-be.rst new file mode 100644 index 000000000000..3281fe366c86 --- /dev/null +++ b/Documentation/userspace-api/media/v4l/metafmt-pisp-be.rst @@ -0,0 +1,56 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. _v4l2-meta-fmt-rpi-be-cfg: + +************************ +V4L2_META_FMT_RPI_BE_CFG +************************ + +Raspberry Pi PiSP Back End configuration format +=============================================== + +The Raspberry Pi PiSP Back End memory-to-memory image signal processor is +configured by userspace by providing a buffer of configuration parameters +to the `pispbe-config` output video device node using the +:c:type:`v4l2_meta_format` interface. + +The PiSP Back End processes images in tiles, and its configuration requires +specifying two different sets of parameters by populating the members of +:c:type:`pisp_be_tiles_config` defined in the ``pisp_be_config.h`` header file. + +The `Raspberry Pi PiSP technical specification +`_ +provide detailed description of the ISP back end configuration and programming +model. + +Global configuration data +------------------------- + +The global configuration data describe how the pixels in a particular image are +to be processed and is therefore shared across all the tiles of the image. So +for example, LSC (Lens Shading Correction) or Denoise parameters would be common +across all tiles from the same frame. + +Global configuration data are passed to the ISP by populating the member of +:c:type:`pisp_be_config`. + +Tile parameters +--------------- + +As the ISP processes images in tiles, each set of tiles parameters describe how +a single tile in an image is going to be processed. A single set of tile +parameters consist of 160 bytes of data and to process a batch of tiles several +sets of tiles parameters are required. + +Tiles parameters are passed to the ISP by populating the member of +``pisp_tile`` and the ``num_tiles`` fields of :c:type:`pisp_be_tiles_config`. + +Raspberry Pi PiSP Back End uAPI data types +========================================== + +This section describes the data types exposed to userspace by the Raspberry Pi +PiSP Back End. The section is informative only, for a detailed description of +each field refer to the `Raspberry Pi PiSP technical specification +`_. + +.. kernel-doc:: include/uapi/linux/media/raspberrypi/pisp_be_config.h diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index ee97643ff3a7..7c684788997c 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1465,6 +1465,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_Y210: descr = "10-bit YUYV Packed"; break; case V4L2_PIX_FMT_Y212: descr = "12-bit YUYV Packed"; break; case V4L2_PIX_FMT_Y216: descr = "16-bit YUYV Packed"; break; + case V4L2_META_FMT_RPI_BE_CFG: descr = "RPi PiSP BE Config format"; break; case V4L2_META_FMT_GENERIC_8: descr = "8-bit Generic Metadata"; break; case V4L2_META_FMT_GENERIC_CSI2_10: descr = "8-bit Generic Meta, 10b CSI-2"; break; case V4L2_META_FMT_GENERIC_CSI2_12: descr = "8-bit Generic Meta, 12b CSI-2"; break; diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index dd6876380fe3..96fc0456081e 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -843,6 +843,9 @@ struct v4l2_pix_format { #define V4L2_META_FMT_RK_ISP1_PARAMS v4l2_fourcc('R', 'K', '1', 'P') /* Rockchip ISP1 3A Parameters */ #define V4L2_META_FMT_RK_ISP1_STAT_3A v4l2_fourcc('R', 'K', '1', 'S') /* Rockchip ISP1 3A Statistics */ +/* Vendor specific - used for RaspberryPi PiSP */ +#define V4L2_META_FMT_RPI_BE_CFG v4l2_fourcc('R', 'P', 'B', 'C') /* PiSP BE configuration */ + #ifdef __KERNEL__ /* * Line-based metadata formats. Remember to update v4l_fill_fmtdesc() when -- cgit v1.2.3 From d260c1224786c870edbae09ef6ecf5f35361821e Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Wed, 26 Jun 2024 20:14:35 +0200 Subject: media: uapi: Add PiSP Compressed RAW Bayer formats Add Raspberry Pi compressed RAW Bayer formats. The compression algorithm description is provided by Nick Hollinghurst from Raspberry Pi. Signed-off-by: Jacopo Mondi Reviewed-by: Naushir Patuck Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../userspace-api/media/v4l/pixfmt-bayer.rst | 1 + .../media/v4l/pixfmt-srggb8-pisp-comp.rst | 74 ++++++++++++++++++++++ drivers/media/v4l2-core/v4l2-ioctl.c | 10 +++ include/uapi/linux/videodev2.h | 12 ++++ 4 files changed, 97 insertions(+) create mode 100644 Documentation/userspace-api/media/v4l/pixfmt-srggb8-pisp-comp.rst (limited to 'Documentation/userspace-api') diff --git a/Documentation/userspace-api/media/v4l/pixfmt-bayer.rst b/Documentation/userspace-api/media/v4l/pixfmt-bayer.rst index 2500413e5f43..ed3eb432967d 100644 --- a/Documentation/userspace-api/media/v4l/pixfmt-bayer.rst +++ b/Documentation/userspace-api/media/v4l/pixfmt-bayer.rst @@ -20,6 +20,7 @@ orders. See also `the Wikipedia article on Bayer filter :maxdepth: 1 pixfmt-srggb8 + pixfmt-srggb8-pisp-comp pixfmt-srggb10 pixfmt-srggb10p pixfmt-srggb10alaw8 diff --git a/Documentation/userspace-api/media/v4l/pixfmt-srggb8-pisp-comp.rst b/Documentation/userspace-api/media/v4l/pixfmt-srggb8-pisp-comp.rst new file mode 100644 index 000000000000..5a82a15559d6 --- /dev/null +++ b/Documentation/userspace-api/media/v4l/pixfmt-srggb8-pisp-comp.rst @@ -0,0 +1,74 @@ +.. SPDX-License-Identifier: GFDL-1.1-no-invariants-or-later + +.. _v4l2-pix-fmt-pisp-comp1-rggb: +.. _v4l2-pix-fmt-pisp-comp1-grbg: +.. _v4l2-pix-fmt-pisp-comp1-gbrg: +.. _v4l2-pix-fmt-pisp-comp1-bggr: +.. _v4l2-pix-fmt-pisp-comp1-mono: +.. _v4l2-pix-fmt-pisp-comp2-rggb: +.. _v4l2-pix-fmt-pisp-comp2-grbg: +.. _v4l2-pix-fmt-pisp-comp2-gbrg: +.. _v4l2-pix-fmt-pisp-comp2-bggr: +.. _v4l2-pix-fmt-pisp-comp2-mono: + +************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** +V4L2_PIX_FMT_PISP_COMP1_RGGB ('PC1R'), V4L2_PIX_FMT_PISP_COMP1_GRBG ('PC1G'), V4L2_PIX_FMT_PISP_COMP1_GBRG ('PC1g'), V4L2_PIX_FMT_PISP_COMP1_BGGR ('PC1B), V4L2_PIX_FMT_PISP_COMP1_MONO ('PC1M'), V4L2_PIX_FMT_PISP_COMP2_RGGB ('PC2R'), V4L2_PIX_FMT_PISP_COMP2_GRBG ('PC2G'), V4L2_PIX_FMT_PISP_COMP2_GBRG ('PC2g'), V4L2_PIX_FMT_PISP_COMP2_BGGR ('PC2B), V4L2_PIX_FMT_PISP_COMP2_MONO ('PC2M') +************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** + +================================================ +Raspberry Pi PiSP compressed 8-bit Bayer formats +================================================ + +Description +=========== + +The Raspberry Pi ISP (PiSP) uses a family of three fixed-rate compressed Bayer +formats. A black-level offset may be subtracted to improve compression +efficiency; the nominal black level and amount of offset must be signalled out +of band. Each scanline is padded to a multiple of 8 pixels wide, and each block +of 8 horizontally-contiguous pixels is coded using 8 bytes. + +Mode 1 uses a quantization and delta-based coding scheme which preserves up to +12 significant bits. Mode 2 is a simple sqrt-like companding scheme with 6 PWL +chords, preserving up to 12 significant bits. Mode 3 combines both companding +(with 4 chords) and the delta scheme, preserving up to 14 significant bits. + +The remainder of this description applies to Modes 1 and 3. + +Each block of 8 pixels is separated into even and odd phases of 4 pixels, +coded independently by 32-bit words at successive locations in memory. +The two LS bits of each 32-bit word give its "quantization mode". + +In quantization mode 0, the lowest 321 quantization levels are multiples of +FSD/4096 and the remaining levels are successive multiples of FSD/2048. +Quantization modes 1 and 2 use linear quantization with step sizes of +FSD/1024 and FSD/512 respectively. Each of the four pixels is quantized +independently, with rounding to the nearest level. +In quantization mode 2 where the middle two samples have quantized values +(q1,q2) both in the range [384..511], they are coded using 9 bits for q1 +followed by 7 bits for (q2 & 127). Otherwise, for quantization modes +0, 1 and 2: a 9-bit field encodes MIN(q1,q2) which must be in the range +[0..511] and a 7-bit field encodes (q2-q1+64) which must be in [0..127]. + +Each of the outer samples (q0,q3) is encoded using a 7-bit field based +on its inner neighbour q1 or q2. In quantization mode 2 where the inner +sample has a quantized value in the range [448..511], the field value is +(q0-384). Otherwise for quantization modes 0, 1 and 2: The outer sample +is encoded as (q0-MAX(0,q1-64)). q3 is likewise coded based on q2. +Each of these values must be in the range [0..127]. All these fields +of 2, 9, 7, 7, 7 bits respectively are packed in little-endian order +to give a 32-bit word with LE byte order. + +Quantization mode 3 has a "7.5-bit" escape, used when none of the above +encodings will fit. Each pixel value is quantized to the nearest of 176 +levels, where the lowest 95 levels are multiples of FSD/256 and the +remaining levels are multiples of FSD/128 (level 175 represents values +very close to FSD and may require saturating arithmetic to decode). + +Each pair of quantized pixels (q0,q1) or (q2,q3) is jointly coded +by a 15-bit field: 2816*(q0>>4) + 16*q1 + (q0&15). +Three fields of 2, 15, 15 bits are packed in LE order {15,15,2}. + +An implementation of a software decoder of compressed formats is available +in `Raspberry Pi camera applications code base +`_. diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 7c684788997c..5eb4d797d259 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1532,6 +1532,16 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_MT2110T: descr = "Mediatek 10bit Tile Mode"; break; case V4L2_PIX_FMT_MT2110R: descr = "Mediatek 10bit Raster Mode"; break; case V4L2_PIX_FMT_HEXTILE: descr = "Hextile Compressed Format"; break; + case V4L2_PIX_FMT_PISP_COMP1_RGGB: descr = "PiSP 8b RGRG/GBGB mode1 compr"; break; + case V4L2_PIX_FMT_PISP_COMP1_GRBG: descr = "PiSP 8b GRGR/BGBG mode1 compr"; break; + case V4L2_PIX_FMT_PISP_COMP1_GBRG: descr = "PiSP 8b GBGB/RGRG mode1 compr"; break; + case V4L2_PIX_FMT_PISP_COMP1_BGGR: descr = "PiSP 8b BGBG/GRGR mode1 compr"; break; + case V4L2_PIX_FMT_PISP_COMP1_MONO: descr = "PiSP 8b monochrome mode1 compr"; break; + case V4L2_PIX_FMT_PISP_COMP2_RGGB: descr = "PiSP 8b RGRG/GBGB mode2 compr"; break; + case V4L2_PIX_FMT_PISP_COMP2_GRBG: descr = "PiSP 8b GRGR/BGBG mode2 compr"; break; + case V4L2_PIX_FMT_PISP_COMP2_GBRG: descr = "PiSP 8b GBGB/RGRG mode2 compr"; break; + case V4L2_PIX_FMT_PISP_COMP2_BGGR: descr = "PiSP 8b BGBG/GRGR mode2 compr"; break; + case V4L2_PIX_FMT_PISP_COMP2_MONO: descr = "PiSP 8b monochrome mode2 compr"; break; default: if (fmt->description[0]) return; diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index 96fc0456081e..4e91362da6da 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -816,6 +816,18 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_IPU3_SGRBG10 v4l2_fourcc('i', 'p', '3', 'G') /* IPU3 packed 10-bit GRBG bayer */ #define V4L2_PIX_FMT_IPU3_SRGGB10 v4l2_fourcc('i', 'p', '3', 'r') /* IPU3 packed 10-bit RGGB bayer */ +/* Raspberry Pi PiSP compressed formats. */ +#define V4L2_PIX_FMT_PISP_COMP1_RGGB v4l2_fourcc('P', 'C', '1', 'R') /* PiSP 8-bit mode 1 compressed RGGB bayer */ +#define V4L2_PIX_FMT_PISP_COMP1_GRBG v4l2_fourcc('P', 'C', '1', 'G') /* PiSP 8-bit mode 1 compressed GRBG bayer */ +#define V4L2_PIX_FMT_PISP_COMP1_GBRG v4l2_fourcc('P', 'C', '1', 'g') /* PiSP 8-bit mode 1 compressed GBRG bayer */ +#define V4L2_PIX_FMT_PISP_COMP1_BGGR v4l2_fourcc('P', 'C', '1', 'B') /* PiSP 8-bit mode 1 compressed BGGR bayer */ +#define V4L2_PIX_FMT_PISP_COMP1_MONO v4l2_fourcc('P', 'C', '1', 'M') /* PiSP 8-bit mode 1 compressed monochrome */ +#define V4L2_PIX_FMT_PISP_COMP2_RGGB v4l2_fourcc('P', 'C', '2', 'R') /* PiSP 8-bit mode 2 compressed RGGB bayer */ +#define V4L2_PIX_FMT_PISP_COMP2_GRBG v4l2_fourcc('P', 'C', '2', 'G') /* PiSP 8-bit mode 2 compressed GRBG bayer */ +#define V4L2_PIX_FMT_PISP_COMP2_GBRG v4l2_fourcc('P', 'C', '2', 'g') /* PiSP 8-bit mode 2 compressed GBRG bayer */ +#define V4L2_PIX_FMT_PISP_COMP2_BGGR v4l2_fourcc('P', 'C', '2', 'B') /* PiSP 8-bit mode 2 compressed BGGR bayer */ +#define V4L2_PIX_FMT_PISP_COMP2_MONO v4l2_fourcc('P', 'C', '2', 'M') /* PiSP 8-bit mode 2 compressed monochrome */ + /* SDR formats - used only for Software Defined Radio devices */ #define V4L2_SDR_FMT_CU8 v4l2_fourcc('C', 'U', '0', '8') /* IQ u8 */ #define V4L2_SDR_FMT_CU16LE v4l2_fourcc('C', 'U', '1', '6') /* IQ u16le */ -- cgit v1.2.3