| // 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 endpoint 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->dovdd)) { |
| dev_err(dev, "cannot get DOVDD (digital I/O) regulator\n"); |
| ret = PTR_ERR(sensor->dovdd); |
| 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->avdd); |
| 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"); |