@@ -1046,6 +1046,19 @@ config VIDEO_OV8856
To compile this driver as a module, choose M here: the
module will be called ov8856.
+config VIDEO_OV8865
+ tristate "OmniVision OV8865 sensor support"
+ depends on I2C && PM && VIDEO_V4L2
+ select MEDIA_CONTROLLER
+ select VIDEO_V4L2_SUBDEV_API
+ select V4L2_FWNODE
+ help
+ This is a Video4Linux2 sensor driver for OmniVision
+ OV8865 camera sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ov8865.
+
config VIDEO_OV9640
tristate "OmniVision OV9640 sensor support"
depends on I2C && VIDEO_V4L2
@@ -82,6 +82,7 @@ obj-$(CONFIG_VIDEO_OV7670) += ov7670.o
obj-$(CONFIG_VIDEO_OV772X) += ov772x.o
obj-$(CONFIG_VIDEO_OV7740) += ov7740.o
obj-$(CONFIG_VIDEO_OV8856) += ov8856.o
+obj-$(CONFIG_VIDEO_OV8865) += ov8865.o
obj-$(CONFIG_VIDEO_OV9640) += ov9640.o
obj-$(CONFIG_VIDEO_OV9650) += ov9650.o
obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
new file mode 100644
@@ -0,0 +1,2972 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2020 Kévin L'hôpital <kevin.lhopital@bootlin.com>
+ * Copyright 2020 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-image-sizes.h>
+#include <media/v4l2-mediabus.h>
+
+/* Clock rate */
+
+#define OV8865_EXTCLK_RATE 24000000
+
+/* Register definitions */
+
+/* System */
+
+#define OV8865_SW_STANDBY_REG 0x100
+#define OV8865_SW_STANDBY_STREAM_ON BIT(0)
+
+#define OV8865_SW_RESET_REG 0x103
+#define OV8865_SW_RESET_RESET BIT(0)
+
+#define OV8865_PLL_CTRL0_REG 0x300
+#define OV8865_PLL_CTRL0_PRE_DIV(v) ((v) & GENMASK(2, 0))
+#define OV8865_PLL_CTRL1_REG 0x301
+#define OV8865_PLL_CTRL1_MUL_H(v) (((v) & GENMASK(9, 8)) >> 8)
+#define OV8865_PLL_CTRL2_REG 0x302
+#define OV8865_PLL_CTRL2_MUL_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_PLL_CTRL3_REG 0x303
+#define OV8865_PLL_CTRL3_M_DIV(v) (((v) - 1) & GENMASK(3, 0))
+#define OV8865_PLL_CTRL4_REG 0x304
+#define OV8865_PLL_CTRL4_MIPI_DIV(v) ((v) & GENMASK(1, 0))
+#define OV8865_PLL_CTRL5_REG 0x305
+#define OV8865_PLL_CTRL5_SYS_PRE_DIV(v) ((v) & GENMASK(1, 0))
+#define OV8865_PLL_CTRL6_REG 0x306
+#define OV8865_PLL_CTRL6_SYS_DIV(v) (((v) - 1) & BIT(0))
+
+#define OV8865_PLL_CTRL8_REG 0x308
+#define OV8865_PLL_CTRL9_REG 0x309
+#define OV8865_PLL_CTRLA_REG 0x30a
+#define OV8865_PLL_CTRLA_PRE_DIV_HALF(v) (((v) - 1) & BIT(0))
+#define OV8865_PLL_CTRLB_REG 0x30b
+#define OV8865_PLL_CTRLB_PRE_DIV(v) ((v) & GENMASK(2, 0))
+#define OV8865_PLL_CTRLC_REG 0x30c
+#define OV8865_PLL_CTRLC_MUL_H(v) (((v) & GENMASK(9, 8)) >> 8)
+#define OV8865_PLL_CTRLD_REG 0x30d
+#define OV8865_PLL_CTRLD_MUL_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_PLL_CTRLE_REG 0x30e
+#define OV8865_PLL_CTRLE_SYS_DIV(v) ((v) & GENMASK(2, 0))
+#define OV8865_PLL_CTRLF_REG 0x30f
+#define OV8865_PLL_CTRLF_SYS_PRE_DIV(v) (((v) - 1) & GENMASK(3, 0))
+#define OV8865_PLL_CTRL10_REG 0x310
+#define OV8865_PLL_CTRL11_REG 0x311
+#define OV8865_PLL_CTRL12_REG 0x312
+#define OV8865_PLL_CTRL12_PRE_DIV_HALF(v) ((((v) - 1) << 4) & BIT(4))
+#define OV8865_PLL_CTRL12_DAC_DIV(v) (((v) - 1) & GENMASK(3, 0))
+
+#define OV8865_PLL_CTRL1B_REG 0x31b
+#define OV8865_PLL_CTRL1C_REG 0x31c
+
+#define OV8865_PLL_CTRL1E_REG 0x31e
+#define OV8865_PLL_CTRL1E_PLL1_NO_LAT BIT(3)
+
+#define OV8865_PAD_OEN0_REG 0x3000
+
+#define OV8865_PAD_OEN2_REG 0x3002
+
+#define OV8865_CLK_RST5_REG 0x3005
+
+#define OV8865_CHIP_ID_HH_REG 0x300a
+#define OV8865_CHIP_ID_HH_VALUE 0x00
+#define OV8865_CHIP_ID_H_REG 0x300b
+#define OV8865_CHIP_ID_H_VALUE 0x88
+#define OV8865_CHIP_ID_L_REG 0x300c
+#define OV8865_CHIP_ID_L_VALUE 0x65
+#define OV8865_PAD_OUT2_REG 0x300d
+
+#define OV8865_PAD_SEL2_REG 0x3010
+#define OV8865_PAD_PK_REG 0x3011
+#define OV8865_PAD_PK_DRIVE_STRENGTH_1X (0 << 5)
+#define OV8865_PAD_PK_DRIVE_STRENGTH_2X (1 << 5)
+#define OV8865_PAD_PK_DRIVE_STRENGTH_3X (2 << 5)
+#define OV8865_PAD_PK_DRIVE_STRENGTH_4X (3 << 5)
+
+#define OV8865_PUMP_CLK_DIV_REG 0x3015
+#define OV8865_PUMP_CLK_DIV_PUMP_N(v) (((v) << 4) & GENMASK(6, 4))
+#define OV8865_PUMP_CLK_DIV_PUMP_P(v) ((v) & GENMASK(2, 0))
+
+#define OV8865_MIPI_SC_CTRL0_REG 0x3018
+#define OV8865_MIPI_SC_CTRL0_LANES(v) ((((v) - 1) << 5) & \
+ GENMASK(7, 5))
+#define OV8865_MIPI_SC_CTRL0_MIPI_EN BIT(4)
+#define OV8865_MIPI_SC_CTRL0_UNKNOWN BIT(1)
+#define OV8865_MIPI_SC_CTRL0_LANES_PD_MIPI BIT(0)
+#define OV8865_MIPI_SC_CTRL1_REG 0x3019
+#define OV8865_CLK_RST0_REG 0x301a
+#define OV8865_CLK_RST1_REG 0x301b
+#define OV8865_CLK_RST2_REG 0x301c
+#define OV8865_CLK_RST3_REG 0x301d
+#define OV8865_CLK_RST4_REG 0x301e
+
+#define OV8865_PCLK_SEL_REG 0x3020
+#define OV8865_PCLK_SEL_PCLK_DIV_MASK BIT(3)
+#define OV8865_PCLK_SEL_PCLK_DIV(v) ((((v) - 1) << 3) & BIT(3))
+
+#define OV8865_MISC_CTRL_REG 0x3021
+#define OV8865_MIPI_SC_CTRL2_REG 0x3022
+#define OV8865_MIPI_SC_CTRL2_CLK_LANES_PD_MIPI BIT(1)
+#define OV8865_MIPI_SC_CTRL2_PD_MIPI_RST_SYNC BIT(0)
+
+#define OV8865_MIPI_BIT_SEL_REG 0x3031
+#define OV8865_MIPI_BIT_SEL(v) (((v) << 0) & GENMASK(4, 0))
+#define OV8865_CLK_SEL0_REG 0x3032
+#define OV8865_CLK_SEL0_PLL1_SYS_SEL(v) (((v) << 7) & BIT(7))
+#define OV8865_CLK_SEL1_REG 0x3033
+#define OV8865_CLK_SEL1_MIPI_EOF BIT(5)
+#define OV8865_CLK_SEL1_UNKNOWN BIT(2)
+#define OV8865_CLK_SEL1_PLL_SCLK_SEL_MASK BIT(1)
+#define OV8865_CLK_SEL1_PLL_SCLK_SEL(v) (((v) << 1) & BIT(1))
+
+#define OV8865_SCLK_CTRL_REG 0x3106
+#define OV8865_SCLK_CTRL_SCLK_DIV(v) (((v) << 4) & GENMASK(7, 4))
+#define OV8865_SCLK_CTRL_SCLK_PRE_DIV(v) (((v) << 2) & GENMASK(3, 2))
+#define OV8865_SCLK_CTRL_UNKNOWN BIT(0)
+
+/* Exposure/gain */
+
+#define OV8865_EXPOSURE_CTRL_HH_REG 0x3500
+#define OV8865_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(19, 16)) >> 16)
+#define OV8865_EXPOSURE_CTRL_H_REG 0x3501
+#define OV8865_EXPOSURE_CTRL_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV8865_EXPOSURE_CTRL_L_REG 0x3502
+#define OV8865_EXPOSURE_CTRL_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_EXPOSURE_GAIN_MANUAL_REG 0x3503
+
+#define OV8865_GAIN_CTRL_H_REG 0x3508
+#define OV8865_GAIN_CTRL_H(v) (((v) & GENMASK(12, 8)) >> 8)
+#define OV8865_GAIN_CTRL_L_REG 0x3509
+#define OV8865_GAIN_CTRL_L(v) ((v) & GENMASK(7, 0))
+
+/* Timing */
+
+#define OV8865_CROP_START_X_H_REG 0x3800
+#define OV8865_CROP_START_X_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_CROP_START_X_L_REG 0x3801
+#define OV8865_CROP_START_X_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_CROP_START_Y_H_REG 0x3802
+#define OV8865_CROP_START_Y_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_CROP_START_Y_L_REG 0x3803
+#define OV8865_CROP_START_Y_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_CROP_END_X_H_REG 0x3804
+#define OV8865_CROP_END_X_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_CROP_END_X_L_REG 0x3805
+#define OV8865_CROP_END_X_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_CROP_END_Y_H_REG 0x3806
+#define OV8865_CROP_END_Y_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_CROP_END_Y_L_REG 0x3807
+#define OV8865_CROP_END_Y_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_OUTPUT_SIZE_X_H_REG 0x3808
+#define OV8865_OUTPUT_SIZE_X_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_OUTPUT_SIZE_X_L_REG 0x3809
+#define OV8865_OUTPUT_SIZE_X_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_OUTPUT_SIZE_Y_H_REG 0x380a
+#define OV8865_OUTPUT_SIZE_Y_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_OUTPUT_SIZE_Y_L_REG 0x380b
+#define OV8865_OUTPUT_SIZE_Y_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_HTS_H_REG 0x380c
+#define OV8865_HTS_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_HTS_L_REG 0x380d
+#define OV8865_HTS_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_VTS_H_REG 0x380e
+#define OV8865_VTS_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_VTS_L_REG 0x380f
+#define OV8865_VTS_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_OFFSET_X_H_REG 0x3810
+#define OV8865_OFFSET_X_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV8865_OFFSET_X_L_REG 0x3811
+#define OV8865_OFFSET_X_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_OFFSET_Y_H_REG 0x3812
+#define OV8865_OFFSET_Y_H(v) (((v) & GENMASK(14, 8)) >> 8)
+#define OV8865_OFFSET_Y_L_REG 0x3813
+#define OV8865_OFFSET_Y_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_INC_X_ODD_REG 0x3814
+#define OV8865_INC_X_ODD(v) ((v) & GENMASK(4, 0))
+#define OV8865_INC_X_EVEN_REG 0x3815
+#define OV8865_INC_X_EVEN(v) ((v) & GENMASK(4, 0))
+#define OV8865_VSYNC_START_H_REG 0x3816
+#define OV8865_VSYNC_START_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV8865_VSYNC_START_L_REG 0x3817
+#define OV8865_VSYNC_START_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_VSYNC_END_H_REG 0x3818
+#define OV8865_VSYNC_END_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV8865_VSYNC_END_L_REG 0x3819
+#define OV8865_VSYNC_END_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_HSYNC_FIRST_H_REG 0x381a
+#define OV8865_HSYNC_FIRST_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV8865_HSYNC_FIRST_L_REG 0x381b
+#define OV8865_HSYNC_FIRST_L(v) ((v) & GENMASK(7, 0))
+
+#define OV8865_FORMAT1_REG 0x3820
+#define OV8865_FORMAT1_FLIP_VERT_ISP_EN BIT(2)
+#define OV8865_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1)
+#define OV8865_FORMAT2_REG 0x3821
+#define OV8865_FORMAT2_HSYNC_EN BIT(6)
+#define OV8865_FORMAT2_FST_VBIN_EN BIT(5)
+#define OV8865_FORMAT2_FST_HBIN_EN BIT(4)
+#define OV8865_FORMAT2_ISP_HORZ_VAR2_EN BIT(3)
+#define OV8865_FORMAT2_FLIP_HORZ_ISP_EN BIT(2)
+#define OV8865_FORMAT2_FLIP_HORZ_SENSOR_EN BIT(1)
+#define OV8865_FORMAT2_SYNC_HBIN_EN BIT(0)
+
+
+#define OV8865_INC_Y_ODD_REG 0x382a
+#define OV8865_INC_Y_ODD(v) ((v) & GENMASK(4, 0))
+#define OV8865_INC_Y_EVEN_REG 0x382b
+#define OV8865_INC_Y_EVEN(v) ((v) & GENMASK(4, 0))
+
+#define OV8865_ABLC_NUM_REG 0x3830
+#define OV8865_ABLC_NUM(v) ((v) & GENMASK(4, 0))
+
+#define OV8865_ZLINE_NUM_REG 0x3836
+#define OV8865_ZLINE_NUM(v) ((v) & GENMASK(4, 0))
+
+#define OV8865_AUTO_SIZE_CTRL_REG 0x3841
+#define OV8865_AUTO_SIZE_CTRL_OFFSET_Y_REG BIT(5)
+#define OV8865_AUTO_SIZE_CTRL_OFFSET_X_REG BIT(4)
+#define OV8865_AUTO_SIZE_CTRL_CROP_END_Y_REG BIT(3)
+#define OV8865_AUTO_SIZE_CTRL_CROP_END_X_REG BIT(2)
+#define OV8865_AUTO_SIZE_CTRL_CROP_START_Y_REG BIT(1)
+#define OV8865_AUTO_SIZE_CTRL_CROP_START_X_REG BIT(0)
+#define OV8865_AUTO_SIZE_X_OFFSET_H_REG 0x3842
+#define OV8865_AUTO_SIZE_X_OFFSET_L_REG 0x3843
+#define OV8865_AUTO_SIZE_Y_OFFSET_H_REG 0x3844
+#define OV8865_AUTO_SIZE_Y_OFFSET_L_REG 0x3845
+#define OV8865_AUTO_SIZE_BOUNDARIES_REG 0x3846
+#define OV8865_AUTO_SIZE_BOUNDARIES_Y(v) (((v) << 4) & GENMASK(7, 4))
+#define OV8865_AUTO_SIZE_BOUNDARIES_X(v) ((v) & GENMASK(3, 0))
+
+/* PSRAM */
+
+#define OV8865_PSRAM_CTRL8_REG 0x3f08
+
+/* Black Level */
+
+#define OV8865_BLC_CTRL0_REG 0x4000
+#define OV8865_BLC_CTRL0_TRIG_RANGE_EN BIT(7)
+#define OV8865_BLC_CTRL0_TRIG_FORMAT_EN BIT(6)
+#define OV8865_BLC_CTRL0_TRIG_GAIN_EN BIT(5)
+#define OV8865_BLC_CTRL0_TRIG_EXPOSURE_EN BIT(4)
+#define OV8865_BLC_CTRL0_TRIG_MANUAL_EN BIT(3)
+#define OV8865_BLC_CTRL0_FREEZE_EN BIT(2)
+#define OV8865_BLC_CTRL0_ALWAYS_EN BIT(1)
+#define OV8865_BLC_CTRL0_FILTER_EN BIT(0)
+#define OV8865_BLC_CTRL1_REG 0x4001
+#define OV8865_BLC_CTRL1_DITHER_EN BIT(7)
+#define OV8865_BLC_CTRL1_ZERO_LINE_DIFF_EN BIT(6)
+#define OV8865_BLC_CTRL1_COL_SHIFT_256 (0 << 4)
+#define OV8865_BLC_CTRL1_COL_SHIFT_128 (1 << 4)
+#define OV8865_BLC_CTRL1_COL_SHIFT_64 (2 << 4)
+#define OV8865_BLC_CTRL1_COL_SHIFT_32 (3 << 4)
+#define OV8865_BLC_CTRL1_OFFSET_LIMIT_EN BIT(2)
+#define OV8865_BLC_CTRL1_COLUMN_CANCEL_EN BIT(1)
+#define OV8865_BLC_CTRL2_REG 0x4002
+#define OV8865_BLC_CTRL3_REG 0x4003
+#define OV8865_BLC_CTRL4_REG 0x4004
+#define OV8865_BLC_CTRL5_REG 0x4005
+#define OV8865_BLC_CTRL6_REG 0x4006
+#define OV8865_BLC_CTRL7_REG 0x4007
+#define OV8865_BLC_CTRL8_REG 0x4008
+#define OV8865_BLC_CTRL9_REG 0x4009
+#define OV8865_BLC_CTRLA_REG 0x400a
+#define OV8865_BLC_CTRLB_REG 0x400b
+#define OV8865_BLC_CTRLC_REG 0x400c
+#define OV8865_BLC_CTRLD_REG 0x400d
+#define OV8865_BLC_CTRLD_OFFSET_TRIGGER(v) ((v) & GENMASK(7, 0))
+
+#define OV8865_BLC_CTRL1F_REG 0x401f
+#define OV8865_BLC_CTRL1F_RB_REVERSE BIT(3)
+#define OV8865_BLC_CTRL1F_INTERPOL_X_EN BIT(2)
+#define OV8865_BLC_CTRL1F_INTERPOL_Y_EN BIT(1)
+
+#define OV8865_BLC_ANCHOR_LEFT_START_H_REG 0x4020
+#define OV8865_BLC_ANCHOR_LEFT_START_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_BLC_ANCHOR_LEFT_START_L_REG 0x4021
+#define OV8865_BLC_ANCHOR_LEFT_START_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_BLC_ANCHOR_LEFT_END_H_REG 0x4022
+#define OV8865_BLC_ANCHOR_LEFT_END_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_BLC_ANCHOR_LEFT_END_L_REG 0x4023
+#define OV8865_BLC_ANCHOR_LEFT_END_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_BLC_ANCHOR_RIGHT_START_H_REG 0x4024
+#define OV8865_BLC_ANCHOR_RIGHT_START_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_BLC_ANCHOR_RIGHT_START_L_REG 0x4025
+#define OV8865_BLC_ANCHOR_RIGHT_START_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_BLC_ANCHOR_RIGHT_END_H_REG 0x4026
+#define OV8865_BLC_ANCHOR_RIGHT_END_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_BLC_ANCHOR_RIGHT_END_L_REG 0x4027
+#define OV8865_BLC_ANCHOR_RIGHT_END_L(v) ((v) & GENMASK(7, 0))
+
+#define OV8865_BLC_TOP_ZLINE_START_REG 0x4028
+#define OV8865_BLC_TOP_ZLINE_START(v) ((v) & GENMASK(5, 0))
+#define OV8865_BLC_TOP_ZLINE_NUM_REG 0x4029
+#define OV8865_BLC_TOP_ZLINE_NUM(v) ((v) & GENMASK(4, 0))
+#define OV8865_BLC_TOP_BLKLINE_START_REG 0x402a
+#define OV8865_BLC_TOP_BLKLINE_START(v) ((v) & GENMASK(5, 0))
+#define OV8865_BLC_TOP_BLKLINE_NUM_REG 0x402b
+#define OV8865_BLC_TOP_BLKLINE_NUM(v) ((v) & GENMASK(4, 0))
+#define OV8865_BLC_BOT_ZLINE_START_REG 0x402c
+#define OV8865_BLC_BOT_ZLINE_START(v) ((v) & GENMASK(5, 0))
+#define OV8865_BLC_BOT_ZLINE_NUM_REG 0x402d
+#define OV8865_BLC_BOT_ZLINE_NUM(v) ((v) & GENMASK(4, 0))
+#define OV8865_BLC_BOT_BLKLINE_START_REG 0x402e
+#define OV8865_BLC_BOT_BLKLINE_START(v) ((v) & GENMASK(5, 0))
+#define OV8865_BLC_BOT_BLKLINE_NUM_REG 0x402f
+#define OV8865_BLC_BOT_BLKLINE_NUM(v) ((v) & GENMASK(4, 0))
+
+#define OV8865_BLC_OFFSET_LIMIT_REG 0x4034
+#define OV8865_BLC_OFFSET_LIMIT(v) ((v) & GENMASK(7, 0))
+
+/* VFIFO */
+
+#define OV8865_VFIFO_READ_START_H_REG 0x4600
+#define OV8865_VFIFO_READ_START_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV8865_VFIFO_READ_START_L_REG 0x4601
+#define OV8865_VFIFO_READ_START_L(v) ((v) & GENMASK(7, 0))
+
+/* MIPI */
+
+#define OV8865_MIPI_CTRL0_REG 0x4800
+#define OV8865_MIPI_CTRL1_REG 0x4801
+#define OV8865_MIPI_CTRL2_REG 0x4802
+#define OV8865_MIPI_CTRL3_REG 0x4803
+#define OV8865_MIPI_CTRL4_REG 0x4804
+#define OV8865_MIPI_CTRL5_REG 0x4805
+#define OV8865_MIPI_CTRL6_REG 0x4806
+#define OV8865_MIPI_CTRL7_REG 0x4807
+#define OV8865_MIPI_CTRL8_REG 0x4808
+
+#define OV8865_MIPI_FCNT_MAX_H_REG 0x4810
+#define OV8865_MIPI_FCNT_MAX_L_REG 0x4811
+
+#define OV8865_MIPI_CTRL13_REG 0x4813
+#define OV8865_MIPI_CTRL14_REG 0x4814
+#define OV8865_MIPI_CTRL15_REG 0x4815
+#define OV8865_MIPI_EMBEDDED_DT_REG 0x4816
+
+#define OV8865_MIPI_HS_ZERO_MIN_H_REG 0x4818
+#define OV8865_MIPI_HS_ZERO_MIN_L_REG 0x4819
+#define OV8865_MIPI_HS_TRAIL_MIN_H_REG 0x481a
+#define OV8865_MIPI_HS_TRAIL_MIN_L_REG 0x481b
+#define OV8865_MIPI_CLK_ZERO_MIN_H_REG 0x481c
+#define OV8865_MIPI_CLK_ZERO_MIN_L_REG 0x481d
+#define OV8865_MIPI_CLK_PREPARE_MAX_REG 0x481e
+#define OV8865_MIPI_CLK_PREPARE_MIN_REG 0x481f
+#define OV8865_MIPI_CLK_POST_MIN_H_REG 0x4820
+#define OV8865_MIPI_CLK_POST_MIN_L_REG 0x4821
+#define OV8865_MIPI_CLK_TRAIL_MIN_H_REG 0x4822
+#define OV8865_MIPI_CLK_TRAIL_MIN_L_REG 0x4823
+#define OV8865_MIPI_LPX_P_MIN_H_REG 0x4824
+#define OV8865_MIPI_LPX_P_MIN_L_REG 0x4825
+#define OV8865_MIPI_HS_PREPARE_MIN_REG 0x4826
+#define OV8865_MIPI_HS_PREPARE_MAX_REG 0x4827
+#define OV8865_MIPI_HS_EXIT_MIN_H_REG 0x4828
+#define OV8865_MIPI_HS_EXIT_MIN_L_REG 0x4829
+#define OV8865_MIPI_UI_HS_ZERO_MIN_REG 0x482a
+#define OV8865_MIPI_UI_HS_TRAIL_MIN_REG 0x482b
+#define OV8865_MIPI_UI_CLK_ZERO_MIN_REG 0x482c
+#define OV8865_MIPI_UI_CLK_PREPARE_REG 0x482d
+#define OV8865_MIPI_UI_CLK_POST_MIN_REG 0x482e
+#define OV8865_MIPI_UI_CLK_TRAIL_MIN_REG 0x482f
+#define OV8865_MIPI_UI_LPX_P_MIN_REG 0x4830
+#define OV8865_MIPI_UI_HS_PREPARE_REG 0x4831
+#define OV8865_MIPI_UI_HS_EXIT_MIN_REG 0x4832
+#define OV8865_MIPI_PKT_START_SIZE_REG 0x4833
+
+#define OV8865_MIPI_PCLK_PERIOD_REG 0x4837
+#define OV8865_MIPI_LP_GPIO0_REG 0x4838
+#define OV8865_MIPI_LP_GPIO1_REG 0x4839
+
+#define OV8865_MIPI_CTRL3C_REG 0x483c
+#define OV8865_MIPI_LP_GPIO4_REG 0x483d
+
+#define OV8865_MIPI_CTRL4A_REG 0x484a
+#define OV8865_MIPI_CTRL4B_REG 0x484b
+#define OV8865_MIPI_CTRL4C_REG 0x484c
+#define OV8865_MIPI_LANE_TEST_PATTERN_REG 0x484d
+#define OV8865_MIPI_FRAME_END_DELAY_REG 0x484e
+#define OV8865_MIPI_CLOCK_TEST_PATTERN_REG 0x484f
+#define OV8865_MIPI_LANE_SEL01_REG 0x4850
+#define OV8865_MIPI_LANE_SEL01_LANE0(v) (((v) << 0) & GENMASK(2, 0))
+#define OV8865_MIPI_LANE_SEL01_LANE1(v) (((v) << 4) & GENMASK(6, 4))
+#define OV8865_MIPI_LANE_SEL23_REG 0x4851
+#define OV8865_MIPI_LANE_SEL23_LANE2(v) (((v) << 0) & GENMASK(2, 0))
+#define OV8865_MIPI_LANE_SEL23_LANE3(v) (((v) << 4) & GENMASK(6, 4))
+
+/* ISP */
+
+#define OV8865_ISP_CTRL0_REG 0x5000
+#define OV8865_ISP_CTRL0_LENC_EN BIT(7)
+#define OV8865_ISP_CTRL0_WHITE_BALANCE_EN BIT(4)
+#define OV8865_ISP_CTRL0_DPC_BLACK_EN BIT(2)
+#define OV8865_ISP_CTRL0_DPC_WHITE_EN BIT(1)
+#define OV8865_ISP_CTRL1_REG 0x5001
+#define OV8865_ISP_CTRL1_BLC_EN BIT(0)
+#define OV8865_ISP_CTRL2_REG 0x5002
+#define OV8865_ISP_CTRL2_DEBUG BIT(3)
+#define OV8865_ISP_CTRL2_VARIOPIXEL_EN BIT(2)
+#define OV8865_ISP_CTRL2_VSYNC_LATCH_EN BIT(0)
+#define OV8865_ISP_CTRL3_REG 0x5003
+
+#define OV8865_ISP_GAIN_RED_H_REG 0x5018
+#define OV8865_ISP_GAIN_RED_H(v) (((v) & GENMASK(13, 6)) >> 6)
+#define OV8865_ISP_GAIN_RED_L_REG 0x5019
+#define OV8865_ISP_GAIN_RED_L(v) ((v) & GENMASK(5, 0))
+#define OV8865_ISP_GAIN_GREEN_H_REG 0x501a
+#define OV8865_ISP_GAIN_GREEN_H(v) (((v) & GENMASK(13, 6)) >> 6)
+#define OV8865_ISP_GAIN_GREEN_L_REG 0x501b
+#define OV8865_ISP_GAIN_GREEN_L(v) ((v) & GENMASK(5, 0))
+#define OV8865_ISP_GAIN_BLUE_H_REG 0x501c
+#define OV8865_ISP_GAIN_BLUE_H(v) (((v) & GENMASK(13, 6)) >> 6)
+#define OV8865_ISP_GAIN_BLUE_L_REG 0x501d
+#define OV8865_ISP_GAIN_BLUE_L(v) ((v) & GENMASK(5, 0))
+
+/* VarioPixel */
+
+#define OV8865_VAP_CTRL0_REG 0x5900
+#define OV8865_VAP_CTRL1_REG 0x5901
+#define OV8865_VAP_CTRL1_HSUB_COEF(v) ((((v) - 1) << 2) & \
+ GENMASK(3, 2))
+#define OV8865_VAP_CTRL1_VSUB_COEF(v) (((v) - 1) & GENMASK(1, 0))
+
+/* Pre-DSP */
+
+#define OV8865_PRE_CTRL0_REG 0x5e00
+#define OV8865_PRE_CTRL0_PATTERN_EN BIT(7)
+#define OV8865_PRE_CTRL0_ROLLING_BAR_EN BIT(6)
+#define OV8865_PRE_CTRL0_TRANSPARENT_MODE BIT(5)
+#define OV8865_PRE_CTRL0_SQUARES_BW_MODE BIT(4)
+#define OV8865_PRE_CTRL0_PATTERN_COLOR_BARS 0
+#define OV8865_PRE_CTRL0_PATTERN_RANDOM_DATA 1
+#define OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES 2
+#define OV8865_PRE_CTRL0_PATTERN_BLACK 3
+
+/* Macros */
+
+#define ov8865_subdev_sensor(s) \
+ container_of(s, struct ov8865_sensor, subdev)
+
+#define ov8865_ctrl_subdev(c) \
+ (&container_of(c->handler, struct ov8865_sensor, ctrls.handler)->subdev)
+
+/* Data structures */
+
+struct ov8865_register_value {
+ u16 address;
+ u8 value;
+ unsigned int delay_ms;
+};
+
+/*
+ * PLL1 Clock Tree:
+ *
+ * +-< EXTCLK
+ * |
+ * +-+ pll_pre_div_half (0x30a [0])
+ * |
+ * +-+ pll_pre_div (0x300 [2:0], special values:
+ * | 0: 1, 1: 1.5, 3: 2.5, 4: 3, 5: 4, 7: 8)
+ * +-+ pll_mul (0x301 [1:0], 0x302 [7:0])
+ * |
+ * +-+ m_div (0x303 [3:0])
+ * | |
+ * | +-> PHY_SCLK
+ * | |
+ * | +-+ mipi_div (0x304 [1:0], special values: 0: 4, 1: 5, 2: 6, 3: 8)
+ * | |
+ * | +-+ pclk_div (0x3020 [3])
+ * | |
+ * | +-> PCLK
+ * |
+ * +-+ sys_pre_div (0x305 [1:0], special values: 0: 3, 1: 4, 2: 5, 3: 6)
+ * |
+ * +-+ sys_div (0x306 [0])
+ * |
+ * +-+ sys_sel (0x3032 [7], 0: PLL1, 1: PLL2)
+ * |
+ * +-+ sclk_sel (0x3033 [1], 0: sys_sel, 1: PLL2 DAC_CLK)
+ * |
+ * +-+ sclk_pre_div (0x3106 [3:2], special values:
+ * | 0: 1, 1: 2, 2: 4, 3: 1)
+ * |
+ * +-+ sclk_div (0x3106 [7:4], special values: 0: 1)
+ * |
+ * +-> SCLK
+ */
+
+struct ov8865_pll1_config {
+ unsigned int pll_pre_div_half;
+ unsigned int pll_pre_div;
+ unsigned int pll_mul;
+ unsigned int m_div;
+ unsigned int mipi_div;
+ unsigned int pclk_div;
+ unsigned int sys_pre_div;
+ unsigned int sys_div;
+};
+
+/*
+ * PLL2 Clock Tree:
+ *
+ * +-< EXTCLK
+ * |
+ * +-+ pll_pre_div_half (0x312 [4])
+ * |
+ * +-+ pll_pre_div (0x30b [2:0], special values:
+ * | 0: 1, 1: 1.5, 3: 2.5, 4: 3, 5: 4, 7: 8)
+ * +-+ pll_mul (0x30c [1:0], 0x30d [7:0])
+ * |
+ * +-+ dac_div (0x312 [3:0])
+ * | |
+ * | +-> DAC_CLK
+ * |
+ * +-+ sys_pre_div (0x30f [3:0])
+ * |
+ * +-+ sys_div (0x30e [2:0], special values:
+ * | 0: 1, 1: 1.5, 3: 2.5, 4: 3, 5: 3.5, 6: 4, 7:5)
+ * |
+ * +-+ sys_sel (0x3032 [7], 0: PLL1, 1: PLL2)
+ * |
+ * +-+ sclk_sel (0x3033 [1], 0: sys_sel, 1: PLL2 DAC_CLK)
+ * |
+ * +-+ sclk_pre_div (0x3106 [3:2], special values:
+ * | 0: 1, 1: 2, 2: 4, 3: 1)
+ * |
+ * +-+ sclk_div (0x3106 [7:4], special values: 0: 1)
+ * |
+ * +-> SCLK
+ */
+
+struct ov8865_pll2_config {
+ unsigned int pll_pre_div_half;
+ unsigned int pll_pre_div;
+ unsigned int pll_mul;
+ unsigned int dac_div;
+ unsigned int sys_pre_div;
+ unsigned int sys_div;
+};
+
+struct ov8865_sclk_config {
+ unsigned int sys_sel;
+ unsigned int sclk_sel;
+ unsigned int sclk_pre_div;
+ unsigned int sclk_div;
+};
+
+/*
+ * General formulas for (array-centered) mode calculation:
+ * - photo_array_width = 3296
+ * - crop_start_x = (photo_array_width - output_size_x) / 2
+ * - crop_end_x = crop_start_x + offset_x + output_size_x - 1
+ *
+ * - photo_array_height = 2480
+ * - crop_start_y = (photo_array_height - output_size_y) / 2
+ * - crop_end_y = crop_start_y + offset_y + output_size_y - 1
+ */
+
+struct ov8865_mode {
+ unsigned int crop_start_x;
+ unsigned int offset_x;
+ unsigned int output_size_x;
+ unsigned int crop_end_x;
+ unsigned int hts;
+
+ unsigned int crop_start_y;
+ unsigned int offset_y;
+ unsigned int output_size_y;
+ unsigned int crop_end_y;
+ unsigned int vts;
+
+ /* With auto size, only output and total sizes need to be set. */
+ bool size_auto;
+ unsigned int size_auto_boundary_x;
+ unsigned int size_auto_boundary_y;
+
+ bool binning_x;
+ bool binning_y;
+ bool variopixel;
+ unsigned int variopixel_hsub_coef;
+ unsigned int variopixel_vsub_coef;
+
+ /* Bits for the format register, used for binning. */
+ bool sync_hbin;
+ bool horz_var2;
+
+ unsigned int inc_x_odd;
+ unsigned int inc_x_even;
+ unsigned int inc_y_odd;
+ unsigned int inc_y_even;
+
+ unsigned int vfifo_read_start;
+
+ unsigned int ablc_num;
+ unsigned int zline_num;
+
+ unsigned int blc_top_zero_line_start;
+ unsigned int blc_top_zero_line_num;
+ unsigned int blc_top_black_line_start;
+ unsigned int blc_top_black_line_num;
+
+ unsigned int blc_bottom_zero_line_start;
+ unsigned int blc_bottom_zero_line_num;
+ unsigned int blc_bottom_black_line_start;
+ unsigned int blc_bottom_black_line_num;
+
+ u8 blc_col_shift_mask;
+
+ unsigned int blc_anchor_left_start;
+ unsigned int blc_anchor_left_end;
+ unsigned int blc_anchor_right_start;
+ unsigned int blc_anchor_right_end;
+
+ struct v4l2_fract frame_interval;
+
+ const struct ov8865_pll1_config *pll1_config;
+ const struct ov8865_pll2_config *pll2_config;
+ const struct ov8865_sclk_config *sclk_config;
+
+ const struct ov8865_register_value *register_values;
+ unsigned int register_values_count;
+};
+
+struct ov8865_state {
+ const struct ov8865_mode *mode;
+ u32 mbus_code;
+
+ bool streaming;
+};
+
+struct ov8865_ctrls {
+ struct v4l2_ctrl *link_freq;
+ struct v4l2_ctrl *pixel_rate;
+
+ struct v4l2_ctrl_handler handler;
+};
+
+struct ov8865_sensor {
+ struct device *dev;
+ struct i2c_client *i2c_client;
+ struct gpio_desc *reset;
+ struct gpio_desc *powerdown;
+ struct regulator *avdd;
+ struct regulator *dvdd;
+ struct regulator *dovdd;
+ struct clk *extclk;
+
+ struct v4l2_fwnode_endpoint endpoint;
+ struct v4l2_subdev subdev;
+ struct media_pad pad;
+
+ struct mutex mutex;
+
+ struct ov8865_state state;
+ struct ov8865_ctrls ctrls;
+};
+
+/* Static definitions */
+
+/*
+ * EXTCLK = 24 MHz
+ * PHY_SCLK = 720 MHz
+ * MIPI_PCLK = 90 MHz
+ */
+static const struct ov8865_pll1_config ov8865_pll1_config_native = {
+ .pll_pre_div_half = 1,
+ .pll_pre_div = 0,
+ .pll_mul = 30,
+ .m_div = 1,
+ .mipi_div = 3,
+ .pclk_div = 1,
+ .sys_pre_div = 1,
+ .sys_div = 2,
+};
+
+/*
+ * EXTCLK = 24 MHz
+ * DAC_CLK = 360 MHz
+ * SCLK = 144 MHz
+ */
+
+static const struct ov8865_pll2_config ov8865_pll2_config_native = {
+ .pll_pre_div_half = 1,
+ .pll_pre_div = 0,
+ .pll_mul = 30,
+ .dac_div = 2,
+ .sys_pre_div = 5,
+ .sys_div = 0,
+};
+
+/*
+ * EXTCLK = 24 MHz
+ * DAC_CLK = 360 MHz
+ * SCLK = 80 MHz
+ */
+
+static const struct ov8865_pll2_config ov8865_pll2_config_binning = {
+ .pll_pre_div_half = 1,
+ .pll_pre_div = 0,
+ .pll_mul = 30,
+ .dac_div = 2,
+ .sys_pre_div = 10,
+ .sys_div = 0,
+};
+
+static const struct ov8865_sclk_config ov8865_sclk_config_native = {
+ .sys_sel = 1,
+ .sclk_sel = 0,
+ .sclk_pre_div = 0,
+ .sclk_div = 0,
+};
+
+static const struct ov8865_register_value ov8865_register_values_native[] = {
+ /* Sensor */
+
+ { 0x3700, 0x48 },
+ { 0x3701, 0x18 },
+ { 0x3702, 0x50 },
+ { 0x3703, 0x32 },
+ { 0x3704, 0x28 },
+ { 0x3706, 0x70 },
+ { 0x3707, 0x08 },
+ { 0x3708, 0x48 },
+ { 0x3709, 0x80 },
+ { 0x370a, 0x01 },
+ { 0x370b, 0x70 },
+ { 0x370c, 0x07 },
+ { 0x3718, 0x14 },
+ { 0x3712, 0x44 },
+ { 0x371e, 0x31 },
+ { 0x371f, 0x7f },
+ { 0x3720, 0x0a },
+ { 0x3721, 0x0a },
+ { 0x3724, 0x04 },
+ { 0x3725, 0x04 },
+ { 0x3726, 0x0c },
+ { 0x3728, 0x0a },
+ { 0x3729, 0x03 },
+ { 0x372a, 0x06 },
+ { 0x372b, 0xa6 },
+ { 0x372c, 0xa6 },
+ { 0x372d, 0xa6 },
+ { 0x372e, 0x0c },
+ { 0x372f, 0x20 },
+ { 0x3730, 0x02 },
+ { 0x3731, 0x0c },
+ { 0x3732, 0x28 },
+ { 0x3736, 0x30 },
+ { 0x373a, 0x04 },
+ { 0x373b, 0x18 },
+ { 0x373c, 0x14 },
+ { 0x373e, 0x06 },
+ { 0x375a, 0x0c },
+ { 0x375b, 0x26 },
+ { 0x375d, 0x04 },
+ { 0x375f, 0x28 },
+ { 0x3767, 0x1e },
+ { 0x3772, 0x46 },
+ { 0x3773, 0x04 },
+ { 0x3774, 0x2c },
+ { 0x3775, 0x13 },
+ { 0x3776, 0x10 },
+ { 0x37a0, 0x88 },
+ { 0x37a1, 0x7a },
+ { 0x37a2, 0x7a },
+ { 0x37a3, 0x02 },
+ { 0x37a5, 0x09 },
+ { 0x37a7, 0x88 },
+ { 0x37a8, 0xb0 },
+ { 0x37a9, 0xb0 },
+ { 0x37aa, 0x88 },
+ { 0x37ab, 0x5c },
+ { 0x37ac, 0x5c },
+ { 0x37ad, 0x55 },
+ { 0x37ae, 0x19 },
+ { 0x37af, 0x19 },
+ { 0x37b3, 0x84 },
+ { 0x37b4, 0x84 },
+ { 0x37b5, 0x66 },
+
+ /* PSRAM */
+
+ { OV8865_PSRAM_CTRL8_REG, 0x16 },
+
+ /* ADC Sync */
+
+ { 0x4500, 0x68 },
+};
+
+static const struct ov8865_register_value ov8865_register_values_binning[] = {
+ /* Sensor */
+
+ { 0x3700, 0x24 },
+ { 0x3701, 0x0c },
+ { 0x3702, 0x28 },
+ { 0x3703, 0x19 },
+ { 0x3704, 0x14 },
+ { 0x3706, 0x38 },
+ { 0x3707, 0x04 },
+ { 0x3708, 0x24 },
+ { 0x3709, 0x40 },
+ { 0x370a, 0x00 },
+ { 0x370b, 0xb8 },
+ { 0x370c, 0x04 },
+ { 0x3718, 0x12 },
+ { 0x3712, 0x42 },
+ { 0x371e, 0x19 },
+ { 0x371f, 0x40 },
+ { 0x3720, 0x05 },
+ { 0x3721, 0x05 },
+ { 0x3724, 0x02 },
+ { 0x3725, 0x02 },
+ { 0x3726, 0x06 },
+ { 0x3728, 0x05 },
+ { 0x3729, 0x02 },
+ { 0x372a, 0x03 },
+ { 0x372b, 0x53 },
+ { 0x372c, 0xa3 },
+ { 0x372d, 0x53 },
+ { 0x372e, 0x06 },
+ { 0x372f, 0x10 },
+ { 0x3730, 0x01 },
+ { 0x3731, 0x06 },
+ { 0x3732, 0x14 },
+ { 0x3736, 0x20 },
+ { 0x373a, 0x02 },
+ { 0x373b, 0x0c },
+ { 0x373c, 0x0a },
+ { 0x373e, 0x03 },
+ { 0x375a, 0x06 },
+ { 0x375b, 0x13 },
+ { 0x375d, 0x02 },
+ { 0x375f, 0x14 },
+ { 0x3767, 0x1c },
+ { 0x3772, 0x23 },
+ { 0x3773, 0x02 },
+ { 0x3774, 0x16 },
+ { 0x3775, 0x12 },
+ { 0x3776, 0x08 },
+ { 0x37a0, 0x44 },
+ { 0x37a1, 0x3d },
+ { 0x37a2, 0x3d },
+ { 0x37a3, 0x01 },
+ { 0x37a5, 0x08 },
+ { 0x37a7, 0x44 },
+ { 0x37a8, 0x58 },
+ { 0x37a9, 0x58 },
+ { 0x37aa, 0x44 },
+ { 0x37ab, 0x2e },
+ { 0x37ac, 0x2e },
+ { 0x37ad, 0x33 },
+ { 0x37ae, 0x0d },
+ { 0x37af, 0x0d },
+ { 0x37b3, 0x42 },
+ { 0x37b4, 0x42 },
+ { 0x37b5, 0x33 },
+
+ /* PSRAM */
+
+ { OV8865_PSRAM_CTRL8_REG, 0x0b },
+
+ /* ADC Sync */
+
+ { 0x4500, 0x40 },
+};
+
+static const struct ov8865_mode ov8865_modes[] = {
+ /* 3264x2448 */
+ {
+ /* Horizontal */
+ .output_size_x = 3264,
+ .hts = 1944,
+
+ /* Vertical */
+ .output_size_y = 2448,
+ .vts = 2470,
+
+ .size_auto = true,
+ .size_auto_boundary_x = 8,
+ .size_auto_boundary_y = 4,
+
+ /* Subsample increase */
+ .inc_x_odd = 1,
+ .inc_x_even = 1,
+ .inc_y_odd = 1,
+ .inc_y_even = 1,
+
+ /* VFIFO */
+ .vfifo_read_start = 16,
+
+ .ablc_num = 4,
+ .zline_num = 1,
+
+ /* Black Level */
+
+ .blc_top_zero_line_start = 0,
+ .blc_top_zero_line_num = 2,
+ .blc_top_black_line_start = 4,
+ .blc_top_black_line_num = 4,
+
+ .blc_bottom_zero_line_start = 2,
+ .blc_bottom_zero_line_num = 2,
+ .blc_bottom_black_line_start = 8,
+ .blc_bottom_black_line_num = 2,
+
+ .blc_anchor_left_start = 576,
+ .blc_anchor_left_end = 831,
+ .blc_anchor_right_start = 1984,
+ .blc_anchor_right_end = 2239,
+
+ /* Frame Interval */
+ .frame_interval = { 1, 30 },
+
+ /* PLL */
+ .pll1_config = &ov8865_pll1_config_native,
+ .pll2_config = &ov8865_pll2_config_native,
+ .sclk_config = &ov8865_sclk_config_native,
+
+ /* Registers */
+ .register_values = ov8865_register_values_native,
+ .register_values_count =
+ ARRAY_SIZE(ov8865_register_values_native),
+ },
+ /* 3264x1836 */
+ {
+ /* Horizontal */
+ .output_size_x = 3264,
+ .hts = 2582,
+
+ /* Vertical */
+ .output_size_y = 1836,
+ .vts = 2002,
+
+ .size_auto = true,
+ .size_auto_boundary_x = 8,
+ .size_auto_boundary_y = 4,
+
+ /* Subsample increase */
+ .inc_x_odd = 1,
+ .inc_x_even = 1,
+ .inc_y_odd = 1,
+ .inc_y_even = 1,
+
+ /* VFIFO */
+ .vfifo_read_start = 16,
+
+ .ablc_num = 4,
+ .zline_num = 1,
+
+ /* Black Level */
+
+ .blc_top_zero_line_start = 0,
+ .blc_top_zero_line_num = 2,
+ .blc_top_black_line_start = 4,
+ .blc_top_black_line_num = 4,
+
+ .blc_bottom_zero_line_start = 2,
+ .blc_bottom_zero_line_num = 2,
+ .blc_bottom_black_line_start = 8,
+ .blc_bottom_black_line_num = 2,
+
+ .blc_anchor_left_start = 576,
+ .blc_anchor_left_end = 831,
+ .blc_anchor_right_start = 1984,
+ .blc_anchor_right_end = 2239,
+
+ /* Frame Interval */
+ .frame_interval = { 1, 30 },
+
+ /* PLL */
+ .pll1_config = &ov8865_pll1_config_native,
+ .pll2_config = &ov8865_pll2_config_native,
+ .sclk_config = &ov8865_sclk_config_native,
+
+ /* Registers */
+ .register_values = ov8865_register_values_native,
+ .register_values_count =
+ ARRAY_SIZE(ov8865_register_values_native),
+ },
+ /* 1632x1224 */
+ {
+ /* Horizontal */
+ .output_size_x = 1632,
+ .hts = 1923,
+
+ /* Vertical */
+ .output_size_y = 1224,
+ .vts = 1248,
+
+ .size_auto = true,
+ .size_auto_boundary_x = 8,
+ .size_auto_boundary_y = 8,
+
+ /* Subsample increase */
+ .inc_x_odd = 3,
+ .inc_x_even = 1,
+ .inc_y_odd = 3,
+ .inc_y_even = 1,
+
+ /* Binning */
+ .binning_y = true,
+ .sync_hbin = true,
+
+ /* VFIFO */
+ .vfifo_read_start = 116,
+
+ .ablc_num = 8,
+ .zline_num = 2,
+
+ /* Black Level */
+
+ .blc_top_zero_line_start = 0,
+ .blc_top_zero_line_num = 2,
+ .blc_top_black_line_start = 4,
+ .blc_top_black_line_num = 4,
+
+ .blc_bottom_zero_line_start = 2,
+ .blc_bottom_zero_line_num = 2,
+ .blc_bottom_black_line_start = 8,
+ .blc_bottom_black_line_num = 2,
+
+ .blc_anchor_left_start = 288,
+ .blc_anchor_left_end = 415,
+ .blc_anchor_right_start = 992,
+ .blc_anchor_right_end = 1119,
+
+ /* Frame Interval */
+ .frame_interval = { 1, 30 },
+
+ /* PLL */
+ .pll1_config = &ov8865_pll1_config_native,
+ .pll2_config = &ov8865_pll2_config_binning,
+ .sclk_config = &ov8865_sclk_config_native,
+
+ /* Registers */
+ .register_values = ov8865_register_values_binning,
+ .register_values_count =
+ ARRAY_SIZE(ov8865_register_values_binning),
+ },
+ /* 800x600 (SVGA) */
+ {
+ /* Horizontal */
+ .output_size_x = 800,
+ .hts = 1250,
+
+ /* Vertical */
+ .output_size_y = 600,
+ .vts = 640,
+
+ .size_auto = true,
+ .size_auto_boundary_x = 8,
+ .size_auto_boundary_y = 8,
+
+ /* Subsample increase */
+ .inc_x_odd = 3,
+ .inc_x_even = 1,
+ .inc_y_odd = 5,
+ .inc_y_even = 3,
+
+ /* Binning */
+ .binning_y = true,
+ .variopixel = true,
+ .variopixel_hsub_coef = 2,
+ .variopixel_vsub_coef = 1,
+ .sync_hbin = true,
+ .horz_var2 = true,
+
+ /* VFIFO */
+ .vfifo_read_start = 80,
+
+ .ablc_num = 8,
+ .zline_num = 2,
+
+ /* Black Level */
+
+ .blc_top_zero_line_start = 0,
+ .blc_top_zero_line_num = 2,
+ .blc_top_black_line_start = 2,
+ .blc_top_black_line_num = 2,
+
+ .blc_bottom_zero_line_start = 0,
+ .blc_bottom_zero_line_num = 0,
+ .blc_bottom_black_line_start = 4,
+ .blc_bottom_black_line_num = 2,
+
+ .blc_col_shift_mask = OV8865_BLC_CTRL1_COL_SHIFT_128,
+
+ .blc_anchor_left_start = 288,
+ .blc_anchor_left_end = 415,
+ .blc_anchor_right_start = 992,
+ .blc_anchor_right_end = 1119,
+
+ /* Frame Interval */
+ .frame_interval = { 1, 90 },
+
+ /* PLL */
+ .pll1_config = &ov8865_pll1_config_native,
+ .pll2_config = &ov8865_pll2_config_binning,
+ .sclk_config = &ov8865_sclk_config_native,
+
+ /* Registers */
+ .register_values = ov8865_register_values_binning,
+ .register_values_count =
+ ARRAY_SIZE(ov8865_register_values_binning),
+ },
+};
+
+static const u32 ov8865_mbus_codes[] = {
+ MEDIA_BUS_FMT_SBGGR10_1X10,
+};
+
+static const struct ov8865_register_value ov8865_init_sequence[] = {
+ /* Analog */
+
+ { 0x3604, 0x04 },
+ { 0x3602, 0x30 },
+ { 0x3605, 0x00 },
+ { 0x3607, 0x20 },
+ { 0x3608, 0x11 },
+ { 0x3609, 0x68 },
+ { 0x360a, 0x40 },
+ { 0x360c, 0xdd },
+ { 0x360e, 0x0c },
+ { 0x3610, 0x07 },
+ { 0x3612, 0x86 },
+ { 0x3613, 0x58 },
+ { 0x3614, 0x28 },
+ { 0x3617, 0x40 },
+ { 0x3618, 0x5a },
+ { 0x3619, 0x9b },
+ { 0x361c, 0x00 },
+ { 0x361d, 0x60 },
+ { 0x3631, 0x60 },
+ { 0x3633, 0x10 },
+ { 0x3634, 0x10 },
+ { 0x3635, 0x10 },
+ { 0x3636, 0x10 },
+ { 0x3638, 0xff },
+ { 0x3641, 0x55 },
+ { 0x3646, 0x86 },
+ { 0x3647, 0x27 },
+ { 0x364a, 0x1b },
+
+ /* Sensor */
+
+ { 0x3700, 0x24 },
+ { 0x3701, 0x0c },
+ { 0x3702, 0x28 },
+ { 0x3703, 0x19 },
+ { 0x3704, 0x14 },
+ { 0x3705, 0x00 },
+ { 0x3706, 0x38 },
+ { 0x3707, 0x04 },
+ { 0x3708, 0x24 },
+ { 0x3709, 0x40 },
+ { 0x370a, 0x00 },
+ { 0x370b, 0xb8 },
+ { 0x370c, 0x04 },
+ { 0x3718, 0x12 },
+ { 0x3719, 0x31 },
+ { 0x3712, 0x42 },
+ { 0x3714, 0x12 },
+ { 0x371e, 0x19 },
+ { 0x371f, 0x40 },
+ { 0x3720, 0x05 },
+ { 0x3721, 0x05 },
+ { 0x3724, 0x02 },
+ { 0x3725, 0x02 },
+ { 0x3726, 0x06 },
+ { 0x3728, 0x05 },
+ { 0x3729, 0x02 },
+ { 0x372a, 0x03 },
+ { 0x372b, 0x53 },
+ { 0x372c, 0xa3 },
+ { 0x372d, 0x53 },
+ { 0x372e, 0x06 },
+ { 0x372f, 0x10 },
+ { 0x3730, 0x01 },
+ { 0x3731, 0x06 },
+ { 0x3732, 0x14 },
+ { 0x3733, 0x10 },
+ { 0x3734, 0x40 },
+ { 0x3736, 0x20 },
+ { 0x373a, 0x02 },
+ { 0x373b, 0x0c },
+ { 0x373c, 0x0a },
+ { 0x373e, 0x03 },
+ { 0x3755, 0x40 },
+ { 0x3758, 0x00 },
+ { 0x3759, 0x4c },
+ { 0x375a, 0x06 },
+ { 0x375b, 0x13 },
+ { 0x375c, 0x40 },
+ { 0x375d, 0x02 },
+ { 0x375e, 0x00 },
+ { 0x375f, 0x14 },
+ { 0x3767, 0x1c },
+ { 0x3768, 0x04 },
+ { 0x3769, 0x20 },
+ { 0x376c, 0xc0 },
+ { 0x376d, 0xc0 },
+ { 0x376a, 0x08 },
+ { 0x3761, 0x00 },
+ { 0x3762, 0x00 },
+ { 0x3763, 0x00 },
+ { 0x3766, 0xff },
+ { 0x376b, 0x42 },
+ { 0x3772, 0x23 },
+ { 0x3773, 0x02 },
+ { 0x3774, 0x16 },
+ { 0x3775, 0x12 },
+ { 0x3776, 0x08 },
+ { 0x37a0, 0x44 },
+ { 0x37a1, 0x3d },
+ { 0x37a2, 0x3d },
+ { 0x37a3, 0x01 },
+ { 0x37a4, 0x00 },
+ { 0x37a5, 0x08 },
+ { 0x37a6, 0x00 },
+ { 0x37a7, 0x44 },
+ { 0x37a8, 0x58 },
+ { 0x37a9, 0x58 },
+ { 0x3760, 0x00 },
+ { 0x376f, 0x01 },
+ { 0x37aa, 0x44 },
+ { 0x37ab, 0x2e },
+ { 0x37ac, 0x2e },
+ { 0x37ad, 0x33 },
+ { 0x37ae, 0x0d },
+ { 0x37af, 0x0d },
+ { 0x37b0, 0x00 },
+ { 0x37b1, 0x00 },
+ { 0x37b2, 0x00 },
+ { 0x37b3, 0x42 },
+ { 0x37b4, 0x42 },
+ { 0x37b5, 0x33 },
+ { 0x37b6, 0x00 },
+ { 0x37b7, 0x00 },
+ { 0x37b8, 0x00 },
+ { 0x37b9, 0xff },
+
+ /* ADC Sync */
+
+ { 0x4503, 0x10 },
+};
+
+static const s64 ov8865_link_freq_menu[] = {
+ 360000000,
+};
+
+static const char *const ov8865_test_pattern_menu[] = {
+ "Disabled",
+ "Random data",
+ "Color bars",
+ "Color bars with rolling bar",
+ "Color squares",
+ "Color squares with rolling bar"
+};
+
+static const u8 ov8865_test_pattern_bits[] = {
+ 0,
+ OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_PATTERN_RANDOM_DATA,
+ OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_PATTERN_COLOR_BARS,
+ OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_ROLLING_BAR_EN |
+ OV8865_PRE_CTRL0_PATTERN_COLOR_BARS,
+ OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES,
+ OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_ROLLING_BAR_EN |
+ OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES,
+};
+
+/* Input/Output */
+
+static int ov8865_read(struct ov8865_sensor *sensor, u16 address, u8 *value)
+{
+ unsigned char data[2] = { address >> 8, address & 0xff };
+ struct i2c_client *client = sensor->i2c_client;
+ int ret;
+
+ ret = i2c_master_send(client, data, sizeof(data));
+ if (ret < 0) {
+ dev_dbg(&client->dev, "i2c send error at address %#04x\n",
+ address);
+ return ret;
+ }
+
+ ret = i2c_master_recv(client, value, 1);
+ if (ret < 0) {
+ dev_dbg(&client->dev, "i2c recv error at address %#04x\n",
+ address);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ov8865_write(struct ov8865_sensor *sensor, u16 address, u8 value)
+{
+ unsigned char data[3] = { address >> 8, address & 0xff, value };
+ struct i2c_client *client = sensor->i2c_client;
+ int ret;
+
+ ret = i2c_master_send(client, data, sizeof(data));
+ if (ret < 0) {
+ dev_dbg(&client->dev, "i2c send error at address %#04x\n",
+ address);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ov8865_write_sequence(struct ov8865_sensor *sensor,
+ const struct ov8865_register_value *sequence,
+ unsigned int sequence_count)
+{
+ unsigned int i;
+ int ret = 0;
+
+ for (i = 0; i < sequence_count; i++) {
+ ret = ov8865_write(sensor, sequence[i].address,
+ sequence[i].value);
+ if (ret)
+ break;
+
+ if (sequence[i].delay_ms)
+ msleep(sequence[i].delay_ms);
+ }
+
+ return ret;
+}
+
+static int ov8865_update_bits(struct ov8865_sensor *sensor, u16 address,
+ u8 mask, u8 bits)
+{
+ u8 value = 0;
+ int ret;
+
+ ret = ov8865_read(sensor, address, &value);
+ if (ret)
+ return ret;
+
+ value &= ~mask;
+ value |= bits;
+
+ return ov8865_write(sensor, address, value);
+}
+
+/* Sensor */
+
+static int ov8865_sw_reset(struct ov8865_sensor *sensor)
+{
+ return ov8865_write(sensor, OV8865_SW_RESET_REG, OV8865_SW_RESET_RESET);
+}
+
+static int ov8865_sw_standby(struct ov8865_sensor *sensor, int standby)
+{
+ u8 value = 0;
+
+ if (!standby)
+ value = OV8865_SW_STANDBY_STREAM_ON;
+
+ return ov8865_write(sensor, OV8865_SW_STANDBY_REG, value);
+}
+
+static int ov8865_chip_id_check(struct ov8865_sensor *sensor)
+{
+ u16 regs[] = { OV8865_CHIP_ID_HH_REG, OV8865_CHIP_ID_H_REG,
+ OV8865_CHIP_ID_L_REG };
+ u8 values[] = { OV8865_CHIP_ID_HH_VALUE, OV8865_CHIP_ID_H_VALUE,
+ OV8865_CHIP_ID_L_VALUE };
+ unsigned int i;
+ u8 value;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(regs); i++) {
+ ret = ov8865_read(sensor, regs[i], &value);
+ if (ret < 0)
+ return ret;
+
+ if (value != values[i]) {
+ dev_err(sensor->dev,
+ "chip id value mismatch: %#x instead of %#x\n",
+ value, values[i]);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int ov8865_charge_pump_configure(struct ov8865_sensor *sensor)
+{
+ return ov8865_write(sensor, OV8865_PUMP_CLK_DIV_REG,
+ OV8865_PUMP_CLK_DIV_PUMP_P(1));
+}
+
+static int ov8865_mipi_configure(struct ov8865_sensor *sensor)
+{
+ struct v4l2_fwnode_bus_mipi_csi2 *bus_mipi_csi2 =
+ &sensor->endpoint.bus.mipi_csi2;
+ unsigned int lanes_count = bus_mipi_csi2->num_data_lanes;
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_MIPI_SC_CTRL0_REG,
+ OV8865_MIPI_SC_CTRL0_LANES(lanes_count) |
+ OV8865_MIPI_SC_CTRL0_MIPI_EN |
+ OV8865_MIPI_SC_CTRL0_UNKNOWN);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_MIPI_SC_CTRL2_REG,
+ OV8865_MIPI_SC_CTRL2_PD_MIPI_RST_SYNC);
+ if (ret)
+ return ret;
+
+ if (lanes_count >= 2) {
+ ret = ov8865_write(sensor, OV8865_MIPI_LANE_SEL01_REG,
+ OV8865_MIPI_LANE_SEL01_LANE0(0) |
+ OV8865_MIPI_LANE_SEL01_LANE1(1));
+ if (ret)
+ return ret;
+ }
+
+ if (lanes_count >= 4) {
+ ret = ov8865_write(sensor, OV8865_MIPI_LANE_SEL23_REG,
+ OV8865_MIPI_LANE_SEL23_LANE2(2) |
+ OV8865_MIPI_LANE_SEL23_LANE3(3));
+ if (ret)
+ return ret;
+ }
+
+ ret = ov8865_update_bits(sensor, OV8865_CLK_SEL1_REG,
+ OV8865_CLK_SEL1_MIPI_EOF,
+ OV8865_CLK_SEL1_MIPI_EOF);
+ if (ret)
+ return ret;
+
+ /*
+ * This value might need to change depending on PCLK rate,
+ * but it's unclear how. This value seems to generally work
+ * while the default value was found to cause transmission errors.
+ */
+ return ov8865_write(sensor, OV8865_MIPI_PCLK_PERIOD_REG, 0x16);
+}
+
+static int ov8865_black_level_configure(struct ov8865_sensor *sensor)
+{
+ int ret;
+
+ /* Trigger BLC on relevant events and enable filter. */
+ ret = ov8865_write(sensor, OV8865_BLC_CTRL0_REG,
+ OV8865_BLC_CTRL0_TRIG_RANGE_EN |
+ OV8865_BLC_CTRL0_TRIG_FORMAT_EN |
+ OV8865_BLC_CTRL0_TRIG_GAIN_EN |
+ OV8865_BLC_CTRL0_TRIG_EXPOSURE_EN |
+ OV8865_BLC_CTRL0_FILTER_EN);
+ if (ret)
+ return ret;
+
+ /* Lower BLC offset trigger threshold. */
+ ret = ov8865_write(sensor, OV8865_BLC_CTRLD_REG,
+ OV8865_BLC_CTRLD_OFFSET_TRIGGER(16));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_CTRL1F_REG, 0);
+ if (ret)
+ return ret;
+
+ /* Increase BLC offset maximum limit. */
+ return ov8865_write(sensor, OV8865_BLC_OFFSET_LIMIT_REG,
+ OV8865_BLC_OFFSET_LIMIT(63));
+}
+
+static int ov8865_isp_configure(struct ov8865_sensor *sensor)
+{
+ int ret;
+
+ /* Disable lens correction. */
+ ret = ov8865_write(sensor, OV8865_ISP_CTRL0_REG,
+ OV8865_ISP_CTRL0_WHITE_BALANCE_EN |
+ OV8865_ISP_CTRL0_DPC_BLACK_EN |
+ OV8865_ISP_CTRL0_DPC_WHITE_EN);
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_ISP_CTRL1_REG,
+ OV8865_ISP_CTRL1_BLC_EN);
+}
+
+static unsigned long ov8865_mode_pll1_rate(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode)
+{
+ const struct ov8865_pll1_config *config = mode->pll1_config;
+ unsigned long extclk_rate;
+ unsigned long pll1_rate;
+
+ extclk_rate = clk_get_rate(sensor->extclk);
+ pll1_rate = extclk_rate * config->pll_mul / config->pll_pre_div_half;
+
+ switch (config->pll_pre_div) {
+ case 0:
+ break;
+ case 1:
+ pll1_rate *= 3;
+ pll1_rate /= 2;
+ break;
+ case 3:
+ pll1_rate *= 5;
+ pll1_rate /= 2;
+ break;
+ case 4:
+ pll1_rate /= 3;
+ break;
+ case 5:
+ pll1_rate /= 4;
+ break;
+ case 7:
+ pll1_rate /= 8;
+ break;
+ default:
+ pll1_rate /= config->pll_pre_div;
+ break;
+ }
+
+ return pll1_rate;
+}
+
+static int ov8865_mode_pll1_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode,
+ u32 mbus_code)
+{
+ const struct ov8865_pll1_config *config = mode->pll1_config;
+ u8 value;
+ int ret;
+
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ value = OV8865_MIPI_BIT_SEL(10);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = ov8865_write(sensor, OV8865_MIPI_BIT_SEL_REG, value);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRLA_REG,
+ OV8865_PLL_CTRLA_PRE_DIV_HALF(config->pll_pre_div_half));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL0_REG,
+ OV8865_PLL_CTRL0_PRE_DIV(config->pll_pre_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL1_REG,
+ OV8865_PLL_CTRL1_MUL_H(config->pll_mul));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL2_REG,
+ OV8865_PLL_CTRL2_MUL_L(config->pll_mul));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL3_REG,
+ OV8865_PLL_CTRL3_M_DIV(config->m_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL4_REG,
+ OV8865_PLL_CTRL4_MIPI_DIV(config->mipi_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_update_bits(sensor, OV8865_PCLK_SEL_REG,
+ OV8865_PCLK_SEL_PCLK_DIV_MASK,
+ OV8865_PCLK_SEL_PCLK_DIV(config->pclk_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL5_REG,
+ OV8865_PLL_CTRL5_SYS_PRE_DIV(config->sys_pre_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL6_REG,
+ OV8865_PLL_CTRL6_SYS_DIV(config->sys_div));
+ if (ret)
+ return ret;
+
+ return ov8865_update_bits(sensor, OV8865_PLL_CTRL1E_REG,
+ OV8865_PLL_CTRL1E_PLL1_NO_LAT,
+ OV8865_PLL_CTRL1E_PLL1_NO_LAT);
+}
+
+static int ov8865_mode_pll2_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode)
+{
+ const struct ov8865_pll2_config *config = mode->pll2_config;
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL12_REG,
+ OV8865_PLL_CTRL12_PRE_DIV_HALF(config->pll_pre_div_half) |
+ OV8865_PLL_CTRL12_DAC_DIV(config->dac_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRLB_REG,
+ OV8865_PLL_CTRLB_PRE_DIV(config->pll_pre_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRLC_REG,
+ OV8865_PLL_CTRLC_MUL_H(config->pll_mul));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRLD_REG,
+ OV8865_PLL_CTRLD_MUL_L(config->pll_mul));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRLF_REG,
+ OV8865_PLL_CTRLF_SYS_PRE_DIV(config->sys_pre_div));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_PLL_CTRLE_REG,
+ OV8865_PLL_CTRLE_SYS_DIV(config->sys_div));
+}
+
+static int ov8865_mode_sclk_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode)
+{
+ const struct ov8865_sclk_config *config = mode->sclk_config;
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_CLK_SEL0_REG,
+ OV8865_CLK_SEL0_PLL1_SYS_SEL(config->sys_sel));
+ if (ret)
+ return ret;
+
+ ret = ov8865_update_bits(sensor, OV8865_CLK_SEL1_REG,
+ OV8865_CLK_SEL1_PLL_SCLK_SEL_MASK,
+ OV8865_CLK_SEL1_PLL_SCLK_SEL(config->sclk_sel));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_SCLK_CTRL_REG,
+ OV8865_SCLK_CTRL_UNKNOWN |
+ OV8865_SCLK_CTRL_SCLK_DIV(config->sclk_div) |
+ OV8865_SCLK_CTRL_SCLK_PRE_DIV(config->sclk_pre_div));
+}
+
+static int ov8865_mode_binning_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode)
+{
+ unsigned int variopixel_hsub_coef, variopixel_vsub_coef;
+ u8 value;
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_FORMAT1_REG, 0);
+ if (ret)
+ return ret;
+
+ value = OV8865_FORMAT2_HSYNC_EN;
+
+ if (mode->binning_x)
+ value |= OV8865_FORMAT2_FST_HBIN_EN;
+
+ if (mode->binning_y)
+ value |= OV8865_FORMAT2_FST_VBIN_EN;
+
+ if (mode->sync_hbin)
+ value |= OV8865_FORMAT2_SYNC_HBIN_EN;
+
+ if (mode->horz_var2)
+ value |= OV8865_FORMAT2_ISP_HORZ_VAR2_EN;
+
+ ret = ov8865_write(sensor, OV8865_FORMAT2_REG, value);
+ if (ret)
+ return ret;
+
+ ret = ov8865_update_bits(sensor, OV8865_ISP_CTRL2_REG,
+ OV8865_ISP_CTRL2_VARIOPIXEL_EN,
+ mode->variopixel ?
+ OV8865_ISP_CTRL2_VARIOPIXEL_EN : 0);
+ if (ret)
+ return ret;
+
+ if (mode->variopixel) {
+ /* VarioPixel coefs needs to be > 1. */
+ variopixel_hsub_coef = mode->variopixel_hsub_coef;
+ variopixel_vsub_coef = mode->variopixel_vsub_coef;
+ } else {
+ variopixel_hsub_coef = 1;
+ variopixel_vsub_coef = 1;
+ }
+
+ ret = ov8865_write(sensor, OV8865_VAP_CTRL1_REG,
+ OV8865_VAP_CTRL1_HSUB_COEF(variopixel_hsub_coef) |
+ OV8865_VAP_CTRL1_VSUB_COEF(variopixel_vsub_coef));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_INC_X_ODD_REG,
+ OV8865_INC_X_ODD(mode->inc_x_odd));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_INC_X_EVEN_REG,
+ OV8865_INC_X_EVEN(mode->inc_x_even));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_INC_Y_ODD_REG,
+ OV8865_INC_Y_ODD(mode->inc_y_odd));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_INC_Y_EVEN_REG,
+ OV8865_INC_Y_EVEN(mode->inc_y_even));
+}
+
+static int ov8865_mode_black_level_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode)
+{
+ int ret;
+
+ /* Note that a zero value for blc_col_shift_mask is the default 256. */
+ ret = ov8865_write(sensor, OV8865_BLC_CTRL1_REG,
+ mode->blc_col_shift_mask |
+ OV8865_BLC_CTRL1_OFFSET_LIMIT_EN);
+ if (ret)
+ return ret;
+
+ /* BLC top zero line */
+
+ ret = ov8865_write(sensor, OV8865_BLC_TOP_ZLINE_START_REG,
+ OV8865_BLC_TOP_ZLINE_START(mode->blc_top_zero_line_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_TOP_ZLINE_NUM_REG,
+ OV8865_BLC_TOP_ZLINE_NUM(mode->blc_top_zero_line_num));
+ if (ret)
+ return ret;
+
+ /* BLC top black line */
+
+ ret = ov8865_write(sensor, OV8865_BLC_TOP_BLKLINE_START_REG,
+ OV8865_BLC_TOP_BLKLINE_START(mode->blc_top_black_line_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_TOP_BLKLINE_NUM_REG,
+ OV8865_BLC_TOP_BLKLINE_NUM(mode->blc_top_black_line_num));
+ if (ret)
+ return ret;
+
+ /* BLC bottom zero line */
+
+ ret = ov8865_write(sensor, OV8865_BLC_BOT_ZLINE_START_REG,
+ OV8865_BLC_BOT_ZLINE_START(mode->blc_bottom_zero_line_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_BOT_ZLINE_NUM_REG,
+ OV8865_BLC_BOT_ZLINE_NUM(mode->blc_bottom_zero_line_num));
+ if (ret)
+ return ret;
+
+ /* BLC bottom black line */
+
+ ret = ov8865_write(sensor, OV8865_BLC_BOT_BLKLINE_START_REG,
+ OV8865_BLC_BOT_BLKLINE_START(mode->blc_bottom_black_line_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_BOT_BLKLINE_NUM_REG,
+ OV8865_BLC_BOT_BLKLINE_NUM(mode->blc_bottom_black_line_num));
+ if (ret)
+ return ret;
+
+ /* BLC anchor */
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_LEFT_START_H_REG,
+ OV8865_BLC_ANCHOR_LEFT_START_H(mode->blc_anchor_left_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_LEFT_START_L_REG,
+ OV8865_BLC_ANCHOR_LEFT_START_L(mode->blc_anchor_left_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_LEFT_END_H_REG,
+ OV8865_BLC_ANCHOR_LEFT_END_H(mode->blc_anchor_left_end));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_LEFT_END_L_REG,
+ OV8865_BLC_ANCHOR_LEFT_END_L(mode->blc_anchor_left_end));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_RIGHT_START_H_REG,
+ OV8865_BLC_ANCHOR_RIGHT_START_H(mode->blc_anchor_right_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_RIGHT_START_L_REG,
+ OV8865_BLC_ANCHOR_RIGHT_START_L(mode->blc_anchor_right_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_RIGHT_END_H_REG,
+ OV8865_BLC_ANCHOR_RIGHT_END_H(mode->blc_anchor_right_end));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_BLC_ANCHOR_RIGHT_END_L_REG,
+ OV8865_BLC_ANCHOR_RIGHT_END_L(mode->blc_anchor_right_end));
+}
+
+static int ov8865_mode_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode, u32 mbus_code)
+{
+ int ret;
+
+ /* Output Size X */
+
+ ret = ov8865_write(sensor, OV8865_OUTPUT_SIZE_X_H_REG,
+ OV8865_OUTPUT_SIZE_X_H(mode->output_size_x));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_OUTPUT_SIZE_X_L_REG,
+ OV8865_OUTPUT_SIZE_X_L(mode->output_size_x));
+ if (ret)
+ return ret;
+
+ /* Horizontal Total Size */
+
+ ret = ov8865_write(sensor, OV8865_HTS_H_REG, OV8865_HTS_H(mode->hts));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_HTS_L_REG, OV8865_HTS_L(mode->hts));
+ if (ret)
+ return ret;
+
+ /* Output Size Y */
+
+ ret = ov8865_write(sensor, OV8865_OUTPUT_SIZE_Y_H_REG,
+ OV8865_OUTPUT_SIZE_Y_H(mode->output_size_y));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_OUTPUT_SIZE_Y_L_REG,
+ OV8865_OUTPUT_SIZE_Y_L(mode->output_size_y));
+ if (ret)
+ return ret;
+
+ /* Vertical Total Size */
+
+ ret = ov8865_write(sensor, OV8865_VTS_H_REG, OV8865_VTS_H(mode->vts));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_VTS_L_REG, OV8865_VTS_L(mode->vts));
+ if (ret)
+ return ret;
+
+ if (mode->size_auto) {
+ /* Auto Size */
+
+ ret = ov8865_write(sensor, OV8865_AUTO_SIZE_CTRL_REG,
+ OV8865_AUTO_SIZE_CTRL_OFFSET_Y_REG |
+ OV8865_AUTO_SIZE_CTRL_OFFSET_X_REG |
+ OV8865_AUTO_SIZE_CTRL_CROP_END_Y_REG |
+ OV8865_AUTO_SIZE_CTRL_CROP_END_X_REG |
+ OV8865_AUTO_SIZE_CTRL_CROP_START_Y_REG |
+ OV8865_AUTO_SIZE_CTRL_CROP_START_X_REG);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_AUTO_SIZE_BOUNDARIES_REG,
+ OV8865_AUTO_SIZE_BOUNDARIES_Y(mode->size_auto_boundary_y) |
+ OV8865_AUTO_SIZE_BOUNDARIES_X(mode->size_auto_boundary_x));
+ if (ret)
+ return ret;
+ } else {
+ /* Crop Start X */
+
+ ret = ov8865_write(sensor, OV8865_CROP_START_X_H_REG,
+ OV8865_CROP_START_X_H(mode->crop_start_x));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_CROP_START_X_L_REG,
+ OV8865_CROP_START_X_L(mode->crop_start_x));
+ if (ret)
+ return ret;
+
+ /* Offset X */
+
+ ret = ov8865_write(sensor, OV8865_OFFSET_X_H_REG,
+ OV8865_OFFSET_X_H(mode->offset_x));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_OFFSET_X_L_REG,
+ OV8865_OFFSET_X_L(mode->offset_x));
+ if (ret)
+ return ret;
+
+ /* Crop End X */
+
+ ret = ov8865_write(sensor, OV8865_CROP_END_X_H_REG,
+ OV8865_CROP_END_X_H(mode->crop_end_x));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_CROP_END_X_L_REG,
+ OV8865_CROP_END_X_L(mode->crop_end_x));
+ if (ret)
+ return ret;
+
+ /* Crop Start Y */
+
+ ret = ov8865_write(sensor, OV8865_CROP_START_Y_H_REG,
+ OV8865_CROP_START_Y_H(mode->crop_start_y));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_CROP_START_Y_L_REG,
+ OV8865_CROP_START_Y_L(mode->crop_start_y));
+ if (ret)
+ return ret;
+
+ /* Offset Y */
+
+ ret = ov8865_write(sensor, OV8865_OFFSET_Y_H_REG,
+ OV8865_OFFSET_Y_H(mode->offset_y));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_OFFSET_Y_L_REG,
+ OV8865_OFFSET_Y_L(mode->offset_y));
+ if (ret)
+ return ret;
+
+ /* Crop End Y */
+
+ ret = ov8865_write(sensor, OV8865_CROP_END_Y_H_REG,
+ OV8865_CROP_END_Y_H(mode->crop_end_y));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_CROP_END_Y_L_REG,
+ OV8865_CROP_END_Y_L(mode->crop_end_y));
+ if (ret)
+ return ret;
+ }
+
+ /* VFIFO */
+
+ ret = ov8865_write(sensor, OV8865_VFIFO_READ_START_H_REG,
+ OV8865_VFIFO_READ_START_H(mode->vfifo_read_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_VFIFO_READ_START_L_REG,
+ OV8865_VFIFO_READ_START_L(mode->vfifo_read_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_ABLC_NUM_REG,
+ OV8865_ABLC_NUM(mode->ablc_num));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_ZLINE_NUM_REG,
+ OV8865_ZLINE_NUM(mode->zline_num));
+ if (ret)
+ return ret;
+
+ /* Binning */
+
+ ret = ov8865_mode_binning_configure(sensor, mode);
+ if (ret)
+ return ret;
+
+ /* Black Level */
+
+ ret = ov8865_mode_black_level_configure(sensor, mode);
+ if (ret)
+ return ret;
+
+ /* PLLs */
+
+ ret = ov8865_mode_pll1_configure(sensor, mode, mbus_code);
+ if (ret)
+ return ret;
+
+ ret = ov8865_mode_pll2_configure(sensor, mode);
+ if (ret)
+ return ret;
+
+ ret = ov8865_mode_sclk_configure(sensor, mode);
+ if (ret)
+ return ret;
+
+ /* Extra registers */
+
+ if (mode->register_values) {
+ ret = ov8865_write_sequence(sensor, mode->register_values,
+ mode->register_values_count);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static unsigned long ov8865_mode_mipi_clk_rate(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode)
+{
+ const struct ov8865_pll1_config *config = mode->pll1_config;
+ unsigned long pll1_rate;
+
+ pll1_rate = ov8865_mode_pll1_rate(sensor, mode);
+
+ return pll1_rate / config->m_div / 2;
+}
+
+/* Exposure */
+
+static int ov8865_exposure_configure(struct ov8865_sensor *sensor, u32 exposure)
+{
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_EXPOSURE_CTRL_HH_REG,
+ OV8865_EXPOSURE_CTRL_HH(exposure));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_EXPOSURE_CTRL_H_REG,
+ OV8865_EXPOSURE_CTRL_H(exposure));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_EXPOSURE_CTRL_L_REG,
+ OV8865_EXPOSURE_CTRL_L(exposure));
+}
+
+/* Gain */
+
+static int ov8865_gain_configure(struct ov8865_sensor *sensor, u32 gain)
+{
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_GAIN_CTRL_H_REG,
+ OV8865_GAIN_CTRL_H(gain));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_GAIN_CTRL_L_REG,
+ OV8865_GAIN_CTRL_L(gain));
+}
+
+/* White Balance */
+
+static int ov8865_red_balance_configure(struct ov8865_sensor *sensor,
+ u32 red_balance)
+{
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_ISP_GAIN_RED_H_REG,
+ OV8865_ISP_GAIN_RED_H(red_balance));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_ISP_GAIN_RED_L_REG,
+ OV8865_ISP_GAIN_RED_L(red_balance));
+}
+
+static int ov8865_blue_balance_configure(struct ov8865_sensor *sensor,
+ u32 blue_balance)
+{
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_ISP_GAIN_BLUE_H_REG,
+ OV8865_ISP_GAIN_BLUE_H(blue_balance));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_ISP_GAIN_BLUE_L_REG,
+ OV8865_ISP_GAIN_BLUE_L(blue_balance));
+}
+
+/* Flip */
+
+static int ov8865_flip_vert_configure(struct ov8865_sensor *sensor, bool enable)
+{
+ u8 bits = OV8865_FORMAT1_FLIP_VERT_ISP_EN |
+ OV8865_FORMAT1_FLIP_VERT_SENSOR_EN;
+
+ return ov8865_update_bits(sensor, OV8865_FORMAT1_REG, bits,
+ enable ? bits : 0);
+}
+
+static int ov8865_flip_horz_configure(struct ov8865_sensor *sensor, bool enable)
+{
+ u8 bits = OV8865_FORMAT2_FLIP_HORZ_ISP_EN |
+ OV8865_FORMAT2_FLIP_HORZ_SENSOR_EN;
+
+ return ov8865_update_bits(sensor, OV8865_FORMAT2_REG, bits,
+ enable ? bits : 0);
+}
+
+/* Test Pattern */
+
+static int ov8865_test_pattern_configure(struct ov8865_sensor *sensor,
+ unsigned int index)
+{
+ if (index >= ARRAY_SIZE(ov8865_test_pattern_bits))
+ return -EINVAL;
+
+ return ov8865_write(sensor, OV8865_PRE_CTRL0_REG,
+ ov8865_test_pattern_bits[index]);
+}
+
+/* State */
+
+static int ov8865_state_mipi_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode,
+ u32 mbus_code)
+{
+ struct ov8865_ctrls *ctrls = &sensor->ctrls;
+ struct v4l2_fwnode_bus_mipi_csi2 *bus_mipi_csi2 =
+ &sensor->endpoint.bus.mipi_csi2;
+ unsigned long mipi_clk_rate;
+ unsigned int bits_per_sample;
+ unsigned int lanes_count;
+ unsigned int i, j;
+ s64 mipi_pixel_rate;
+
+ mipi_clk_rate = ov8865_mode_mipi_clk_rate(sensor, mode);
+ if (!mipi_clk_rate)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(ov8865_link_freq_menu); i++) {
+ s64 freq = ov8865_link_freq_menu[i];
+
+ if (freq == mipi_clk_rate)
+ break;
+ }
+
+ for (j = 0; j < sensor->endpoint.nr_of_link_frequencies; j++) {
+ u64 freq = sensor->endpoint.link_frequencies[j];
+
+ if (freq == mipi_clk_rate)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(ov8865_link_freq_menu)) {
+ dev_err(sensor->dev,
+ "failed to find %lu clk rate in link freq\n",
+ mipi_clk_rate);
+ } else if (j == sensor->endpoint.nr_of_link_frequencies) {
+ dev_err(sensor->dev,
+ "failed to find %lu clk rate in endpoint link-frequencies\n",
+ mipi_clk_rate);
+ } else {
+ __v4l2_ctrl_s_ctrl(ctrls->link_freq, i);
+ }
+
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ bits_per_sample = 10;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ lanes_count = bus_mipi_csi2->num_data_lanes;
+ mipi_pixel_rate = mipi_clk_rate * 2 * lanes_count / bits_per_sample;
+
+ __v4l2_ctrl_s_ctrl_int64(ctrls->pixel_rate, mipi_pixel_rate);
+
+ return 0;
+}
+
+static int ov8865_state_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode,
+ u32 mbus_code)
+{
+ int ret;
+
+ if (sensor->state.streaming)
+ return -EBUSY;
+
+ /* State will be configured at first power on otherwise. */
+ if (pm_runtime_enabled(sensor->dev) &&
+ !pm_runtime_suspended(sensor->dev)) {
+ ret = ov8865_mode_configure(sensor, mode, mbus_code);
+ if (ret)
+ return ret;
+ }
+
+ ret = ov8865_state_mipi_configure(sensor, mode, mbus_code);
+ if (ret)
+ return ret;
+
+ sensor->state.mode = mode;
+ sensor->state.mbus_code = mbus_code;
+
+ return 0;
+}
+
+static int ov8865_state_init(struct ov8865_sensor *sensor)
+{
+ return ov8865_state_configure(sensor, &ov8865_modes[0],
+ ov8865_mbus_codes[0]);
+}
+
+/* Sensor Base */
+
+static int ov8865_sensor_init(struct ov8865_sensor *sensor)
+{
+ int ret;
+
+ ret = ov8865_sw_reset(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to perform sw reset\n");
+ return ret;
+ }
+
+ ret = ov8865_sw_standby(sensor, 1);
+ if (ret) {
+ dev_err(sensor->dev, "failed to set sensor standby\n");
+ return ret;
+ }
+
+ ret = ov8865_chip_id_check(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to check sensor chip id\n");
+ return ret;
+ }
+
+ ret = ov8865_write_sequence(sensor, ov8865_init_sequence,
+ ARRAY_SIZE(ov8865_init_sequence));
+ if (ret) {
+ dev_err(sensor->dev, "failed to write init sequence\n");
+ return ret;
+ }
+
+ ret = ov8865_charge_pump_configure(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure pad\n");
+ return ret;
+ }
+
+ ret = ov8865_mipi_configure(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure MIPI\n");
+ return ret;
+ }
+
+ ret = ov8865_isp_configure(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure ISP\n");
+ return ret;
+ }
+
+ ret = ov8865_black_level_configure(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure black level\n");
+ return ret;
+ }
+
+ /* Configure current mode. */
+ ret = ov8865_state_configure(sensor, sensor->state.mode,
+ sensor->state.mbus_code);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure state\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ov8865_sensor_power(struct ov8865_sensor *sensor, bool on)
+{
+ /* Keep initialized to zero for disable label. */
+ int ret = 0;
+
+ if (on) {
+ gpiod_set_value_cansleep(sensor->reset, 1);
+ gpiod_set_value_cansleep(sensor->powerdown, 1);
+
+ ret = regulator_enable(sensor->dovdd);
+ if (ret) {
+ dev_err(sensor->dev,
+ "failed to enable DOVDD regulator\n");
+ goto disable;
+ }
+
+ ret = regulator_enable(sensor->avdd);
+ if (ret) {
+ dev_err(sensor->dev,
+ "failed to enable AVDD regulator\n");
+ goto disable;
+ }
+
+ ret = regulator_enable(sensor->dvdd);
+ if (ret) {
+ dev_err(sensor->dev,
+ "failed to enable DVDD regulator\n");
+ goto disable;
+ }
+
+ ret = clk_prepare_enable(sensor->extclk);
+ if (ret) {
+ dev_err(sensor->dev, "failed to enable EXTCLK clock\n");
+ goto disable;
+ }
+
+ gpiod_set_value_cansleep(sensor->reset, 0);
+ gpiod_set_value_cansleep(sensor->powerdown, 0);
+
+ /* Time to enter streaming mode according to power timings. */
+ usleep_range(10000, 12000);
+ } else {
+disable:
+ gpiod_set_value_cansleep(sensor->powerdown, 1);
+ gpiod_set_value_cansleep(sensor->reset, 1);
+
+ clk_disable_unprepare(sensor->extclk);
+
+ regulator_disable(sensor->dvdd);
+ regulator_disable(sensor->avdd);
+ regulator_disable(sensor->dovdd);
+ }
+
+ return ret;
+}
+
+/* Controls */
+
+static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct v4l2_subdev *subdev = ov8865_ctrl_subdev(ctrl);
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ unsigned int index;
+ int ret;
+
+ /* Wait for the sensor to be on before setting controls. */
+ if (pm_runtime_suspended(sensor->dev))
+ return 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ ret = ov8865_exposure_configure(sensor, ctrl->val);
+ if (ret)
+ return ret;
+ break;
+ case V4L2_CID_GAIN:
+ ret = ov8865_gain_configure(sensor, ctrl->val);
+ if (ret)
+ return ret;
+ break;
+ case V4L2_CID_RED_BALANCE:
+ return ov8865_red_balance_configure(sensor, ctrl->val);
+ case V4L2_CID_BLUE_BALANCE:
+ return ov8865_blue_balance_configure(sensor, ctrl->val);
+ case V4L2_CID_HFLIP:
+ return ov8865_flip_horz_configure(sensor, !!ctrl->val);
+ case V4L2_CID_VFLIP:
+ return ov8865_flip_vert_configure(sensor, !!ctrl->val);
+ case V4L2_CID_TEST_PATTERN:
+ index = (unsigned int)ctrl->val;
+ return ov8865_test_pattern_configure(sensor, index);
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops ov8865_ctrl_ops = {
+ .s_ctrl = ov8865_s_ctrl,
+};
+
+static int ov8865_ctrls_init(struct ov8865_sensor *sensor)
+{
+ struct ov8865_ctrls *ctrls = &sensor->ctrls;
+ struct v4l2_ctrl_handler *handler = &ctrls->handler;
+ const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops;
+ int ret;
+
+ v4l2_ctrl_handler_init(handler, 32);
+
+ /* Use our mutex for ctrl locking. */
+ handler->lock = &sensor->mutex;
+
+ /* Exposure */
+
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, 1048575, 16,
+ 512);
+
+ /* Gain */
+
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_GAIN, 128, 8191, 128, 128);
+
+ /* White Balance */
+
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_RED_BALANCE, 1, 32767, 1,
+ 1024);
+
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_BLUE_BALANCE, 1, 32767, 1,
+ 1024);
+
+ /* Flip */
+
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_VFLIP, 0, 1, 1, 0);
+
+ /* Test Pattern */
+
+ v4l2_ctrl_new_std_menu_items(handler, ops, V4L2_CID_TEST_PATTERN,
+ ARRAY_SIZE(ov8865_test_pattern_menu) - 1,
+ 0, 0, ov8865_test_pattern_menu);
+
+ /* MIPI CSI-2 */
+
+ ctrls->link_freq =
+ v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ,
+ ARRAY_SIZE(ov8865_link_freq_menu) - 1,
+ 0, ov8865_link_freq_menu);
+
+ ctrls->pixel_rate =
+ v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 1,
+ INT_MAX, 1, 1);
+
+ if (handler->error) {
+ ret = handler->error;
+ goto error_ctrls;
+ }
+
+ ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+ ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ sensor->subdev.ctrl_handler = handler;
+
+ return 0;
+
+error_ctrls:
+ v4l2_ctrl_handler_free(handler);
+
+ return ret;
+}
+
+/* Subdev Video Operations */
+
+static int ov8865_s_stream(struct v4l2_subdev *subdev, int enable)
+{
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ struct ov8865_state *state = &sensor->state;
+ int ret;
+
+ if (enable) {
+ ret = pm_runtime_get_sync(sensor->dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(sensor->dev);
+ return ret;
+ }
+ }
+
+ mutex_lock(&sensor->mutex);
+ ret = ov8865_sw_standby(sensor, !enable);
+ mutex_unlock(&sensor->mutex);
+
+ if (ret)
+ return ret;
+
+ state->streaming = !!enable;
+
+ if (!enable)
+ pm_runtime_put(sensor->dev);
+
+ return 0;
+}
+
+static int ov8865_g_frame_interval(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_frame_interval *interval)
+{
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ const struct ov8865_mode *mode;
+ int ret = 0;
+
+ mutex_lock(&sensor->mutex);
+
+ mode = sensor->state.mode;
+ interval->interval = mode->frame_interval;
+
+ mutex_unlock(&sensor->mutex);
+
+ return ret;
+}
+
+static const struct v4l2_subdev_video_ops ov8865_subdev_video_ops = {
+ .s_stream = ov8865_s_stream,
+ .g_frame_interval = ov8865_g_frame_interval,
+ .s_frame_interval = ov8865_g_frame_interval,
+};
+
+/* Subdev Pad Operations */
+
+static int ov8865_enum_mbus_code(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_mbus_code_enum *code_enum)
+{
+ if (code_enum->index >= ARRAY_SIZE(ov8865_mbus_codes))
+ return -EINVAL;
+
+ code_enum->code = ov8865_mbus_codes[code_enum->index];
+
+ return 0;
+}
+
+static void ov8865_mbus_format_fill(struct v4l2_mbus_framefmt *mbus_format,
+ u32 mbus_code,
+ const struct ov8865_mode *mode)
+{
+ mbus_format->width = mode->output_size_x;
+ mbus_format->height = mode->output_size_y;
+ mbus_format->code = mbus_code;
+
+ mbus_format->field = V4L2_FIELD_NONE;
+ mbus_format->colorspace = V4L2_COLORSPACE_RAW;
+ mbus_format->ycbcr_enc =
+ V4L2_MAP_YCBCR_ENC_DEFAULT(mbus_format->colorspace);
+ mbus_format->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+ mbus_format->xfer_func =
+ V4L2_MAP_XFER_FUNC_DEFAULT(mbus_format->colorspace);
+}
+
+static int ov8865_get_fmt(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_format *format)
+{
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ struct v4l2_mbus_framefmt *mbus_format = &format->format;
+
+ mutex_lock(&sensor->mutex);
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+ *mbus_format = *v4l2_subdev_get_try_format(subdev, config,
+ format->pad);
+ else
+ ov8865_mbus_format_fill(mbus_format, sensor->state.mbus_code,
+ sensor->state.mode);
+
+ mutex_unlock(&sensor->mutex);
+
+ return 0;
+}
+
+static int ov8865_set_fmt(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_format *format)
+{
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ struct v4l2_mbus_framefmt *mbus_format = &format->format;
+ const struct ov8865_mode *mode;
+ u32 mbus_code = 0;
+ unsigned int index;
+ int ret = 0;
+
+ mutex_lock(&sensor->mutex);
+
+ if (sensor->state.streaming) {
+ ret = -EBUSY;
+ goto complete;
+ }
+
+ /* Try to find requested mbus code. */
+ for (index = 0; index < ARRAY_SIZE(ov8865_mbus_codes); index++) {
+ if (ov8865_mbus_codes[index] == mbus_format->code) {
+ mbus_code = mbus_format->code;
+ break;
+ }
+ }
+
+ /* Fallback to default. */
+ if (!mbus_code)
+ mbus_code = ov8865_mbus_codes[0];
+
+ /* Find the mode with nearest dimensions. */
+ mode = v4l2_find_nearest_size(ov8865_modes, ARRAY_SIZE(ov8865_modes),
+ output_size_x, output_size_y,
+ mbus_format->width, mbus_format->height);
+ if (!mode) {
+ ret = -EINVAL;
+ goto complete;
+ }
+
+ ov8865_mbus_format_fill(mbus_format, mbus_code, mode);
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+ *v4l2_subdev_get_try_format(subdev, config, format->pad) =
+ *mbus_format;
+ else if (sensor->state.mode != mode ||
+ sensor->state.mbus_code != mbus_code)
+ ret = ov8865_state_configure(sensor, mode, mbus_code);
+
+complete:
+ mutex_unlock(&sensor->mutex);
+
+ return ret;
+}
+
+static int ov8865_enum_frame_size(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_frame_size_enum *size_enum)
+{
+ const struct ov8865_mode *mode;
+
+ if (size_enum->index >= ARRAY_SIZE(ov8865_modes))
+ return -EINVAL;
+
+ mode = &ov8865_modes[size_enum->index];
+
+ size_enum->min_width = size_enum->max_width = mode->output_size_x;
+ size_enum->min_height = size_enum->max_height = mode->output_size_y;
+
+ return 0;
+}
+
+static int ov8865_enum_frame_interval(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_frame_interval_enum *interval_enum)
+{
+ const struct ov8865_mode *mode = NULL;
+ unsigned int mode_index;
+ unsigned int interval_index;
+
+ if (interval_enum->index > 0)
+ return -EINVAL;
+ /*
+ * Multiple modes with the same dimensions may have different frame
+ * intervals, so look up each relevant mode.
+ */
+ for (mode_index = 0, interval_index = 0;
+ mode_index < ARRAY_SIZE(ov8865_modes); mode_index++) {
+ mode = &ov8865_modes[mode_index];
+
+ if (mode->output_size_x == interval_enum->width &&
+ mode->output_size_y == interval_enum->height) {
+ if (interval_index == interval_enum->index)
+ break;
+
+ interval_index++;
+ }
+ }
+
+ if (mode_index == ARRAY_SIZE(ov8865_modes) || !mode)
+ return -EINVAL;
+
+ interval_enum->interval = mode->frame_interval;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops ov8865_subdev_pad_ops = {
+ .enum_mbus_code = ov8865_enum_mbus_code,
+ .get_fmt = ov8865_get_fmt,
+ .set_fmt = ov8865_set_fmt,
+ .enum_frame_size = ov8865_enum_frame_size,
+ .enum_frame_interval = ov8865_enum_frame_interval,
+};
+
+static const struct v4l2_subdev_ops ov8865_subdev_ops = {
+ .video = &ov8865_subdev_video_ops,
+ .pad = &ov8865_subdev_pad_ops,
+};
+
+static int ov8865_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ struct ov8865_state *state = &sensor->state;
+ int ret = 0;
+
+ mutex_lock(&sensor->mutex);
+
+ if (state->streaming) {
+ ret = ov8865_sw_standby(sensor, true);
+ if (ret)
+ goto complete;
+ }
+
+ ret = ov8865_sensor_power(sensor, false);
+ if (ret)
+ ov8865_sw_standby(sensor, false);
+
+complete:
+ mutex_unlock(&sensor->mutex);
+
+ return ret;
+}
+
+static int ov8865_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ struct ov8865_state *state = &sensor->state;
+ int ret = 0;
+
+ mutex_lock(&sensor->mutex);
+
+ ret = ov8865_sensor_power(sensor, true);
+ if (ret)
+ goto complete;
+
+ ret = ov8865_sensor_init(sensor);
+ if (ret)
+ goto error_power;
+
+ ret = __v4l2_ctrl_handler_setup(&sensor->ctrls.handler);
+ if (ret)
+ goto error_power;
+
+ if (state->streaming) {
+ ret = ov8865_sw_standby(sensor, false);
+ if (ret)
+ goto error_power;
+ }
+
+ goto complete;
+
+error_power:
+ ov8865_sensor_power(sensor, false);
+
+complete:
+ mutex_unlock(&sensor->mutex);
+
+ return ret;
+}
+
+static int ov8865_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct fwnode_handle *handle;
+ struct ov8865_sensor *sensor;
+ struct v4l2_subdev *subdev;
+ struct media_pad *pad;
+ unsigned long rate;
+ int ret;
+
+ sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
+ if (!sensor)
+ return -ENOMEM;
+
+ sensor->dev = dev;
+ sensor->i2c_client = client;
+
+ /* Graph Endpoint */
+
+ handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+ if (!handle) {
+ dev_err(dev, "unable to find enpoint node\n");
+ return -EINVAL;
+ }
+
+ sensor->endpoint.bus_type = V4L2_MBUS_CSI2_DPHY;
+
+ ret = v4l2_fwnode_endpoint_alloc_parse(handle, &sensor->endpoint);
+ fwnode_handle_put(handle);
+ if (ret) {
+ dev_err(dev, "failed to parse endpoint node\n");
+ return ret;
+ }
+
+ /* GPIOs */
+
+ sensor->powerdown = devm_gpiod_get_optional(dev, "powerdown",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(sensor->powerdown)) {
+ ret = PTR_ERR(sensor->powerdown);
+ goto error_endpoint;
+ }
+
+ sensor->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(sensor->reset)) {
+ ret = PTR_ERR(sensor->reset);
+ goto error_endpoint;
+ }
+
+ /* Regulators */
+
+ /* DVDD: digital core */
+ sensor->dvdd = devm_regulator_get(dev, "dvdd");
+ if (IS_ERR(sensor->dvdd)) {
+ dev_err(dev, "cannot get DVDD (digital core) regulator\n");
+ ret = PTR_ERR(sensor->dvdd);
+ goto error_endpoint;
+ }
+
+ /* DOVDD: digital I/O */
+ sensor->dovdd = devm_regulator_get(dev, "dovdd");
+ if (IS_ERR(sensor->dvdd)) {
+ dev_err(dev, "cannot get DOVDD (digital I/O) regulator\n");
+ ret = PTR_ERR(sensor->dvdd);
+ goto error_endpoint;
+ }
+
+ /* AVDD: analog */
+ sensor->avdd = devm_regulator_get(dev, "avdd");
+ if (IS_ERR(sensor->avdd)) {
+ dev_err(dev, "cannot get AVDD (analog) regulator\n");
+ ret = PTR_ERR(sensor->dvdd);
+ goto error_endpoint;
+ }
+
+ /* External Clock */
+
+ sensor->extclk = devm_clk_get(dev, NULL);
+ if (IS_ERR(sensor->extclk)) {
+ dev_err(dev, "failed to get external clock\n");
+ ret = PTR_ERR(sensor->extclk);
+ goto error_endpoint;
+ }
+
+ rate = clk_get_rate(sensor->extclk);
+ if (rate != OV8865_EXTCLK_RATE) {
+ dev_err(dev, "clock rate %lu Hz is unsupported\n", rate);
+ ret = -EINVAL;
+ goto error_endpoint;
+ }
+
+ /* Subdev, entity and pad */
+
+ subdev = &sensor->subdev;
+ v4l2_i2c_subdev_init(subdev, client, &ov8865_subdev_ops);
+
+ subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ subdev->entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+ pad = &sensor->pad;
+ pad->flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&subdev->entity, 1, pad);
+ if (ret)
+ goto error_entity;
+
+ /* Mutex */
+
+ mutex_init(&sensor->mutex);
+
+ /* Sensor */
+
+ ret = ov8865_ctrls_init(sensor);
+ if (ret)
+ goto error_mutex;
+
+ ret = ov8865_state_init(sensor);
+ if (ret)
+ goto error_ctrls;
+
+ /* Runtime PM */
+
+ pm_runtime_enable(sensor->dev);
+ pm_runtime_set_suspended(sensor->dev);
+
+ /* V4L2 subdev register */
+
+ ret = v4l2_async_register_subdev_sensor_common(subdev);
+ if (ret)
+ goto error_pm;
+
+ return 0;
+
+error_pm:
+ pm_runtime_disable(sensor->dev);
+
+error_ctrls:
+ v4l2_ctrl_handler_free(&sensor->ctrls.handler);
+
+error_mutex:
+ mutex_destroy(&sensor->mutex);
+
+error_entity:
+ media_entity_cleanup(&sensor->subdev.entity);
+
+error_endpoint:
+ v4l2_fwnode_endpoint_free(&sensor->endpoint);
+
+ return ret;
+}
+
+static int ov8865_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+
+ v4l2_async_unregister_subdev(subdev);
+ pm_runtime_disable(sensor->dev);
+ v4l2_ctrl_handler_free(&sensor->ctrls.handler);
+ mutex_destroy(&sensor->mutex);
+ media_entity_cleanup(&subdev->entity);
+
+ v4l2_fwnode_endpoint_free(&sensor->endpoint);
+
+ return 0;
+}
+
+static const struct dev_pm_ops ov8865_pm_ops = {
+ SET_RUNTIME_PM_OPS(ov8865_suspend, ov8865_resume, NULL)
+};
+
+static const struct of_device_id ov8865_of_match[] = {
+ { .compatible = "ovti,ov8865" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ov8865_of_match);
+
+static struct i2c_driver ov8865_driver = {
+ .driver = {
+ .name = "ov8865",
+ .of_match_table = ov8865_of_match,
+ .pm = &ov8865_pm_ops,
+ },
+ .probe_new = ov8865_probe,
+ .remove = ov8865_remove,
+};
+
+module_i2c_driver(ov8865_driver);
+
+MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>");
+MODULE_DESCRIPTION("V4L2 driver for the OmniVision OV8865 image sensor");
+MODULE_LICENSE("GPL v2");