diff --git a/MAINTAINERS b/MAINTAINERS index 34e8f284501cba..0ca82e50fe372a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21598,6 +21598,7 @@ L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/sony,imx500.yaml +F: drivers/media/i2c/imx500.c SONY IMX519 SENSOR DRIVER M: Arducam Kernel Maintenance diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index a2bb05ec217e7d..989806018e4d73 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -299,6 +299,18 @@ config VIDEO_IMX477 To compile this driver as a module, choose M here: the module will be called imx477. +config VIDEO_IMX500 + tristate "Sony IMX500 sensor support" + depends on I2C && VIDEO_DEV + select VIDEO_V4L2_SUBDEV_API + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX500 camera. + + To compile this driver as a module, choose M here: the + module will be called IMX500. + config VIDEO_IMX519 tristate "Arducam IMX519 sensor support" depends on I2C && VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index b265cabb507296..edcf0776d5eef0 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_VIDEO_IMX355) += imx355.o obj-$(CONFIG_VIDEO_IMX412) += imx412.o obj-$(CONFIG_VIDEO_IMX415) += imx415.o obj-$(CONFIG_VIDEO_IMX477) += imx477.o +obj-$(CONFIG_VIDEO_IMX500) += imx500.o obj-$(CONFIG_VIDEO_IMX519) += imx519.o obj-$(CONFIG_VIDEO_IMX708) += imx708.o obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o diff --git a/drivers/media/i2c/imx500.c b/drivers/media/i2c/imx500.c new file mode 100644 index 00000000000000..6aadd6bf5b550c --- /dev/null +++ b/drivers/media/i2c/imx500.c @@ -0,0 +1,2839 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX500 cameras. + * Copyright (C) 2024, Raspberry Pi Ltd + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Chip ID */ +#define IMX500_REG_CHIP_ID CCI_REG16(0x0016) +#define IMX500_CHIP_ID 0x0500 + +#define IMX500_REG_MODE_SELECT CCI_REG8(0x0100) +#define IMX500_MODE_STANDBY 0x00 +#define IMX500_MODE_STREAMING 0x01 + +#define IMX500_REG_IMAGE_ONLY_MODE CCI_REG8(0xa700) +#define IMX500_IMAGE_ONLY_FALSE 0x00 +#define IMX500_IMAGE_ONLY_TRUE 0x01 + +#define IMX500_REG_ORIENTATION CCI_REG8(0x101) + +#define IMX500_XCLK_FREQ 24000000 + +#define IMX500_DEFAULT_LINK_FREQ 444000000 + +#define IMX500_PIXEL_RATE 744000000 + +/* V_TIMING internal */ +#define IMX500_REG_FRAME_LENGTH CCI_REG16(0x0340) +#define IMX500_FRAME_LENGTH_MAX 0xffdc +#define IMX500_VBLANK_MIN 1117 + +/* H_TIMING internal */ +#define IMX500_REG_LINE_LENGTH CCI_REG16(0x0342) +#define IMX500_LINE_LENGTH_MAX 0xfff0 + +/* Long exposure multiplier */ +#define IMX500_LONG_EXP_SHIFT_MAX 7 +#define IMX500_LONG_EXP_SHIFT_REG CCI_REG8(0x3210) + +/* Exposure control */ +#define IMX500_REG_EXPOSURE CCI_REG16(0x0202) +#define IMX500_EXPOSURE_OFFSET 22 +#define IMX500_EXPOSURE_MIN 8 +#define IMX500_EXPOSURE_STEP 1 +#define IMX500_EXPOSURE_DEFAULT 0x640 +#define IMX500_EXPOSURE_MAX (IMX500_FRAME_LENGTH_MAX - IMX500_EXPOSURE_OFFSET) + +/* Analog gain control */ +#define IMX500_REG_ANALOG_GAIN CCI_REG16(0x0204) +#define IMX500_ANA_GAIN_MIN 0 +#define IMX500_ANA_GAIN_MAX 978 +#define IMX500_ANA_GAIN_STEP 1 +#define IMX500_ANA_GAIN_DEFAULT 0x0 + +/* Inference windows */ +#define IMX500_REG_DWP_AP_VC_VOFF CCI_REG16(0xD500) +#define IMX500_REG_DWP_AP_VC_HOFF CCI_REG16(0xD502) +#define IMX500_REG_DWP_AP_VC_VSIZE CCI_REG16(0xD504) +#define IMX500_REG_DWP_AP_VC_HSIZE CCI_REG16(0xD506) + +#define IMX500_REG_DD_CH06_X_OUT_SIZE \ + CCI_REG16(0x3054) /* Output pixel count for KPI */ +#define IMX500_REG_DD_CH07_X_OUT_SIZE \ + CCI_REG16(0x3056) /* Output pixel count for Input Tensor */ +#define IMX500_REG_DD_CH08_X_OUT_SIZE \ + CCI_REG16(0x3058) /* Output pixel count for Output Tensor */ +#define IMX500_REG_DD_CH09_X_OUT_SIZE \ + CCI_REG16(0x305A) /* Output pixel count for PQ Settings */ + +#define IMX500_REG_DD_CH06_Y_OUT_SIZE \ + CCI_REG16(0x305C) /* Output line count for KPI */ +#define IMX500_REG_DD_CH07_Y_OUT_SIZE \ + CCI_REG16(0x305E) /* Output line count for Input Tensor */ +#define IMX500_REG_DD_CH08_Y_OUT_SIZE \ + CCI_REG16(0x3060) /* Output line count for Output Tensor */ +#define IMX500_REG_DD_CH09_Y_OUT_SIZE \ + CCI_REG16(0x3062) /* Output line count for PQ Settings */ + +#define IMX500_REG_DD_CH06_VCID \ + CCI_REG8(0x3064) /* Virtual channel ID for KPI */ +#define IMX500_REG_DD_CH07_VCID \ + CCI_REG8(0x3065) /* Virtual channel ID for Input Tensor */ +#define IMX500_REG_DD_CH08_VCID \ + CCI_REG8(0x3066) /* Virtual channel ID for Output Tensor */ +#define IMX500_REG_DD_CH09_VCID \ + CCI_REG8(0x3067) /* Virtual channel ID for PQ Settings */ + +#define IMX500_REG_DD_CH06_DT CCI_REG8(0x3068) /* Data Type for KPI */ +#define IMX500_REG_DD_CH07_DT CCI_REG8(0x3069) /* Data Type for Input Tensor */ +#define IMX500_REG_DD_CH08_DT CCI_REG8(0x306A) /* Data Type for Output Tensor */ +#define IMX500_REG_DD_CH09_DT CCI_REG8(0x306B) /* Data Type for PQ Settings */ + +#define IMX500_REG_DD_CH06_PACKING \ + CCI_REG8(0x306C) /* Pixel/byte packing for KPI */ +#define IMX500_REG_DD_CH07_PACKING \ + CCI_REG8(0x306D) /* Pixel/byte packing for Input Tensor */ +#define IMX500_REG_DD_CH08_PACKING \ + CCI_REG8(0x306E) /* Pixel/byte packing for Output Tensor */ +#define IMX500_REG_DD_CH09_PACKING \ + CCI_REG8(0x306F) /* Pixel/byte packing for PQ Settings */ +#define IMX500_DD_PACKING_8BPP 2 /* 8 bits/pixel */ +#define IMX500_DD_PACKING_10BPP 3 /* 10 bits/pixel */ + +/* Interrupt command (start processing command inside IMX500 CPU) */ +#define IMX500_REG_DD_CMD_INT CCI_REG8(0x3080) +#define IMX500_DD_CMD_INT_ST_TRANS 0 +#define IMX500_DD_CMD_INT_UPDATE 1 +#define IMX500_DD_CMD_INT_FLASH_ERASE 2 + +/* State transition command type */ +#define IMX500_REG_DD_ST_TRANS_CMD CCI_REG8(0xD000) +#define IMX500_DD_ST_TRANS_CMD_LOADER_FW 0 +#define IMX500_DD_ST_TRANS_CMD_MAIN_FW 1 +#define IMX500_DD_ST_TRANS_CMD_NW_WEIGHTS 2 +#define IMX500_DD_ST_TRANS_CMD_CLEAR_WEIGHTS 3 + +/* Network weights update command */ +#define IMX500_REG_DD_UPDATE_CMD CCI_REG8(0xD001) +#define IMX500_DD_UPDATE_CMD_SRAM 0 +#define IMX500_DD_UPDATE_CMD_FLASH 1 + +/* Transfer source when loading into RAM */ +#define IMX500_REG_DD_LOAD_MODE CCI_REG8(0xD002) +#define IMX500_DD_LOAD_MODE_AP 0 +#define IMX500_DD_LOAD_MODE_FLASH 1 + +/* Image type to transfer */ +#define IMX500_REG_DD_IMAGE_TYPE CCI_REG8(0xD003) +#define IMX500_DD_IMAGE_TYPE_LOADER_FW 0 +#define IMX500_DD_IMAGE_TYPE_MAIN_FW 1 +#define IMX500_DD_IMAGE_TYPE_NETWORK_WEIGHTS 2 + +/* Number of divisions of download image file */ +#define IMX500_REG_DD_DOWNLOAD_DIV_NUM CCI_REG8(0xD004) + +#define IMX500_REG_DD_FLASH_TYPE CCI_REG8(0xD005) + +/* total size of download file (4-byte) */ +#define IMX500_REG_DD_DOWNLOAD_FILE_SIZE CCI_REG32(0xD008) + +/* Status notification (4-byte) */ +#define IMX500_REG_DD_REF_STS CCI_REG32(0xD010) +#define IMX500_DD_REF_STS_FATAL 0xFF +#define IMX500_DD_REF_STS_DETECT_CNT 0xFF00 +#define IMX500_DD_REF_STS_ERR_CNT 0xFF0000 +#define IMX500_DD_REF_CMD_REPLY_CNT 0xFF000000 + +/* Command reply status */ +#define IMX500_REG_DD_CMD_REPLY_STS CCI_REG8(0xD014) +#define IMX500_DD_CMD_REPLY_STS_TRANS_READY 0x00 +#define IMX500_DD_CMD_REPLY_STS_TRANS_DONE 0x01 +#define IMX500_DD_CMD_REPLY_STS_UPDATE_READY 0x10 +#define IMX500_DD_CMD_REPLY_STS_UPDATE_DONE 0x11 +#define IMX500_DD_CMD_REPLY_STS_UPDATE_CANCEL_DONE 0x12 +#define IMX500_DD_CMD_REPLY_STS_STATUS_ERROR 0xFF +#define IMX500_DD_CMD_REPLY_STS_MAC_AUTH_ERROR 0xFE +#define IMX500_DD_CMD_REPLY_STS_TIMEOUT_ERROR 0xFD +#define IMX500_DD_CMD_REPLY_STS_PARAMETER_ERROR 0xFC +#define IMX500_DD_CMD_REPLY_STS_INTERNAL_ERROR 0xFB +#define IMX500_DD_CMD_REPLY_STS_PACKET_FMT_ERROR 0xFA + +/* Download status */ +#define IMX500_REG_DD_DOWNLOAD_STS CCI_REG8(0xD015) +#define IMX500_DD_DOWNLOAD_STS_READY 0 +#define IMX500_DD_DOWNLOAD_STS_DOWNLOADING 1 + +/* Update cancel */ +#define IMX500_REG_DD_UPDATE_CANCEL CCI_REG8(0xD016) +#define IMX500_DD_UPDATE_CANCEL_NOT_CANCEL 0 +#define IMX500_DD_UPDATE_CANCEL_DO_CANCEL 1 + +/* Notify error status */ +#define IMX500_REG_DD_ERR_STS CCI_REG8(0xD020) +#define IMX500_DD_ERR_STS_STATUS_ERROR_BIT 0x1 +#define IMX500_DD_ERR_STS_INTERNAL_ERROR_BIT 0x2 +#define IMX500_DD_ERR_STS_PARAMETER_ERROR_BIT 0x4 + +/* System state */ +#define IMX500_REG_DD_SYS_STATE CCI_REG8(0xD02A) +#define IMX500_DD_SYS_STATE_STANDBY_NO_NETWORK 0 +#define IMX500_DD_SYS_STATE_STEAMING_NO_NETWORK 1 +#define IMX500_DD_SYS_STATE_STANDBY_WITH_NETWORK 2 +#define IMX500_DD_SYS_STATE_STREAMING_WITH_NETWORK 3 + +#define IMX500_REG_MAIN_FW_VERSION CCI_REG32(0xD07C) + +/* Colour balance controls */ +#define IMX500_REG_COLOUR_BALANCE_R CCI_REG16(0xd804) +#define IMX500_REG_COLOUR_BALANCE_GR CCI_REG16(0xd806) +#define IMX500_REG_COLOUR_BALANCE_GB CCI_REG16(0xd808) +#define IMX500_REG_COLOUR_BALANCE_B CCI_REG16(0xd80a) +#define IMX500_COLOUR_BALANCE_MIN 0x0001 +#define IMX500_COLOUR_BALANCE_MAX 0x0fff +#define IMX500_COLOUR_BALANCE_STEP 0x0001 +#define IMX500_COLOUR_BALANCE_DEFAULT 0x0100 + +/* Embedded sizes */ +#define IMX500_MAX_EMBEDDED_SIZE \ + (2 * ((((IMX500_PIXEL_ARRAY_WIDTH * 10) >> 3) + 15) & ~15)) + +/* Inference sizes */ +#define IMX500_INFERENCE_LINE_WIDTH 2560 +#define IMX500_NUM_KPI_LINES 1 +#define IMX500_NUM_PQ_LINES 1 + +/* IMX500 native and active pixel array size. */ +#define IMX500_NATIVE_WIDTH 4072U +#define IMX500_NATIVE_HEIGHT 3176U +#define IMX500_PIXEL_ARRAY_LEFT 8U +#define IMX500_PIXEL_ARRAY_TOP 16U +#define IMX500_PIXEL_ARRAY_WIDTH 4056U +#define IMX500_PIXEL_ARRAY_HEIGHT 3040U + +enum pad_types { IMAGE_PAD, METADATA_PAD, NUM_PADS }; + +#define V4L2_CID_USER_IMX500_INFERENCE_WINDOW (V4L2_CID_USER_IMX500_BASE + 0) +#define V4L2_CID_USER_IMX500_NETWORK_FW_FD (V4L2_CID_USER_IMX500_BASE + 1) + +#define ONE_MIB (1024 * 1024) + +/* regulator supplies */ +static const char *const imx500_supply_name[] = { + /* Supplies can be enabled in any order */ + "vana", /* Analog (2.7V) supply */ + "vdig", /* Digital Core (0.84V) supply */ + "vif", /* Interface (1.8V) supply */ +}; + +#define IMX500_NUM_SUPPLIES ARRAY_SIZE(imx500_supply_name) + +enum imx500_image_type { + TYPE_LOADER = 0, + TYPE_MAIN = 1, + TYPE_NW_WEIGHTS = 2, + TYPE_MAX +}; + +struct imx500_reg_list { + unsigned int num_of_regs; + const struct cci_reg_sequence *regs; +}; + +/* Mode : resolution and related config&values */ +struct imx500_mode { + /* Frame width */ + unsigned int width; + + /* Frame height */ + unsigned int height; + + /* H-timing in pixels */ + unsigned int line_length_pix; + + /* Analog crop rectangle. */ + struct v4l2_rect crop; + + /* Default register values */ + struct imx500_reg_list reg_list; +}; + +static const struct cci_reg_sequence mode_common_regs[] = { + { CCI_REG8(0x0305), 0x02 }, + { CCI_REG8(0x0306), 0x00 }, + { CCI_REG8(0x030d), 0x02 }, + { CCI_REG8(0x030e), 0x00 }, + { CCI_REG8(0x0106), 0x01 }, /* FAST_STANDBY_CTL */ + { CCI_REG8(0x0136), 0x1b }, /* EXCLK_FREQ */ + { CCI_REG8(0x0137), 0x00 }, + { CCI_REG8(0x0112), 0x0a }, + { CCI_REG8(0x0113), 0x0a }, + { CCI_REG8(0x0114), 0x01 }, /* CSI_LANE_MODE */ + { CCI_REG16(0x3054), IMX500_INFERENCE_LINE_WIDTH }, + { CCI_REG16(0x3056), IMX500_INFERENCE_LINE_WIDTH }, + { CCI_REG16(0x3058), IMX500_INFERENCE_LINE_WIDTH }, + { CCI_REG16(0x305A), IMX500_INFERENCE_LINE_WIDTH }, /* X_OUT */ + { CCI_REG16(0x305C), IMX500_NUM_KPI_LINES }, /* KPI Y_OUT */ + { CCI_REG16(0x3062), IMX500_NUM_PQ_LINES }, /* PQ Y_OUT */ + { CCI_REG8(0x3068), 0x30 }, + { CCI_REG8(0x3069), 0x31 }, + { CCI_REG8(0x306A), 0x32 }, + { CCI_REG8(0x306B), 0x33 }, /* Data Types */ +}; + +/* 12 mpix 15fps */ +static const struct cci_reg_sequence mode_4056x3040_regs[] = { + { CCI_REG8(0x0340), 0x12 }, + { CCI_REG8(0x0341), 0x42 }, + { CCI_REG8(0x0342), 0x45 }, + { CCI_REG8(0x0343), 0xec }, + { CCI_REG8(0x3210), 0x00 }, + { CCI_REG8(0x0344), 0x00 }, + { CCI_REG8(0x0345), 0x00 }, + { CCI_REG8(0x0346), 0x00 }, + { CCI_REG8(0x0347), 0x00 }, + { CCI_REG8(0x0348), 0x0f }, + { CCI_REG8(0x0349), 0xd7 }, + { CCI_REG8(0x0350), 0x00 }, + { CCI_REG8(0x034a), 0x0b }, + { CCI_REG8(0x034b), 0xdf }, + { CCI_REG8(0x3f58), 0x01 }, + { CCI_REG8(0x0381), 0x01 }, + { CCI_REG8(0x0383), 0x01 }, + { CCI_REG8(0x0385), 0x01 }, + { CCI_REG8(0x0387), 0x01 }, + { CCI_REG8(0x0900), 0x00 }, + { CCI_REG8(0x0901), 0x11 }, + { CCI_REG8(0x0902), 0x00 }, + { CCI_REG8(0x3241), 0x11 }, + { CCI_REG8(0x3242), 0x01 }, + { CCI_REG8(0x3250), 0x00 }, + { CCI_REG8(0x3f0f), 0x00 }, + { CCI_REG8(0x3f40), 0x00 }, + { CCI_REG8(0x3f41), 0x00 }, + { CCI_REG8(0x3f42), 0x00 }, + { CCI_REG8(0x3f43), 0x00 }, + { CCI_REG8(0xb34e), 0x00 }, + { CCI_REG8(0xb351), 0x20 }, + { CCI_REG8(0xb35c), 0x00 }, + { CCI_REG8(0xb35e), 0x08 }, + { CCI_REG8(0x0401), 0x00 }, + { CCI_REG8(0x0404), 0x00 }, + { CCI_REG8(0x0405), 0x10 }, + { CCI_REG8(0x0408), 0x00 }, + { CCI_REG8(0x0409), 0x00 }, + { CCI_REG8(0x040a), 0x00 }, + { CCI_REG8(0x040b), 0x00 }, + { CCI_REG8(0x040c), 0x0f }, + { CCI_REG8(0x040d), 0xd8 }, + { CCI_REG8(0x040e), 0x0b }, + { CCI_REG8(0x040f), 0xe0 }, + { CCI_REG8(0x034c), 0x0f }, + { CCI_REG8(0x034d), 0xd8 }, + { CCI_REG8(0x034e), 0x0b }, + { CCI_REG8(0x034f), 0xe0 }, + { CCI_REG8(0x0301), 0x05 }, + { CCI_REG8(0x0303), 0x02 }, + { CCI_REG8(0x0307), 0x9b }, + { CCI_REG8(0x0309), 0x0a }, + { CCI_REG8(0x030b), 0x01 }, + { CCI_REG8(0x030f), 0x4a }, + { CCI_REG8(0x0310), 0x01 }, + { CCI_REG8(0x0820), 0x07 }, + { CCI_REG8(0x0821), 0xce }, + { CCI_REG8(0x0822), 0x00 }, + { CCI_REG8(0x0823), 0x00 }, + { CCI_REG8(0x3e20), 0x01 }, + { CCI_REG8(0x3e35), 0x01 }, + { CCI_REG8(0x3e36), 0x01 }, + { CCI_REG8(0x3e37), 0x00 }, + { CCI_REG8(0x3e3a), 0x01 }, + { CCI_REG8(0x3e3b), 0x00 }, + { CCI_REG8(0x00e3), 0x00 }, + { CCI_REG8(0x00e4), 0x00 }, + { CCI_REG8(0x00e6), 0x00 }, + { CCI_REG8(0x00e7), 0x00 }, + { CCI_REG8(0x00e8), 0x00 }, + { CCI_REG8(0x00e9), 0x00 }, + { CCI_REG8(0x3f50), 0x00 }, + { CCI_REG8(0x3f56), 0x02 }, + { CCI_REG8(0x3f57), 0x42 }, + { CCI_REG8(0x3606), 0x01 }, + { CCI_REG8(0x3607), 0x01 }, + { CCI_REG8(0x3f26), 0x00 }, + { CCI_REG8(0x3f4a), 0x00 }, + { CCI_REG8(0x3f4b), 0x00 }, + { CCI_REG8(0x4bc0), 0x16 }, + { CCI_REG8(0x7ba8), 0x00 }, + { CCI_REG8(0x7ba9), 0x00 }, + { CCI_REG8(0x886b), 0x00 }, + { CCI_REG8(0x579a), 0x00 }, + { CCI_REG8(0x579b), 0x0a }, + { CCI_REG8(0x579c), 0x01 }, + { CCI_REG8(0x579d), 0x2a }, + { CCI_REG8(0x57ac), 0x00 }, + { CCI_REG8(0x57ad), 0x00 }, + { CCI_REG8(0x57ae), 0x00 }, + { CCI_REG8(0x57af), 0x81 }, + { CCI_REG8(0x57be), 0x00 }, + { CCI_REG8(0x57bf), 0x00 }, + { CCI_REG8(0x57c0), 0x00 }, + { CCI_REG8(0x57c1), 0x81 }, + { CCI_REG8(0x57d0), 0x00 }, + { CCI_REG8(0x57d1), 0x00 }, + { CCI_REG8(0x57d2), 0x00 }, + { CCI_REG8(0x57d3), 0x81 }, + { CCI_REG8(0x5324), 0x00 }, + { CCI_REG8(0x5325), 0x26 }, + { CCI_REG8(0x5326), 0x00 }, + { CCI_REG8(0x5327), 0x6b }, + { CCI_REG8(0xbca7), 0x00 }, + { CCI_REG8(0x5fcc), 0x28 }, + { CCI_REG8(0x5fd7), 0x2d }, + { CCI_REG8(0x5fe2), 0x2d }, + { CCI_REG8(0x5fed), 0x2d }, + { CCI_REG8(0x5ff8), 0x2d }, + { CCI_REG8(0x6003), 0x2d }, + { CCI_REG8(0x5d0b), 0x01 }, + { CCI_REG8(0x6f6d), 0x00 }, + { CCI_REG8(0x61c9), 0x00 }, + { CCI_REG8(0x5352), 0x00 }, + { CCI_REG8(0x5353), 0x49 }, + { CCI_REG8(0x5356), 0x00 }, + { CCI_REG8(0x5357), 0x30 }, + { CCI_REG8(0x5358), 0x00 }, + { CCI_REG8(0x5359), 0x3b }, + { CCI_REG8(0x535c), 0x00 }, + { CCI_REG8(0x535d), 0xb0 }, + { CCI_REG8(0x6187), 0x18 }, + { CCI_REG8(0x6189), 0x18 }, + { CCI_REG8(0x618b), 0x18 }, + { CCI_REG8(0x618d), 0x1d }, + { CCI_REG8(0x618f), 0x1d }, + { CCI_REG8(0x5414), 0x01 }, + { CCI_REG8(0x5415), 0x0c }, + { CCI_REG8(0xbca8), 0x0a }, + { CCI_REG8(0x5fcf), 0x1e }, + { CCI_REG8(0x5fda), 0x1e }, + { CCI_REG8(0x5fe5), 0x1e }, + { CCI_REG8(0x5ff0), 0x1e }, + { CCI_REG8(0x5ffb), 0x1e }, + { CCI_REG8(0x6006), 0x1e }, + { CCI_REG8(0x616e), 0x04 }, + { CCI_REG8(0x616f), 0x04 }, + { CCI_REG8(0x6170), 0x04 }, + { CCI_REG8(0x6171), 0x06 }, + { CCI_REG8(0x6172), 0x06 }, + { CCI_REG8(0x6173), 0x0c }, + { CCI_REG8(0x6174), 0x0c }, + { CCI_REG8(0x6175), 0x0c }, + { CCI_REG8(0x6176), 0x00 }, + { CCI_REG8(0x6177), 0x10 }, + { CCI_REG8(0x6178), 0x00 }, + { CCI_REG8(0x6179), 0x1a }, + { CCI_REG8(0x617a), 0x00 }, + { CCI_REG8(0x617b), 0x1a }, + { CCI_REG8(0x617c), 0x00 }, + { CCI_REG8(0x617d), 0x27 }, + { CCI_REG8(0x617e), 0x00 }, + { CCI_REG8(0x617f), 0x27 }, + { CCI_REG8(0x6180), 0x00 }, + { CCI_REG8(0x6181), 0x44 }, + { CCI_REG8(0x6182), 0x00 }, + { CCI_REG8(0x6183), 0x44 }, + { CCI_REG8(0x6184), 0x00 }, + { CCI_REG8(0x6185), 0x44 }, + { CCI_REG8(0x5dfc), 0x0a }, + { CCI_REG8(0x5e00), 0x0a }, + { CCI_REG8(0x5e04), 0x0a }, + { CCI_REG8(0x5e08), 0x0a }, + { CCI_REG8(0x5dfd), 0x0a }, + { CCI_REG8(0x5e01), 0x0a }, + { CCI_REG8(0x5e05), 0x0a }, + { CCI_REG8(0x5e09), 0x0a }, + { CCI_REG8(0x5dfe), 0x0a }, + { CCI_REG8(0x5e02), 0x0a }, + { CCI_REG8(0x5e06), 0x0a }, + { CCI_REG8(0x5e0a), 0x0a }, + { CCI_REG8(0x5dff), 0x0a }, + { CCI_REG8(0x5e03), 0x0a }, + { CCI_REG8(0x5e07), 0x0a }, + { CCI_REG8(0x5e0b), 0x0a }, + { CCI_REG8(0x5dec), 0x12 }, + { CCI_REG8(0x5df0), 0x12 }, + { CCI_REG8(0x5df4), 0x21 }, + { CCI_REG8(0x5df8), 0x31 }, + { CCI_REG8(0x5ded), 0x12 }, + { CCI_REG8(0x5df1), 0x12 }, + { CCI_REG8(0x5df5), 0x21 }, + { CCI_REG8(0x5df9), 0x31 }, + { CCI_REG8(0x5dee), 0x12 }, + { CCI_REG8(0x5df2), 0x12 }, + { CCI_REG8(0x5df6), 0x21 }, + { CCI_REG8(0x5dfa), 0x31 }, + { CCI_REG8(0x5def), 0x12 }, + { CCI_REG8(0x5df3), 0x12 }, + { CCI_REG8(0x5df7), 0x21 }, + { CCI_REG8(0x5dfb), 0x31 }, + { CCI_REG8(0x5ddc), 0x0d }, + { CCI_REG8(0x5de0), 0x0d }, + { CCI_REG8(0x5de4), 0x0d }, + { CCI_REG8(0x5de8), 0x0d }, + { CCI_REG8(0x5ddd), 0x0d }, + { CCI_REG8(0x5de1), 0x0d }, + { CCI_REG8(0x5de5), 0x0d }, + { CCI_REG8(0x5de9), 0x0d }, + { CCI_REG8(0x5dde), 0x0d }, + { CCI_REG8(0x5de2), 0x0d }, + { CCI_REG8(0x5de6), 0x0d }, + { CCI_REG8(0x5dea), 0x0d }, + { CCI_REG8(0x5ddf), 0x0d }, + { CCI_REG8(0x5de3), 0x0d }, + { CCI_REG8(0x5de7), 0x0d }, + { CCI_REG8(0x5deb), 0x0d }, + { CCI_REG8(0x5dcc), 0x55 }, + { CCI_REG8(0x5dd0), 0x50 }, + { CCI_REG8(0x5dd4), 0x4b }, + { CCI_REG8(0x5dd8), 0x4b }, + { CCI_REG8(0x5dcd), 0x55 }, + { CCI_REG8(0x5dd1), 0x50 }, + { CCI_REG8(0x5dd5), 0x4b }, + { CCI_REG8(0x5dd9), 0x4b }, + { CCI_REG8(0x5dce), 0x55 }, + { CCI_REG8(0x5dd2), 0x50 }, + { CCI_REG8(0x5dd6), 0x4b }, + { CCI_REG8(0x5dda), 0x4b }, + { CCI_REG8(0x5dcf), 0x55 }, + { CCI_REG8(0x5dd3), 0x50 }, + { CCI_REG8(0x5dd7), 0x4b }, + { CCI_REG8(0x5ddb), 0x4b }, + { CCI_REG8(0x0202), 0x12 }, + { CCI_REG8(0x0203), 0x2c }, + { CCI_REG8(0x0204), 0x00 }, + { CCI_REG8(0x0205), 0x00 }, + { CCI_REG8(0x020e), 0x01 }, + { CCI_REG8(0x020f), 0x00 }, + { CCI_REG8(0x0210), 0x01 }, + { CCI_REG8(0x0211), 0x00 }, + { CCI_REG8(0x0212), 0x01 }, + { CCI_REG8(0x0213), 0x00 }, + { CCI_REG8(0x0214), 0x01 }, + { CCI_REG8(0x0215), 0x00 }, +}; + +/* 2x2 binned. 56fps */ +static const struct cci_reg_sequence mode_2028x1520_regs[] = { + { CCI_REG8(0x0112), 0x0a }, + { CCI_REG8(0x0113), 0x0a }, + { CCI_REG8(0x0114), 0x01 }, + { CCI_REG8(0x0342), 0x24 }, + { CCI_REG8(0x0343), 0xb6 }, + { CCI_REG8(0x0340), 0x0b }, + { CCI_REG8(0x0341), 0x9c }, + { CCI_REG8(0x3210), 0x00 }, + { CCI_REG8(0x0344), 0x00 }, + { CCI_REG8(0x0345), 0x00 }, + { CCI_REG8(0x0346), 0x00 }, + { CCI_REG8(0x0347), 0x00 }, + { CCI_REG8(0x0348), 0x0f }, + { CCI_REG8(0x0349), 0xd7 }, + { CCI_REG8(0x0350), 0x00 }, + { CCI_REG8(0x034a), 0x0b }, + { CCI_REG8(0x034b), 0xdf }, + { CCI_REG8(0x3f58), 0x01 }, + { CCI_REG8(0x0381), 0x01 }, + { CCI_REG8(0x0383), 0x01 }, + { CCI_REG8(0x0385), 0x01 }, + { CCI_REG8(0x0387), 0x01 }, + { CCI_REG8(0x0900), 0x01 }, + { CCI_REG8(0x0901), 0x22 }, + { CCI_REG8(0x0902), 0x02 }, + { CCI_REG8(0x3241), 0x11 }, + { CCI_REG8(0x3242), 0x01 }, + { CCI_REG8(0x3250), 0x03 }, + { CCI_REG8(0x3f0f), 0x00 }, + { CCI_REG8(0x3f40), 0x00 }, + { CCI_REG8(0x3f41), 0x00 }, + { CCI_REG8(0x3f42), 0x00 }, + { CCI_REG8(0x3f43), 0x00 }, + { CCI_REG8(0xb34e), 0x00 }, + { CCI_REG8(0xb351), 0x20 }, + { CCI_REG8(0xb35c), 0x00 }, + { CCI_REG8(0xb35e), 0x08 }, + { CCI_REG8(0x0401), 0x00 }, + { CCI_REG8(0x0404), 0x00 }, + { CCI_REG8(0x0405), 0x10 }, + { CCI_REG8(0x0408), 0x00 }, + { CCI_REG8(0x0409), 0x00 }, + { CCI_REG8(0x040a), 0x00 }, + { CCI_REG8(0x040b), 0x00 }, + { CCI_REG8(0x040c), 0x07 }, + { CCI_REG8(0x040d), 0xec }, + { CCI_REG8(0x040e), 0x05 }, + { CCI_REG8(0x040f), 0xf0 }, + { CCI_REG8(0x034c), 0x07 }, + { CCI_REG8(0x034d), 0xec }, + { CCI_REG8(0x034e), 0x05 }, + { CCI_REG8(0x034f), 0xf0 }, + { CCI_REG8(0x0301), 0x05 }, + { CCI_REG8(0x0303), 0x02 }, + { CCI_REG8(0x0307), 0x9b }, + { CCI_REG8(0x0309), 0x0a }, + { CCI_REG8(0x030b), 0x01 }, + { CCI_REG8(0x030f), 0x4a }, + { CCI_REG8(0x0310), 0x01 }, + { CCI_REG8(0x0820), 0x07 }, + { CCI_REG8(0x0821), 0xce }, + { CCI_REG8(0x0822), 0x00 }, + { CCI_REG8(0x0823), 0x00 }, + { CCI_REG8(0x3e20), 0x01 }, + { CCI_REG8(0x3e35), 0x01 }, + { CCI_REG8(0x3e36), 0x01 }, + { CCI_REG8(0x3e37), 0x00 }, + { CCI_REG8(0x3e3a), 0x01 }, + { CCI_REG8(0x3e3b), 0x00 }, + { CCI_REG8(0x00e3), 0x00 }, + { CCI_REG8(0x00e4), 0x00 }, + { CCI_REG8(0x00e6), 0x00 }, + { CCI_REG8(0x00e7), 0x00 }, + { CCI_REG8(0x00e8), 0x00 }, + { CCI_REG8(0x00e9), 0x00 }, + { CCI_REG8(0x3f50), 0x00 }, + { CCI_REG8(0x3f56), 0x01 }, + { CCI_REG8(0x3f57), 0x30 }, + { CCI_REG8(0x3606), 0x01 }, + { CCI_REG8(0x3607), 0x01 }, + { CCI_REG8(0x3f26), 0x00 }, + { CCI_REG8(0x3f4a), 0x00 }, + { CCI_REG8(0x3f4b), 0x00 }, + { CCI_REG8(0x4bc0), 0x16 }, + { CCI_REG8(0x7ba8), 0x00 }, + { CCI_REG8(0x7ba9), 0x00 }, + { CCI_REG8(0x886b), 0x00 }, + { CCI_REG8(0x579a), 0x00 }, + { CCI_REG8(0x579b), 0x0a }, + { CCI_REG8(0x579c), 0x01 }, + { CCI_REG8(0x579d), 0x2a }, + { CCI_REG8(0x57ac), 0x00 }, + { CCI_REG8(0x57ad), 0x00 }, + { CCI_REG8(0x57ae), 0x00 }, + { CCI_REG8(0x57af), 0x81 }, + { CCI_REG8(0x57be), 0x00 }, + { CCI_REG8(0x57bf), 0x00 }, + { CCI_REG8(0x57c0), 0x00 }, + { CCI_REG8(0x57c1), 0x81 }, + { CCI_REG8(0x57d0), 0x00 }, + { CCI_REG8(0x57d1), 0x00 }, + { CCI_REG8(0x57d2), 0x00 }, + { CCI_REG8(0x57d3), 0x81 }, + { CCI_REG8(0x5324), 0x00 }, + { CCI_REG8(0x5325), 0x31 }, + { CCI_REG8(0x5326), 0x00 }, + { CCI_REG8(0x5327), 0x60 }, + { CCI_REG8(0xbca7), 0x08 }, + { CCI_REG8(0x5fcc), 0x1e }, + { CCI_REG8(0x5fd7), 0x1e }, + { CCI_REG8(0x5fe2), 0x1e }, + { CCI_REG8(0x5fed), 0x1e }, + { CCI_REG8(0x5ff8), 0x1e }, + { CCI_REG8(0x6003), 0x1e }, + { CCI_REG8(0x5d0b), 0x02 }, + { CCI_REG8(0x6f6d), 0x01 }, + { CCI_REG8(0x61c9), 0x68 }, + { CCI_REG8(0x5352), 0x00 }, + { CCI_REG8(0x5353), 0x3f }, + { CCI_REG8(0x5356), 0x00 }, + { CCI_REG8(0x5357), 0x1c }, + { CCI_REG8(0x5358), 0x00 }, + { CCI_REG8(0x5359), 0x3d }, + { CCI_REG8(0x535c), 0x00 }, + { CCI_REG8(0x535d), 0xa6 }, + { CCI_REG8(0x6187), 0x1d }, + { CCI_REG8(0x6189), 0x1d }, + { CCI_REG8(0x618b), 0x1d }, + { CCI_REG8(0x618d), 0x23 }, + { CCI_REG8(0x618f), 0x23 }, + { CCI_REG8(0x5414), 0x01 }, + { CCI_REG8(0x5415), 0x12 }, + { CCI_REG8(0xbca8), 0x00 }, + { CCI_REG8(0x5fcf), 0x28 }, + { CCI_REG8(0x5fda), 0x2d }, + { CCI_REG8(0x5fe5), 0x2d }, + { CCI_REG8(0x5ff0), 0x2d }, + { CCI_REG8(0x5ffb), 0x2d }, + { CCI_REG8(0x6006), 0x2d }, + { CCI_REG8(0x616e), 0x04 }, + { CCI_REG8(0x616f), 0x04 }, + { CCI_REG8(0x6170), 0x04 }, + { CCI_REG8(0x6171), 0x06 }, + { CCI_REG8(0x6172), 0x06 }, + { CCI_REG8(0x6173), 0x0c }, + { CCI_REG8(0x6174), 0x0c }, + { CCI_REG8(0x6175), 0x0c }, + { CCI_REG8(0x6176), 0x00 }, + { CCI_REG8(0x6177), 0x10 }, + { CCI_REG8(0x6178), 0x00 }, + { CCI_REG8(0x6179), 0x1a }, + { CCI_REG8(0x617a), 0x00 }, + { CCI_REG8(0x617b), 0x1a }, + { CCI_REG8(0x617c), 0x00 }, + { CCI_REG8(0x617d), 0x27 }, + { CCI_REG8(0x617e), 0x00 }, + { CCI_REG8(0x617f), 0x27 }, + { CCI_REG8(0x6180), 0x00 }, + { CCI_REG8(0x6181), 0x44 }, + { CCI_REG8(0x6182), 0x00 }, + { CCI_REG8(0x6183), 0x44 }, + { CCI_REG8(0x6184), 0x00 }, + { CCI_REG8(0x6185), 0x44 }, + { CCI_REG8(0x5dfc), 0x0a }, + { CCI_REG8(0x5e00), 0x0a }, + { CCI_REG8(0x5e04), 0x0a }, + { CCI_REG8(0x5e08), 0x0a }, + { CCI_REG8(0x5dfd), 0x0a }, + { CCI_REG8(0x5e01), 0x0a }, + { CCI_REG8(0x5e05), 0x0a }, + { CCI_REG8(0x5e09), 0x0a }, + { CCI_REG8(0x5dfe), 0x0a }, + { CCI_REG8(0x5e02), 0x0a }, + { CCI_REG8(0x5e06), 0x0a }, + { CCI_REG8(0x5e0a), 0x0a }, + { CCI_REG8(0x5dff), 0x0a }, + { CCI_REG8(0x5e03), 0x0a }, + { CCI_REG8(0x5e07), 0x0a }, + { CCI_REG8(0x5e0b), 0x0a }, + { CCI_REG8(0x5dec), 0x12 }, + { CCI_REG8(0x5df0), 0x12 }, + { CCI_REG8(0x5df4), 0x21 }, + { CCI_REG8(0x5df8), 0x31 }, + { CCI_REG8(0x5ded), 0x12 }, + { CCI_REG8(0x5df1), 0x12 }, + { CCI_REG8(0x5df5), 0x21 }, + { CCI_REG8(0x5df9), 0x31 }, + { CCI_REG8(0x5dee), 0x12 }, + { CCI_REG8(0x5df2), 0x12 }, + { CCI_REG8(0x5df6), 0x21 }, + { CCI_REG8(0x5dfa), 0x31 }, + { CCI_REG8(0x5def), 0x12 }, + { CCI_REG8(0x5df3), 0x12 }, + { CCI_REG8(0x5df7), 0x21 }, + { CCI_REG8(0x5dfb), 0x31 }, + { CCI_REG8(0x5ddc), 0x0d }, + { CCI_REG8(0x5de0), 0x0d }, + { CCI_REG8(0x5de4), 0x0d }, + { CCI_REG8(0x5de8), 0x0d }, + { CCI_REG8(0x5ddd), 0x0d }, + { CCI_REG8(0x5de1), 0x0d }, + { CCI_REG8(0x5de5), 0x0d }, + { CCI_REG8(0x5de9), 0x0d }, + { CCI_REG8(0x5dde), 0x0d }, + { CCI_REG8(0x5de2), 0x0d }, + { CCI_REG8(0x5de6), 0x0d }, + { CCI_REG8(0x5dea), 0x0d }, + { CCI_REG8(0x5ddf), 0x0d }, + { CCI_REG8(0x5de3), 0x0d }, + { CCI_REG8(0x5de7), 0x0d }, + { CCI_REG8(0x5deb), 0x0d }, + { CCI_REG8(0x5dcc), 0x55 }, + { CCI_REG8(0x5dd0), 0x50 }, + { CCI_REG8(0x5dd4), 0x4b }, + { CCI_REG8(0x5dd8), 0x4b }, + { CCI_REG8(0x5dcd), 0x55 }, + { CCI_REG8(0x5dd1), 0x50 }, + { CCI_REG8(0x5dd5), 0x4b }, + { CCI_REG8(0x5dd9), 0x4b }, + { CCI_REG8(0x5dce), 0x55 }, + { CCI_REG8(0x5dd2), 0x50 }, + { CCI_REG8(0x5dd6), 0x4b }, + { CCI_REG8(0x5dda), 0x4b }, + { CCI_REG8(0x5dcf), 0x55 }, + { CCI_REG8(0x5dd3), 0x50 }, + { CCI_REG8(0x5dd7), 0x4b }, + { CCI_REG8(0x5ddb), 0x4b }, + { CCI_REG8(0x0202), 0x0b }, + { CCI_REG8(0x0203), 0x86 }, + { CCI_REG8(0x0204), 0x00 }, + { CCI_REG8(0x0205), 0x00 }, + { CCI_REG8(0x020e), 0x01 }, + { CCI_REG8(0x020f), 0x00 }, + { CCI_REG8(0x0210), 0x01 }, + { CCI_REG8(0x0211), 0x00 }, + { CCI_REG8(0x0212), 0x01 }, + { CCI_REG8(0x0213), 0x00 }, + { CCI_REG8(0x0214), 0x01 }, + { CCI_REG8(0x0215), 0x00 }, +}; + +static const struct cci_reg_sequence metadata_output[] = { + { CCI_REG8(0x3050), 1 }, /* MIPI Output enabled */ + { CCI_REG8(0x3051), 1 }, /* MIPI output frame includes pixels data */ + { CCI_REG8(0x3052), 1 }, /* MIPI output frame includes meta data */ + { IMX500_REG_DD_CH06_VCID, 0 }, + { IMX500_REG_DD_CH07_VCID, 0 }, + { IMX500_REG_DD_CH08_VCID, 0 }, + { IMX500_REG_DD_CH09_VCID, 0 }, + { IMX500_REG_DD_CH06_DT, + 0x12 }, /* KPI - User Defined 8-bit Data Type 1 */ + { IMX500_REG_DD_CH07_DT, 0x12 }, /* Input Tensor - U.D. 8-bit type 2 */ + { IMX500_REG_DD_CH08_DT, 0x12 }, /* Output Tensor - U.D. 8-bit type 3 */ + { IMX500_REG_DD_CH09_DT, 0x12 }, /* PQ - U.D. 8-bit type 4 */ + { IMX500_REG_DD_CH06_PACKING, IMX500_DD_PACKING_8BPP }, + { IMX500_REG_DD_CH07_PACKING, IMX500_DD_PACKING_8BPP }, + { IMX500_REG_DD_CH08_PACKING, IMX500_DD_PACKING_8BPP }, + { IMX500_REG_DD_CH09_PACKING, IMX500_DD_PACKING_8BPP }, +}; + +static const struct cci_reg_sequence dnn_regs[] = { + { CCI_REG8(0xd960), 0x52 }, + { CCI_REG8(0xd961), 0x52 }, + { CCI_REG8(0xd962), 0x52 }, + { CCI_REG8(0xd963), 0x52 }, + { CCI_REG8(0xd96c), 0x44 }, + { CCI_REG8(0xd96d), 0x44 }, + { CCI_REG8(0xd96e), 0x44 }, + { CCI_REG8(0xd96f), 0x44 }, + { CCI_REG8(0xd600), 0x20 }, + /* Black level */ + { CCI_REG16(0xd80c), 0x100 }, + { CCI_REG16(0xd80e), 0x100 }, + { CCI_REG16(0xd810), 0x100 }, + { CCI_REG16(0xd812), 0x100 }, + /* Gamma */ + { CCI_REG8(0xd814), 1 }, + { CCI_REG32(0xd850), 0x10000 }, + { CCI_REG32(0xd854), 0x40002 }, + { CCI_REG32(0xd858), 0x60005 }, + { CCI_REG32(0xd85c), 0x90008 }, + { CCI_REG32(0xd860), 0xc000a }, + { CCI_REG32(0xd864), 0x12000f }, + { CCI_REG32(0xd868), 0x1c0014 }, + { CCI_REG32(0xd86c), 0x2a0024 }, + { CCI_REG32(0xd870), 0x360030 }, + { CCI_REG32(0xd874), 0x46003c }, + { CCI_REG32(0xd878), 0x5a0051 }, + { CCI_REG32(0xd87c), 0x750064 }, + { CCI_REG32(0xd880), 0x920084 }, + { CCI_REG32(0xd884), 0xa9009e }, + { CCI_REG32(0xd888), 0xba00b2 }, + { CCI_REG32(0xd88c), 0xc700c1 }, + { CCI_REG32(0xd890), 0xd100cd }, + { CCI_REG32(0xd894), 0xde00d6 }, + { CCI_REG32(0xd898), 0xe900e4 }, + { CCI_REG32(0xd89c), 0xf300ee }, + { CCI_REG32(0xd8a0), 0xfb00f7 }, + { CCI_REG16(0xd8a4), 0xff }, + { CCI_REG32(0xd8a8), 0x10000 }, + { CCI_REG32(0xd8ac), 0x40002 }, + { CCI_REG32(0xd8b0), 0x60005 }, + { CCI_REG32(0xd8b4), 0x90008 }, + { CCI_REG32(0xd8b8), 0xc000a }, + { CCI_REG32(0xd8bc), 0x12000f }, + { CCI_REG32(0xd8c0), 0x1c0014 }, + { CCI_REG32(0xd8c4), 0x2a0024 }, + { CCI_REG32(0xd8c8), 0x360030 }, + { CCI_REG32(0xd8cc), 0x46003c }, + { CCI_REG32(0xd8d0), 0x5a0051 }, + { CCI_REG32(0xd8d4), 0x750064 }, + { CCI_REG32(0xd8d8), 0x920084 }, + { CCI_REG32(0xd8dc), 0xa9009e }, + { CCI_REG32(0xd8e0), 0xba00b2 }, + { CCI_REG32(0xd8e4), 0xc700c1 }, + { CCI_REG32(0xd8e8), 0xd100cd }, + { CCI_REG32(0xd8ec), 0xde00d6 }, + { CCI_REG32(0xd8f0), 0xe900e4 }, + { CCI_REG32(0xd8f4), 0xf300ee }, + { CCI_REG32(0xd8f8), 0xfb00f7 }, + { CCI_REG16(0xd8fc), 0xff }, + { CCI_REG32(0xd900), 0x10000 }, + { CCI_REG32(0xd904), 0x40002 }, + { CCI_REG32(0xd908), 0x60005 }, + { CCI_REG32(0xd90c), 0x90008 }, + { CCI_REG32(0xd910), 0xc000a }, + { CCI_REG32(0xd914), 0x12000f }, + { CCI_REG32(0xd918), 0x1c0014 }, + { CCI_REG32(0xd91c), 0x2a0024 }, + { CCI_REG32(0xd920), 0x360030 }, + { CCI_REG32(0xd924), 0x46003c }, + { CCI_REG32(0xd928), 0x5a0051 }, + { CCI_REG32(0xd92c), 0x750064 }, + { CCI_REG32(0xd930), 0x920084 }, + { CCI_REG32(0xd934), 0xa9009e }, + { CCI_REG32(0xd938), 0xba00b2 }, + { CCI_REG32(0xd93c), 0xc700c1 }, + { CCI_REG32(0xd940), 0xd100cd }, + { CCI_REG32(0xd944), 0xde00d6 }, + { CCI_REG32(0xd948), 0xe900e4 }, + { CCI_REG32(0xd94c), 0xf300ee }, + { CCI_REG32(0xd950), 0xfb00f7 }, + { CCI_REG16(0xd954), 0xff }, + { CCI_REG8(0xd826), 1 }, +}; + +/* Mode configs */ +static const struct imx500_mode imx500_supported_modes[] = { + { + /* 12MPix 10fps mode */ + .width = 4056, + .height = 3040, + .line_length_pix = 17900, + .crop = { + .left = IMX500_PIXEL_ARRAY_LEFT, + .top = IMX500_PIXEL_ARRAY_TOP, + .width = 4056, + .height = 3040, + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4056x3040_regs), + .regs = mode_4056x3040_regs, + }, + }, + { + /* 2x2 binned 40fps mode */ + .width = 2028, + .height = 1520, + .line_length_pix = 9398, + .crop = { + .left = IMX500_PIXEL_ARRAY_LEFT, + .top = IMX500_PIXEL_ARRAY_TOP, + .width = 4056, + .height = 3040, + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2028x1520_regs), + .regs = mode_2028x1520_regs, + }, + }, +}; + +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 codes[] = { + /* 10-bit modes. */ + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +enum imx500_state { + IMX500_STATE_RESET = 0, + IMX500_STATE_PROGRAM_EMPTY, + IMX500_STATE_WITHOUT_NETWORK, + IMX500_STATE_WITH_NETWORK, +}; + +struct imx500 { + struct dentry *debugfs; + struct v4l2_subdev sd; + struct media_pad pad[NUM_PADS]; + struct regmap *regmap; + + unsigned int fmt_code; + + struct clk *xclk; + u32 xclk_freq; + + struct gpio_desc *led_gpio; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[IMX500_NUM_SUPPLIES]; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *network_fw_ctrl; + + struct v4l2_rect inference_window; + + /* Current mode */ + const struct imx500_mode *mode; + + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; + + /* Rewrite common registers on stream on? */ + bool common_regs_written; + + bool loader_and_main_written; + bool network_written; + + /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */ + unsigned int long_exp_shift; + + struct spi_device *spi_device; + + const struct firmware *fw_loader; + const struct firmware *fw_main; + const u8 *fw_network; + size_t fw_network_size; + size_t fw_progress; + unsigned int fw_stage; + + enum imx500_state fsm_state; + + u32 num_inference_lines; +}; + +static inline struct imx500 *to_imx500(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx500, sd); +} + +static bool validate_normalization_yuv(u16 reg, uint8_t size, + uint32_t value) +{ + /* Some regs are 9-bit, some 8-bit, some 1-bit */ + switch (reg) { + case 0xD62A: + case 0xD632: + case 0xD63A: + case 0xD644: + case 0xD648: + case 0xD64C: + case 0xD650: + case 0xD654: + case 0xD658: + return size == 2 && !(value & ~0x1FF); + case 0xD600: + case 0xD601: + case 0xD602: + return size == 1 && !(value & ~0xFF); + case 0xD629: + case 0xD630: + case 0xD638: + case 0xD643: + case 0xD647: + case 0xD64B: + case 0xD64F: + case 0xD653: + case 0xD657: + return size == 1 && !(value & ~0x01); + default: + return false; + } +} + +/* Common function as bayer rgb + normalization use the same repeating register + * layout + */ +static bool validate_bit_pattern(u8 offset, uint8_t size, uint32_t value) +{ + /* There are no odd register addresses */ + if (offset & 1) + return false; + + /* Valid register sizes/patterns repeat every 4 */ + offset = (offset >> 1) & 3; + + if (offset == 1) + return size == 1 && !(value & ~1); + else + return size == 2 && !(value & ~0x1FF); +} + +static bool validate_bayer_rgb_normalization(u16 reg, uint8_t size, + uint32_t value) +{ + if (reg < 0xD684 || reg >= 0xD6E4) + return false; + return validate_bit_pattern(reg - 0xD684, size, value); +} + +static bool validate_normalization_registers(u16 reg, uint8_t size, + uint32_t value) +{ + if (reg < 0xD708 || reg >= 0xD750) + return false; + return validate_bit_pattern(reg - 0xD708, size, value); +} + +static bool validate_image_format_selection(u16 reg, uint8_t size, + uint32_t value) +{ + if (size != 1 || value > 5) + return false; + if (reg < 0xD750 || reg > 0xd752) + return false; + return true; +} + +static bool validate_yc_conversion_factor(u16 reg, uint8_t size, + uint32_t value) +{ + static const u32 allowed[9] = { + 0x0FFF0FFF, 0x0FFF1FFF, 0x0FFF0FFF, 0x0FFF1FFF, 0x0FFF0FFF, + 0x0FFF1FFF, 0x01FF01FF, 0x01FF01FF, 0x01FF01FF, + }; + + if (size > 4 || size & 1 || reg & 1 || reg < 0x76C || reg > 0xD7FA) + return false; + + if (size == 2) { + if (reg & 2) + reg -= 2; + else + value <<= 16; + } + + /* High registers (clip values) are all 2x 9-bit */ + if (reg >= 0xD7D8) + return !(value & ~0x01FF01FF); + + /* Early registers follow a repeating pattern */ + reg -= 0xD76C; + reg >>= 2; + return !(value & ~allowed[reg % sizeof(allowed)]); +} + +static bool validate_dnn_output_setting(u16 reg, uint8_t size, + uint32_t value) +{ + /* Only Y_OUT_SIZE for Input Tensor / Output Tensor is configurable from + * userspace + */ + return (size == 2) && (value < 2046) && + ((reg == CCI_REG_ADDR(IMX500_REG_DD_CH07_Y_OUT_SIZE)) || + (reg == CCI_REG_ADDR(IMX500_REG_DD_CH08_Y_OUT_SIZE))); +} + +static bool __must_check +imx500_validate_inference_register(const struct cci_reg_sequence *reg) +{ + unsigned int i; + + static bool (*const checks[])(uint16_t, uint8_t, uint32_t) = { + validate_normalization_yuv, + validate_bayer_rgb_normalization, + validate_normalization_registers, + validate_image_format_selection, + validate_yc_conversion_factor, + validate_dnn_output_setting, + }; + + if (!reg) + return false; + + for (i = 0; i < ARRAY_SIZE(checks); i++) { + if (checks[i](CCI_REG_ADDR(reg->reg), + CCI_REG_WIDTH_BYTES(reg->reg), reg->val)) + return true; + } + + return false; +} + +static int imx500_set_inference_window(struct imx500 *imx500) +{ + u16 left, top, width, height; + + if (!imx500->inference_window.width || + !imx500->inference_window.height) { + width = 4056; + height = 3040; + left = 0; + top = 0; + } else { + width = min_t(u16, imx500->inference_window.width, 4056); + height = min_t(u16, imx500->inference_window.height, 3040); + left = min_t(u16, imx500->inference_window.left, 4056); + top = min_t(u16, imx500->inference_window.top, 3040); + } + + const struct cci_reg_sequence window_regs[] = { + { IMX500_REG_DWP_AP_VC_HOFF, left }, + { IMX500_REG_DWP_AP_VC_VOFF, top }, + { IMX500_REG_DWP_AP_VC_HSIZE, width }, + { IMX500_REG_DWP_AP_VC_VSIZE, height }, + }; + + return cci_multi_reg_write(imx500->regmap, window_regs, + ARRAY_SIZE(window_regs), NULL); +} + +static int imx500_reg_val_write_cbk(void *arg, + const struct cci_reg_sequence *reg) +{ + struct imx500 *imx500 = arg; + + if (!imx500_validate_inference_register(reg)) + return -EINVAL; + + return cci_write(imx500->regmap, reg->reg, reg->val, NULL); +} + +/* Get bayer order based on flip setting. */ +static u32 imx500_get_format_code(struct imx500 *imx500) +{ + unsigned int i; + + lockdep_assert_held(&imx500->mutex); + + i = (imx500->vflip->val ? 2 : 0) | (imx500->hflip->val ? 1 : 0); + + return codes[i]; +} + +static void imx500_set_default_format(struct imx500 *imx500) +{ + /* Set default mode to max resolution */ + imx500->mode = &imx500_supported_modes[0]; + imx500->fmt_code = MEDIA_BUS_FMT_SRGGB10_1X10; +} + +/* -1 on fail, block size on success */ +static int imx500_validate_fw_block(const char *data, size_t maxlen) +{ + const size_t header_size = 32; + static const char header_id[] = { '9', '4', '6', '4' }; + + const size_t footer_size = 64; + static const char footer_id[] = { '3', '6', '9', '5' }; + + u32 data_size; + + const char *end = data + maxlen; + + if (!data) + return -1; + + if (maxlen < header_size) + return -1; + + if (memcmp(data, &header_id, sizeof(header_id))) + return -1; + + /* data_size is size of header + body */ + memcpy(&data_size, data + sizeof(header_id), sizeof(data_size)); + data_size = ___constant_swab32(data_size); + + if (end - data_size - footer_size < data) + return -1; + if (memcmp(data + data_size + footer_size - sizeof(footer_id), + &footer_id, sizeof(footer_id))) + return -1; + + return data_size + footer_size; +} + +/* Parse fw block by block, returning total valid fw size */ +static size_t imx500_valid_fw_bytes(const u8 *fw, + const size_t fw_size) +{ + int i; + size_t bytes = 0; + + const u8 *data = fw; + size_t size = fw_size; + + while ((i = imx500_validate_fw_block(data, size)) > 0) { + bytes += i; + data += i; + size -= i; + } + + return bytes; +} + +static int imx500_iterate_nw_regs( + const u8 *fw, size_t fw_size, void *arg, + int (*cbk)(void *arg, const struct cci_reg_sequence *reg)) +{ + struct cpio_data cd = { NULL, 0, "" }; + const u8 *read_pos; + size_t entries; + size_t size; + + if (!fw || !cbk) + return -EINVAL; + + size = imx500_valid_fw_bytes(fw, fw_size); + cd = find_cpio_data("imx500_regs", (void *)(fw + size), + fw_size - size, NULL); + if (!cd.data || cd.size % 7) + return -EINVAL; + + read_pos = cd.data; + entries = cd.size / 7; + + while (entries--) { + struct cci_reg_sequence reg = { 0, 0 }; + u16 addr; + u8 len; + u32 val; + int ret; + + memcpy(&addr, read_pos, sizeof(addr)); + read_pos += sizeof(addr); + memcpy(&len, read_pos, sizeof(len)); + read_pos += sizeof(len); + memcpy(&val, read_pos, sizeof(val)); + read_pos += sizeof(val); + + reg.reg = ((len << CCI_REG_WIDTH_SHIFT) | addr); + reg.val = val; + + ret = cbk(arg, ®); + if (ret) + return ret; + } + return 0; +} + +static int imx500_reg_tensor_lines_cbk(void *arg, + const struct cci_reg_sequence *reg) +{ + u16 *tensor_lines = arg; + + if (reg->val < 2046) { + switch (reg->reg) { + case IMX500_REG_DD_CH07_Y_OUT_SIZE: + tensor_lines[0] = reg->val; + break; + case IMX500_REG_DD_CH08_Y_OUT_SIZE: + tensor_lines[1] = reg->val; + break; + } + } + + return 0; +} + +static void imx500_calc_inference_lines(struct imx500 *imx500) +{ + u16 tensor_lines[2] = { 0, 0 }; + + if (!imx500->fw_network) { + imx500->num_inference_lines = 0; + return; + } + + imx500_iterate_nw_regs(imx500->fw_network, imx500->fw_network_size, + tensor_lines, imx500_reg_tensor_lines_cbk); + + /* Full-res mode, embedded lines are actually slightly shorter than inference + * lines 2544 vs 2560 (over-allocate with inf. width) + */ + imx500->num_inference_lines = IMX500_NUM_KPI_LINES + + IMX500_NUM_PQ_LINES + tensor_lines[0] + + tensor_lines[1]; +} + +static void imx500_adjust_exposure_range(struct imx500 *imx500) +{ + int exposure_max, exposure_def; + + /* Honour the VBLANK limits when setting exposure. */ + exposure_max = imx500->mode->height + imx500->vblank->val - + IMX500_EXPOSURE_OFFSET; + exposure_def = min(exposure_max, imx500->exposure->val); + __v4l2_ctrl_modify_range(imx500->exposure, imx500->exposure->minimum, + exposure_max, imx500->exposure->step, + exposure_def); +} + +static int imx500_set_frame_length(struct imx500 *imx500, unsigned int val) +{ + int ret = 0; + + imx500->long_exp_shift = 0; + + while (val > IMX500_FRAME_LENGTH_MAX) { + imx500->long_exp_shift++; + val >>= 1; + } + + ret = cci_write(imx500->regmap, IMX500_REG_FRAME_LENGTH, val, NULL); + if (ret) + return ret; + + return cci_write(imx500->regmap, IMX500_LONG_EXP_SHIFT_REG, + imx500->long_exp_shift, NULL); +} + +/* reg is both input and output: + * reg->val is the value we're polling until we're NEQ to + * It is then populated with the updated value. + */ +static int __must_check imx500_poll_status_reg(struct imx500 *state, + struct cci_reg_sequence *reg, + u8 timeout) +{ + u64 read_value; + int ret; + + while (timeout) { + ret = cci_read(state->regmap, reg->reg, &read_value, NULL); + if (ret) + return ret; + + if (read_value != reg->val) { + reg->val = read_value; + return 0; + } + + timeout--; + mdelay(50); + } + return -EAGAIN; +} + +static int imx500_prepare_poll_cmd_reply_sts(struct imx500 *imx500, + struct cci_reg_sequence *cmd_reply) +{ + /* Perform single-byte read of 4-byte IMX500_REG_DD_REF_STS register to + * target CMD_REPLY_STS_CNT sub-register + */ + cmd_reply->reg = CCI_REG8(CCI_REG_ADDR(IMX500_REG_DD_REF_STS)); + + return cci_read(imx500->regmap, cmd_reply->reg, &cmd_reply->val, NULL); +} + +static int imx500_clear_weights(struct imx500 *imx500) +{ + struct cci_reg_sequence cmd_reply_sts_cnt_reg; + u64 imx500_fsm_state; + u64 cmd_reply; + int ret; + + static const struct cci_reg_sequence request_clear[] = { + { IMX500_REG_DD_ST_TRANS_CMD, + IMX500_DD_ST_TRANS_CMD_CLEAR_WEIGHTS }, + { IMX500_REG_DD_CMD_INT, IMX500_DD_CMD_INT_ST_TRANS }, + }; + + if (imx500->fsm_state != IMX500_STATE_WITH_NETWORK) + return -EINVAL; + + ret = cci_read(imx500->regmap, IMX500_REG_DD_SYS_STATE, + &imx500_fsm_state, NULL); + if (ret || imx500_fsm_state != IMX500_DD_SYS_STATE_STANDBY_WITH_NETWORK) + return ret ? ret : -EREMOTEIO; + + ret = imx500_prepare_poll_cmd_reply_sts(imx500, &cmd_reply_sts_cnt_reg); + if (ret) + return ret; + + ret = cci_multi_reg_write(imx500->regmap, request_clear, + ARRAY_SIZE(request_clear), NULL); + if (ret) + return ret; + + ret = imx500_poll_status_reg(imx500, &cmd_reply_sts_cnt_reg, 5); + if (ret) + return ret; + + ret = cci_read(imx500->regmap, IMX500_REG_DD_CMD_REPLY_STS, &cmd_reply, + NULL); + if (ret || cmd_reply != IMX500_DD_CMD_REPLY_STS_TRANS_DONE) + return ret ? ret : -EREMOTEIO; + + imx500->fsm_state = IMX500_STATE_WITHOUT_NETWORK; + imx500->network_written = false; + return 0; +} + +static void imx500_clear_fw_network(struct imx500 *imx500) +{ + /* Remove any previous firmware blob. */ + if (imx500->fw_network) + vfree(imx500->fw_network); + + imx500->fw_network = NULL; + imx500->network_written = false; + imx500->fw_progress = 0; +} + +static int imx500_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx500 *imx500 = + container_of(ctrl->handler, struct imx500, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + int ret = 0; + + if (ctrl->id == V4L2_CID_USER_IMX500_NETWORK_FW_FD) { + /* Reset state of the control. */ + if (ctrl->val < 0) { + return 0; + } else if (ctrl->val == S32_MAX) { + ctrl->val = -1; + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + if (imx500->network_written) + ret = imx500_clear_weights(imx500); + imx500_clear_fw_network(imx500); + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return ret; + } + + imx500_clear_fw_network(imx500); + ret = kernel_read_file_from_fd(ctrl->val, 0, + (void **)&imx500->fw_network, INT_MAX, + &imx500->fw_network_size, + 1); + /* + * Back to reset state, the FD cannot be considered valid after + * this IOCTL completes. + */ + ctrl->val = -1; + + if (ret < 0) { + dev_err(&client->dev, "%s failed to read fw image: %d\n", + __func__, ret); + imx500_clear_fw_network(imx500); + return ret; + } + if (ret != imx500->fw_network_size) { + dev_err(&client->dev, "%s read fw image size mismatich: got %u, expected %zu\n", + __func__, ret, imx500->fw_network_size); + imx500_clear_fw_network(imx500); + return -EIO; + } + + imx500_calc_inference_lines(imx500); + return 0; + } + + /* + * The VBLANK control may change the limits of usable exposure, so check + * and adjust if necessary. + */ + if (ctrl->id == V4L2_CID_VBLANK) + imx500_adjust_exposure_range(imx500); + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = cci_write(imx500->regmap, IMX500_REG_ANALOG_GAIN, + ctrl->val, NULL); + break; + case V4L2_CID_EXPOSURE: + ret = cci_write(imx500->regmap, IMX500_REG_EXPOSURE, + ctrl->val >> imx500->long_exp_shift, NULL); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = cci_write(imx500->regmap, IMX500_REG_ORIENTATION, + imx500->hflip->val | imx500->vflip->val << 1, + NULL); + break; + case V4L2_CID_VBLANK: + ret = imx500_set_frame_length(imx500, + imx500->mode->height + ctrl->val); + break; + case V4L2_CID_HBLANK: + ret = cci_write(imx500->regmap, IMX500_REG_LINE_LENGTH, + imx500->mode->width + ctrl->val, NULL); + break; + case V4L2_CID_NOTIFY_GAINS: + ret = cci_write(imx500->regmap, IMX500_REG_COLOUR_BALANCE_B, + ctrl->p_new.p_u32[0], NULL); + cci_write(imx500->regmap, IMX500_REG_COLOUR_BALANCE_GB, + ctrl->p_new.p_u32[1], &ret); + cci_write(imx500->regmap, IMX500_REG_COLOUR_BALANCE_GR, + ctrl->p_new.p_u32[2], &ret); + cci_write(imx500->regmap, IMX500_REG_COLOUR_BALANCE_R, + ctrl->p_new.p_u32[3], &ret); + break; + case V4L2_CID_USER_IMX500_INFERENCE_WINDOW: + memcpy(&imx500->inference_window, ctrl->p_new.p_u32, + sizeof(struct v4l2_rect)); + ret = imx500_set_inference_window(imx500); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", ctrl->id, + ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx500_ctrl_ops = { + .s_ctrl = imx500_set_ctrl, +}; + +static const struct v4l2_ctrl_config imx500_notify_gains_ctrl = { + .ops = &imx500_ctrl_ops, + .id = V4L2_CID_NOTIFY_GAINS, + .type = V4L2_CTRL_TYPE_U32, + .min = IMX500_COLOUR_BALANCE_MIN, + .max = IMX500_COLOUR_BALANCE_MAX, + .step = IMX500_COLOUR_BALANCE_STEP, + .def = IMX500_COLOUR_BALANCE_DEFAULT, + .dims = { 4 }, + .elem_size = sizeof(u32), +}; + +static int imx500_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx500 *imx500 = to_imx500(sd); + + if (code->pad >= NUM_PADS) + return -EINVAL; + + if (code->pad == IMAGE_PAD) { + if (code->index != 0) + return -EINVAL; + + code->code = imx500_get_format_code(imx500); + } else { + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SENSOR_DATA; + } + + return 0; +} + +static int imx500_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx500 *imx500 = to_imx500(sd); + + if (fse->pad >= NUM_PADS) + return -EINVAL; + + if (fse->pad == IMAGE_PAD) { + const struct imx500_mode *mode_list = imx500_supported_modes; + unsigned int num_modes = ARRAY_SIZE(imx500_supported_modes); + + if (fse->index >= num_modes) + return -EINVAL; + + if (fse->code != imx500_get_format_code(imx500)) + return -EINVAL; + + fse->min_width = mode_list[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = mode_list[fse->index].height; + fse->max_height = fse->min_height; + } else { + if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0) + return -EINVAL; + + fse->min_width = IMX500_MAX_EMBEDDED_SIZE + + imx500->num_inference_lines * + IMX500_INFERENCE_LINE_WIDTH; + fse->max_width = fse->min_width; + fse->min_height = 1; + fse->max_height = fse->min_height; + } + + return 0; +} + +static void imx500_update_image_pad_format(struct imx500 *imx500, + const struct imx500_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_RAW; + fmt->format.ycbcr_enc = + V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->format.colorspace); + fmt->format.quantization = V4L2_MAP_QUANTIZATION_DEFAULT( + true, fmt->format.colorspace, fmt->format.ycbcr_enc); + fmt->format.xfer_func = + V4L2_MAP_XFER_FUNC_DEFAULT(fmt->format.colorspace); +} + +static void imx500_update_metadata_pad_format(const struct imx500 *imx500, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = + IMX500_MAX_EMBEDDED_SIZE + + imx500->num_inference_lines * IMX500_INFERENCE_LINE_WIDTH; + fmt->format.height = 1; + fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int imx500_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx500 *imx500 = to_imx500(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx500->mutex); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *try_fmt = v4l2_subdev_state_get_format( + sd_state, fmt->pad); + /* update the code which could change due to vflip or hflip */ + try_fmt->code = fmt->pad == IMAGE_PAD ? + imx500_get_format_code(imx500) : + MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format = *try_fmt; + } else { + if (fmt->pad == IMAGE_PAD) { + imx500_update_image_pad_format(imx500, imx500->mode, + fmt); + fmt->format.code = imx500_get_format_code(imx500); + } else { + imx500_update_metadata_pad_format(imx500, fmt); + } + } + + mutex_unlock(&imx500->mutex); + return 0; +} + +static void imx500_set_framing_limits(struct imx500 *imx500) +{ + unsigned int hblank_min; + const struct imx500_mode *mode = imx500->mode; + + /* Default to no long exposure multiplier. */ + imx500->long_exp_shift = 0; + + /* Update limits and set FPS to default */ + __v4l2_ctrl_modify_range( + imx500->vblank, IMX500_VBLANK_MIN, + ((1 << IMX500_LONG_EXP_SHIFT_MAX) * IMX500_FRAME_LENGTH_MAX) - + mode->height, 1, IMX500_VBLANK_MIN); + + /* Setting this will adjust the exposure limits as well. */ + __v4l2_ctrl_s_ctrl(imx500->vblank, IMX500_VBLANK_MIN); + + hblank_min = mode->line_length_pix - mode->width; + __v4l2_ctrl_modify_range(imx500->hblank, hblank_min, hblank_min, 1, + hblank_min); + __v4l2_ctrl_s_ctrl(imx500->hblank, hblank_min); +} + +static int imx500_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + const struct imx500_mode *mode; + struct imx500 *imx500 = to_imx500(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx500->mutex); + + if (fmt->pad == IMAGE_PAD) { + const struct imx500_mode *mode_list = imx500_supported_modes; + unsigned int num_modes = ARRAY_SIZE(imx500_supported_modes); + + /* Bayer order varies with flips */ + fmt->format.code = imx500_get_format_code(imx500); + + mode = v4l2_find_nearest_size(mode_list, num_modes, width, + height, fmt->format.width, + fmt->format.height); + imx500_update_image_pad_format(imx500, mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); + *framefmt = fmt->format; + } else if (imx500->mode != mode) { + imx500->mode = mode; + imx500->fmt_code = fmt->format.code; + imx500_set_framing_limits(imx500); + } + } else { + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); + *framefmt = fmt->format; + } else { + /* Only one embedded data mode is supported */ + imx500_update_metadata_pad_format(imx500, fmt); + } + } + + mutex_unlock(&imx500->mutex); + + return 0; +} + +static const struct v4l2_rect * +__imx500_get_pad_crop(struct imx500 *imx500, struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_state_get_crop(sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &imx500->mode->crop; + } + + return NULL; +} + +static int imx500_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct imx500 *imx500 = to_imx500(sd); + + mutex_lock(&imx500->mutex); + sel->r = *__imx500_get_pad_crop(imx500, sd_state, sel->pad, + sel->which); + mutex_unlock(&imx500->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = IMX500_NATIVE_WIDTH; + sel->r.height = IMX500_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = IMX500_PIXEL_ARRAY_LEFT; + sel->r.top = IMX500_PIXEL_ARRAY_TOP; + sel->r.width = IMX500_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX500_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +static int __must_check imx500_spi_write(struct imx500 *state, const u8 *data, + size_t size) +{ + if (size % 4 || size > ONE_MIB) + return -EINVAL; + + if (!state->spi_device) + return -ENODEV; + + return spi_write(state->spi_device, data, size); +} + +/* Moves the IMX500 internal state machine between states or updates. + * + * Prerequisites: Sensor is powered on and not currently streaming + */ +static int imx500_state_transition(struct imx500 *imx500, const u8 *fw, + size_t fw_size, enum imx500_image_type type, + bool update) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + struct cci_reg_sequence cmd_reply_sts_cnt_reg; + size_t valid_size; + int ret; + u64 tmp; + + if (!imx500 || !fw || type >= TYPE_MAX) + return -EINVAL; + + if (!update && (int)type != (int)imx500->fsm_state) + return -EINVAL; + + /* Validate firmware */ + valid_size = imx500_valid_fw_bytes(fw, fw_size); + if (!valid_size) + return -EINVAL; + + ret = imx500_prepare_poll_cmd_reply_sts(imx500, &cmd_reply_sts_cnt_reg); + if (ret) + return ret; + + struct cci_reg_sequence common_regs[] = { + { IMX500_REG_DD_FLASH_TYPE, 0x02 }, + { IMX500_REG_DD_LOAD_MODE, IMX500_DD_LOAD_MODE_AP }, + { IMX500_REG_DD_IMAGE_TYPE, type }, + { IMX500_REG_DD_DOWNLOAD_DIV_NUM, (valid_size - 1) / ONE_MIB }, + { IMX500_REG_DD_DOWNLOAD_FILE_SIZE, valid_size }, + }; + + struct cci_reg_sequence state_transition_regs[] = { + { IMX500_REG_DD_ST_TRANS_CMD, type }, + { IMX500_REG_DD_CMD_INT, IMX500_DD_CMD_INT_ST_TRANS }, + }; + + struct cci_reg_sequence update_regs[] = { + { IMX500_REG_DD_UPDATE_CMD, IMX500_DD_UPDATE_CMD_SRAM }, + { IMX500_REG_DD_CMD_INT, IMX500_DD_CMD_INT_UPDATE }, + }; + + ret = cci_multi_reg_write(imx500->regmap, common_regs, + ARRAY_SIZE(common_regs), NULL); + + cci_multi_reg_write(imx500->regmap, + update ? update_regs : state_transition_regs, 2, + &ret); + if (ret) + return ret; + + /* Poll CMD_REPLY_STS_CNT until a response is available */ + ret = imx500_poll_status_reg(imx500, &cmd_reply_sts_cnt_reg, 5); + if (ret) { + dev_err(&client->dev, "DD_REF_STS register did not update\n"); + return ret; + } + + /* Read response to state transition / update request */ + ret = cci_read(imx500->regmap, IMX500_REG_DD_CMD_REPLY_STS, &tmp, NULL); + if (ret || tmp != (update ? IMX500_DD_CMD_REPLY_STS_UPDATE_READY : + IMX500_DD_CMD_REPLY_STS_TRANS_READY)) + return ret ? ret : -EBUSY; + + imx500->fw_stage = type; + imx500->fw_progress = 0; + + for (size_t i = 0; i <= valid_size / ONE_MIB; i++) { + const u8 *data = fw + (i * ONE_MIB); + size_t size = valid_size - (i * ONE_MIB); + struct cci_reg_sequence download_sts_reg = { + IMX500_REG_DD_DOWNLOAD_STS, + IMX500_DD_DOWNLOAD_STS_DOWNLOADING, + }; + + /* Calculate SPI xfer size avoiding 0-sized TXNs */ + size = min_t(size_t, size, ONE_MIB); + if (!size) + break; + + /* Poll until device is ready for download */ + ret = imx500_poll_status_reg(imx500, &download_sts_reg, 100); + if (ret) { + dev_err(&client->dev, + "DD_DOWNLOAD_STS was never ready\n"); + return ret; + } + + /* Do SPI transfer */ + gpiod_set_value_cansleep(imx500->led_gpio, 1); + ret = imx500_spi_write(imx500, data, size); + gpiod_set_value_cansleep(imx500->led_gpio, 0); + + imx500->fw_progress += size; + + if (ret < 0) + return ret; + } + + /* Poll until another response is available */ + ret = imx500_poll_status_reg(imx500, &cmd_reply_sts_cnt_reg, 5); + if (ret) { + dev_err(&client->dev, + "DD_REF_STS register did not update after SPI write(s)\n"); + return ret; + } + + /* Verify that state transition / update completed successfully */ + ret = cci_read(imx500->regmap, IMX500_REG_DD_CMD_REPLY_STS, &tmp, NULL); + if (ret || tmp != (update ? IMX500_DD_CMD_REPLY_STS_UPDATE_DONE : + IMX500_DD_CMD_REPLY_STS_TRANS_DONE)) + return ret ? ret : -EREMOTEIO; + + if (!update && imx500->fsm_state < IMX500_STATE_WITH_NETWORK) + imx500->fsm_state++; + + imx500->fw_progress = fw_size; + + return 0; +} + +static int imx500_transition_to_standby_wo_network(struct imx500 *imx500) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + const struct firmware *firmware; + u64 fw_ver; + int ret; + + firmware = imx500->fw_loader; + ret = imx500_state_transition(imx500, firmware->data, firmware->size, + TYPE_LOADER, false); + if (ret) { + dev_err(&client->dev, "%s: failed to load loader firmware\n", + __func__); + return ret; + } + + firmware = imx500->fw_main; + ret = imx500_state_transition(imx500, firmware->data, firmware->size, + TYPE_MAIN, false); + if (ret) { + dev_err(&client->dev, "%s: failed to load main firmware\n", + __func__); + return ret; + } + + ret = cci_read(imx500->regmap, IMX500_REG_MAIN_FW_VERSION, &fw_ver, + NULL); + if (ret) { + dev_err(&client->dev, + "%s: could not read main firmware version\n", __func__); + return ret; + } + + dev_info(&client->dev, + "main firmware version: %llu%llu.%llu%llu.%llu%llu\n", + (fw_ver >> 20) & 0xF, (fw_ver >> 16) & 0xF, + (fw_ver >> 12) & 0xF, (fw_ver >> 8) & 0xF, (fw_ver >> 4) & 0xF, + fw_ver & 0xF); + + ret = cci_multi_reg_write(imx500->regmap, metadata_output, + ARRAY_SIZE(metadata_output), NULL); + if (ret) { + dev_err(&client->dev, + "%s: failed to configure MIPI output for DNN\n", + __func__); + return ret; + } + + ret = cci_multi_reg_write(imx500->regmap, dnn_regs, + ARRAY_SIZE(dnn_regs), NULL); + if (ret) { + dev_err(&client->dev, "%s: unable to write DNN regs\n", + __func__); + return ret; + } + + return 0; +} + +static int imx500_transition_to_network(struct imx500 *imx500) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + u64 imx500_fsm_state; + int ret; + + ret = imx500_iterate_nw_regs(imx500->fw_network, + imx500->fw_network_size, imx500, + imx500_reg_val_write_cbk); + if (ret) { + dev_err(&client->dev, + "%s: unable to apply register writes from firmware\n", + __func__); + return ret; + } + + /* Read IMX500 state to determine whether transition or update is required */ + ret = cci_read(imx500->regmap, IMX500_REG_DD_SYS_STATE, + &imx500_fsm_state, NULL); + if (ret || imx500_fsm_state & 1) + return ret ? ret : -EREMOTEIO; + + ret = imx500_state_transition( + imx500, imx500->fw_network, imx500->fw_network_size, + TYPE_NW_WEIGHTS, + imx500_fsm_state == IMX500_DD_SYS_STATE_STANDBY_WITH_NETWORK); + if (ret) { + dev_err(&client->dev, "%s: failed to load network weights\n", + __func__); + return ret; + } + + /* Select network 0 */ + ret = cci_write(imx500->regmap, CCI_REG8(0xD701), 0, NULL); + if (ret) { + dev_err(&client->dev, "%s: failed to select network 0\n", + __func__); + return ret; + } + + return ret; +} + +/* Start streaming */ +static int imx500_start_streaming(struct imx500 *imx500) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + const struct imx500_reg_list *reg_list; + int ret; + + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + return ret; + + ret = cci_write(imx500->regmap, IMX500_REG_IMAGE_ONLY_MODE, + imx500->fw_network ? IMX500_IMAGE_ONLY_FALSE : + IMX500_IMAGE_ONLY_TRUE, + NULL); + if (ret) { + dev_err(&client->dev, "%s failed to set image mode\n", + __func__); + return ret; + } + + /* Acquire loader and main firmware if needed */ + if (imx500->fw_network) { + if (!imx500->fw_loader) { + ret = request_firmware(&imx500->fw_loader, + "imx500_loader.fpk", + &client->dev); + if (ret) { + dev_err(&client->dev, + "Unable to acquire firmware loader\n"); + return ret; + } + } + if (!imx500->fw_main) { + ret = request_firmware(&imx500->fw_main, + "imx500_firmware.fpk", + &client->dev); + if (ret) { + dev_err(&client->dev, + "Unable to acquire main firmware\n"); + return ret; + } + } + } + + if (!imx500->common_regs_written) { + ret = cci_multi_reg_write(imx500->regmap, mode_common_regs, + ARRAY_SIZE(mode_common_regs), NULL); + + if (ret) { + dev_err(&client->dev, + "%s failed to set common settings\n", __func__); + return ret; + } + + imx500->common_regs_written = true; + } + + if (imx500->fw_network && !imx500->loader_and_main_written) { + ret = imx500_transition_to_standby_wo_network(imx500); + if (ret) { + dev_err(&client->dev, + "%s failed to transition from program empty state\n", + __func__); + return ret; + } + imx500->loader_and_main_written = true; + } + + if (imx500->fw_network && !imx500->network_written) { + ret = imx500_transition_to_network(imx500); + if (ret) { + dev_err(&client->dev, + "%s failed to transition to network loaded\n", + __func__); + return ret; + } + imx500->network_written = true; + } + + /* Enable DNN */ + if (imx500->fw_network) { + ret = cci_write(imx500->regmap, CCI_REG8(0xD100), 4, NULL); + if (ret) { + dev_err(&client->dev, "%s failed to enable DNN\n", + __func__); + return ret; + } + } + + /* Apply default values of current mode */ + reg_list = &imx500->mode->reg_list; + ret = cci_multi_reg_write(imx500->regmap, reg_list->regs, + reg_list->num_of_regs, NULL); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx500->sd.ctrl_handler); + + /* Disable any sensor startup frame drops. This must be written here! */ + cci_write(imx500->regmap, CCI_REG8(0xD405), 0, &ret); + + /* set stream on register */ + cci_write(imx500->regmap, IMX500_REG_MODE_SELECT, IMX500_MODE_STREAMING, + &ret); + + return ret; +} + +/* Stop streaming */ +static void imx500_stop_streaming(struct imx500 *imx500) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + int ret; + + /* set stream off register */ + ret = cci_write(imx500->regmap, IMX500_REG_MODE_SELECT, + IMX500_MODE_STANDBY, NULL); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); + + /* Disable DNN */ + ret = cci_write(imx500->regmap, CCI_REG8(0xD100), 0, NULL); + if (ret) + dev_err(&client->dev, "%s failed to disable DNN\n", __func__); + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); +} + +static int imx500_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx500 *imx500 = to_imx500(sd); + int ret = 0; + + mutex_lock(&imx500->mutex); + if (imx500->streaming == enable) { + mutex_unlock(&imx500->mutex); + return 0; + } + + if (enable) { + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx500_start_streaming(imx500); + if (ret) + goto err_start_streaming; + } else { + imx500_stop_streaming(imx500); + } + + imx500->streaming = enable; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx500->vflip, enable); + __v4l2_ctrl_grab(imx500->hflip, enable); + __v4l2_ctrl_grab(imx500->network_fw_ctrl, enable); + + mutex_unlock(&imx500->mutex); + + return ret; + +err_start_streaming: + mutex_unlock(&imx500->mutex); + + return ret; +} + +/* Power/clock management functions */ +static int imx500_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx500 *imx500 = to_imx500(sd); + int ret; + + ret = regulator_bulk_enable(IMX500_NUM_SUPPLIES, imx500->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + /* T4 - 1us + * Ambiguous: Regulators rising to INCK start is specified by the datasheet + * but also "Presence of INCK during Power off is acceptable" + */ + udelay(2); + + ret = clk_prepare_enable(imx500->xclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", __func__); + goto reg_off; + } + + /* T5 - 0ms + * Ambiguous: Regulators rising to XCLR rising is specified by the datasheet + * as 0ms but also "XCLR pin should be set to 'High' after INCK supplied.". + * T4 and T5 are shown as overlapping. + */ + gpiod_set_value_cansleep(imx500->reset_gpio, 1); + + /* T7 - 9ms + * "INCK start and CXLR rising till Send Streaming Command wait time" + */ + usleep_range(9000, 12000); + + return 0; + +reg_off: + regulator_bulk_disable(IMX500_NUM_SUPPLIES, imx500->supplies); + return ret; +} + +static int imx500_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx500 *imx500 = to_imx500(sd); + + /* Datasheet specifies power down sequence as INCK disable, XCLR low, + * regulator disable. T1 (XCLR neg-edge to regulator disable) is specified + * as 0us. + * + * Note, this is not the reverse order of power up. + */ + clk_disable_unprepare(imx500->xclk); + gpiod_set_value_cansleep(imx500->reset_gpio, 0); + regulator_bulk_disable(IMX500_NUM_SUPPLIES, imx500->supplies); + + /* Force reprogramming of the common registers when powered up again. */ + imx500->fsm_state = IMX500_STATE_RESET; + imx500->common_regs_written = false; + imx500->loader_and_main_written = false; + imx500_clear_fw_network(imx500); + + return 0; +} + +static int imx500_get_regulators(struct imx500 *imx500) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + unsigned int i; + + for (i = 0; i < IMX500_NUM_SUPPLIES; i++) + imx500->supplies[i].supply = imx500_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, IMX500_NUM_SUPPLIES, + imx500->supplies); +} + +/* Verify chip ID */ +static int imx500_identify_module(struct imx500 *imx500) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + int ret; + u64 val; + + ret = cci_read(imx500->regmap, IMX500_REG_CHIP_ID, &val, NULL); + if (ret) { + dev_err(&client->dev, + "failed to read chip id %x, with error %d\n", + IMX500_CHIP_ID, ret); + return ret; + } + + if (val != IMX500_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%llx\n", + IMX500_CHIP_ID, val); + return -EIO; + } + + dev_info(&client->dev, "Device found is imx%llx\n", val); + + return 0; +} + +static const struct v4l2_subdev_core_ops imx500_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops imx500_video_ops = { + .s_stream = imx500_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx500_pad_ops = { + .enum_mbus_code = imx500_enum_mbus_code, + .get_fmt = imx500_get_pad_format, + .set_fmt = imx500_set_pad_format, + .get_selection = imx500_get_selection, + .enum_frame_size = imx500_enum_frame_size, +}; + +static const struct v4l2_subdev_ops imx500_subdev_ops = { + .core = &imx500_core_ops, + .video = &imx500_video_ops, + .pad = &imx500_pad_ops, +}; + +static const s64 imx500_link_freq_menu[] = { + IMX500_DEFAULT_LINK_FREQ, +}; + +/* Custom control for inference window */ +static const struct v4l2_ctrl_config inf_window_ctrl = { + .name = "IMX500 Inference Windows", + .id = V4L2_CID_USER_IMX500_INFERENCE_WINDOW, + .dims[0] = 4, + .ops = &imx500_ctrl_ops, + .type = V4L2_CTRL_TYPE_U32, + .elem_size = sizeof(u32), + .flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | + V4L2_CTRL_FLAG_HAS_PAYLOAD, + .def = 0, + .min = 0x00, + .max = 4032, + .step = 1, +}; + +/* Custom control for network firmware file FD */ +static const struct v4l2_ctrl_config network_fw_fd = { + .name = "IMX500 Network Firmware File FD", + .id = V4L2_CID_USER_IMX500_NETWORK_FW_FD, + .ops = &imx500_ctrl_ops, + .type = V4L2_CTRL_TYPE_INTEGER, + .flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | + V4L2_CTRL_FLAG_WRITE_ONLY, + .min = -1, + .max = S32_MAX, + .step = 1, + .def = -1, +}; + +/* Initialize control handlers */ +static int imx500_init_controls(struct imx500 *imx500) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + struct v4l2_fwnode_device_properties props; + int ret; + + ctrl_hdlr = &imx500->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16); + if (ret) + return ret; + + mutex_init(&imx500->mutex); + ctrl_hdlr->lock = &imx500->mutex; + + /* By default, PIXEL_RATE is read only */ + imx500->pixel_rate = v4l2_ctrl_new_std( + ctrl_hdlr, &imx500_ctrl_ops, V4L2_CID_PIXEL_RATE, + IMX500_PIXEL_RATE, IMX500_PIXEL_RATE, 1, IMX500_PIXEL_RATE); + + /* LINK_FREQ is also read only */ + imx500->link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx500_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(imx500_link_freq_menu) - 1, 0, + imx500_link_freq_menu); + if (imx500->link_freq) + imx500->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* + * Create the controls here, but mode specific limits are setup + * in the imx500_set_framing_limits() call below. + */ + imx500->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops, + V4L2_CID_VBLANK, IMX500_VBLANK_MIN, + 0xffff, 1, IMX500_VBLANK_MIN); + imx500->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops, + V4L2_CID_HBLANK, 0, 0xffff, 1, 0); + + imx500->exposure = v4l2_ctrl_new_std( + ctrl_hdlr, &imx500_ctrl_ops, V4L2_CID_EXPOSURE, + IMX500_EXPOSURE_MIN, IMX500_EXPOSURE_MAX, IMX500_EXPOSURE_STEP, + IMX500_EXPOSURE_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX500_ANA_GAIN_MIN, IMX500_ANA_GAIN_MAX, + IMX500_ANA_GAIN_STEP, IMX500_ANA_GAIN_DEFAULT); + + imx500->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (imx500->hflip) + imx500->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + imx500->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (imx500->vflip) + imx500->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + v4l2_ctrl_new_custom(ctrl_hdlr, &imx500_notify_gains_ctrl, NULL); + v4l2_ctrl_new_custom(ctrl_hdlr, &inf_window_ctrl, NULL); + imx500->network_fw_ctrl = + v4l2_ctrl_new_custom(ctrl_hdlr, &network_fw_fd, NULL); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", __func__, + ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx500_ctrl_ops, + &props); + if (ret) + goto error; + + imx500->sd.ctrl_handler = ctrl_hdlr; + + /* Setup exposure and frame/line length limits. */ + imx500_set_framing_limits(imx500); + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&imx500->mutex); + + return ret; +} + +static void imx500_free_controls(struct imx500 *imx500) +{ + v4l2_ctrl_handler_free(imx500->sd.ctrl_handler); + mutex_destroy(&imx500->mutex); +} + +static int imx500_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { .bus_type = + V4L2_MBUS_CSI2_DPHY }; + int ret = -EINVAL; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + /* Check the number of MIPI CSI2 data lanes */ + if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) { + dev_err(dev, "only 2 data lanes are currently supported\n"); + goto error_out; + } + + /* Check the link frequency set in device tree */ + if (!ep_cfg.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property not found in DT\n"); + goto error_out; + } + + if (ep_cfg.nr_of_link_frequencies != 1 || + ep_cfg.link_frequencies[0] != IMX500_DEFAULT_LINK_FREQ) { + dev_err(dev, "Link frequency not supported: %lld\n", + ep_cfg.link_frequencies[0]); + goto error_out; + } + + ret = 0; + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static int fw_progress_show(struct seq_file *s, void *data) +{ + struct imx500 *imx500 = s->private; + + seq_printf(s, "%d %zu %zu\n", imx500->fw_stage, imx500->fw_progress, + imx500->fw_network_size); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(fw_progress); + +static int imx500_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct spi_device *spi = NULL; + char debugfs_name[128]; + struct imx500 *imx500; + int ret; + + struct device_node *spi_node = of_parse_phandle(dev->of_node, "spi", 0); + + if (spi_node) { + struct device *tmp = + bus_find_device_by_of_node(&spi_bus_type, spi_node); + of_node_put(spi_node); + spi = tmp ? to_spi_device(tmp) : NULL; + if (!spi) + return -EPROBE_DEFER; + } + + imx500 = devm_kzalloc(&client->dev, sizeof(*imx500), GFP_KERNEL); + if (!imx500) + return -ENOMEM; + + imx500->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(imx500->regmap)) + return dev_err_probe(dev, PTR_ERR(imx500->regmap), + "failed to initialise CCI\n"); + + imx500->spi_device = spi; + + v4l2_i2c_subdev_init(&imx500->sd, client, &imx500_subdev_ops); + + /* Check the hardware configuration in device tree */ + if (imx500_check_hwcfg(dev)) + return -EINVAL; + + /* Get system clock (xclk) */ + imx500->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(imx500->xclk)) + return dev_err_probe(dev, PTR_ERR(imx500->xclk), + "failed to get xclk\n"); + + imx500->xclk_freq = clk_get_rate(imx500->xclk); + if (imx500->xclk_freq != IMX500_XCLK_FREQ) { + dev_err(dev, "xclk frequency not supported: %d Hz\n", + imx500->xclk_freq); + return -EINVAL; + } + + ret = imx500_get_regulators(imx500); + if (ret) { + dev_err(dev, "failed to get regulators\n"); + return ret; + } + + imx500->led_gpio = devm_gpiod_get_optional(dev, "led", GPIOD_OUT_LOW); + + imx500->reset_gpio = + devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + + /* + * The sensor must be powered for imx500_identify_module() + * to be able to read the CHIP_ID register + */ + ret = imx500_power_on(dev); + if (ret) + return ret; + + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 5000); + pm_runtime_use_autosuspend(dev); + + ret = imx500_identify_module(imx500); + if (ret) + goto error_power_off; + + /* Initialize default format */ + imx500_set_default_format(imx500); + + /* This needs the pm runtime to be registered. */ + ret = imx500_init_controls(imx500); + if (ret) + goto error_power_off; + + /* Initialize subdev */ + imx500->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + imx500->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pads */ + imx500->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE; + imx500->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&imx500->sd.entity, NUM_PADS, imx500->pad); + if (ret) { + dev_err(dev, "failed to init entity pads: %d\n", ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&imx500->sd); + if (ret < 0) { + dev_err(dev, "failed to register sensor sub-device: %d\n", ret); + goto error_media_entity; + } + + snprintf(debugfs_name, sizeof(debugfs_name), "imx500-fw:%s", + dev_name(dev)); + imx500->debugfs = debugfs_create_dir(debugfs_name, NULL); + debugfs_create_file("fw_progress", 0444, imx500->debugfs, imx500, + &fw_progress_fops); + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&imx500->sd.entity); + +error_handler_free: + imx500_free_controls(imx500); + +error_power_off: + pm_runtime_disable(&client->dev); + pm_runtime_put_noidle(&client->dev); + imx500_power_off(&client->dev); + + return ret; +} + +static void imx500_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx500 *imx500 = to_imx500(sd); + + if (imx500->spi_device) + put_device(&imx500->spi_device->dev); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + imx500_free_controls(imx500); + + if (imx500->fw_loader) + release_firmware(imx500->fw_loader); + + if (imx500->fw_main) + release_firmware(imx500->fw_main); + + imx500->fw_loader = NULL; + imx500->fw_main = NULL; + imx500_clear_fw_network(imx500); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + imx500_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id imx500_dt_ids[] = { + { .compatible = "sony,imx500" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, imx500_dt_ids); + +static const struct dev_pm_ops imx500_pm_ops = { SET_RUNTIME_PM_OPS( + imx500_power_off, imx500_power_on, NULL) }; + +static struct i2c_driver imx500_i2c_driver = { + .driver = { + .name = "imx500", + .of_match_table = imx500_dt_ids, + .pm = &imx500_pm_ops, + }, + .probe = imx500_probe, + .remove = imx500_remove, +}; + +static int imx500_spi_probe(struct spi_device *spi) +{ + int result; + + spi->bits_per_word = 8; + spi->max_speed_hz = 35000000; + spi->mode = SPI_MODE_3; + + result = spi_setup(spi); + if (result < 0) + return dev_err_probe(&spi->dev, result, "spi_setup() failed"); + + return 0; +} + +static void imx500_spi_remove(struct spi_device *spi) +{ +} + +static const struct spi_device_id imx500_spi_id[] = { + { "imx500", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(spi, imx500_spi_id); + +static struct spi_driver imx500_spi_driver = { + .driver = { + .name = "imx500", + .of_match_table = imx500_dt_ids, + }, + .probe = imx500_spi_probe, + .remove = imx500_spi_remove, + .id_table = imx500_spi_id, +}; + +static int __init imx500_driver_init(void) +{ + int ret; + + ret = spi_register_driver(&imx500_spi_driver); + if (ret) + return ret; + + ret = i2c_add_driver(&imx500_i2c_driver); + if (ret) + spi_unregister_driver(&imx500_spi_driver); + + return ret; +} +module_init(imx500_driver_init); + +static void __exit imx500_driver_exit(void) +{ + i2c_del_driver(&imx500_i2c_driver); + spi_unregister_driver(&imx500_spi_driver); +} +module_exit(imx500_driver_exit); + +MODULE_AUTHOR("Naushir Patuck "); +MODULE_DESCRIPTION("Sony IMX500 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 959b6288c5f393..f7ac33a1d57bc1 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -219,6 +219,12 @@ enum v4l2_colorfx { * We reserve 16 controls for this driver. */ #define V4L2_CID_USER_BCM2835_ISP_BASE (V4L2_CID_USER_BASE + 0x10e0) +/* + * The base for IMX500 driver controls. + * We reserve 16 controls for this driver. + */ +#define V4L2_CID_USER_IMX500_BASE (V4L2_CID_USER_BASE + 0x2000) + /* MPEG-class control IDs */ /* The MPEG controls are applicable to all codec controls * and the 'MPEG' part of the define is historical */