lkml.org 
[lkml]   [2020]   [Aug]   [28]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[PATCH v2 2/3] media: i2c: Add support for the OV8865 image sensor
Date
The ov8865 sensor from the Omnivision supports up to 3264x2448,
a 10 bits output format and MIPI CSI2 interface.

The following driver adds support of all the resolutions at 30
and 60 fps as well as the adjustement of the exposure, the gain and
the rotation of the image.

Signed-off-by: Kévin L'hôpital <kevin.lhopital@bootlin.com>
---
drivers/media/i2c/Kconfig | 12 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/ov8865.c | 2541 ++++++++++++++++++++++++++++++++++++
3 files changed, 2554 insertions(+)
create mode 100644 drivers/media/i2c/ov8865.c

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index c68e002d26ea..f50ea3410be7 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -819,6 +819,18 @@ 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 OF
+ depends on GPIOLIB && I2C && VIDEO_V4L2 && 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
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index c147bb9d28db..f7779483a86a 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -79,6 +79,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
diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c
new file mode 100644
index 000000000000..6992a433c0b4
--- /dev/null
+++ b/drivers/media/i2c/ov8865.c
@@ -0,0 +1,2541 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * OV8865 MIPI Camera Subdev Driver
+ * Copyright (C) 2020 Kévin L'hôpital.
+ * Based on the ov5640 driver and an out of tree ov8865 driver by Allwinner.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+#define OV8865_XCLK_FREQ 24000000
+
+/* System */
+
+#define OV8865_SW_STANDBY_REG 0x0100
+#define OV8865_SW_STANDBY_STANDBY_N BIT(0)
+
+#define OV8865_SW_RESET_REG 0x0103
+
+#define OV8865_PLL_CTRL2_REG 0x0302
+#define OV8865_PLL_CTRL3_REG 0x0303
+#define OV8865_PLL_CTRL4_REG 0x0304
+#define OV8865_PLL_CTRLE_REG 0x030e
+#define OV8865_PLL_CTRLF_REG 0x030f
+#define OV8865_PLL_CTRL12_REG 0x0312
+#define OV8865_PLL_CTRL1E_REG 0x031e
+
+#define OV8865_SLAVE_ID_REG 0x3004
+#define OV8865_SLAVE_ID_DEFAULT 0x36
+
+#define OV8865_PUMP_CLK_DIV_REG 0x3015
+
+#define OV8865_MIPI_CTRL_REG 0x3018
+#define OV8865_CLOCK_SEL_REG 0x3020
+#define OV8865_MIPI_SC_CTRL_REG 0X3022
+
+#define OV8865_CHIP_ID_REG 0x300a
+#define OV8865_CHIP_ID 0x008865
+
+/* Exposure/gain/banding */
+
+#define OV8865_EXPOSURE_CTRL_HH_REG 0x3500
+#define OV8865_EXPOSURE_CTRL_H_REG 0x3501
+#define OV8865_EXPOSURE_CTRL_L_REG 0x3502
+#define OV8865_MANUAL_CTRL_REG 0x3503
+#define OV8865_GAIN_CTRL_H_REG 0x3508
+#define OV8865_GAIN_CTRL_L_REG 0x3509
+
+#define OV8865_ASP_CTRL41_REG 0x3641
+#define OV8865_ASP_CTRL46_REG 0x3646
+#define OV8865_ASP_CTRL47_REG 0x3647
+#define OV8865_ASP_CTRL50_REG 0x364a
+
+/* Timing control */
+#define OV8865_X_ADDR_START_H_REG 0x3800
+#define OV8865_X_ADDR_START_L_REG 0x3801
+#define OV8865_Y_ADDR_START_H_REG 0x3802
+#define OV8865_Y_ADDR_START_L_REG 0x3803
+#define OV8865_X_ADDR_END_H_REG 0x3804
+#define OV8865_X_ADDR_END_L_REG 0x3805
+#define OV8865_Y_ADDR_END_H_REG 0x3806
+#define OV8865_Y_ADDR_END_L_REG 0x3807
+#define OV8865_X_OUTPUT_SIZE_REG 0x3808
+#define OV8865_Y_OUTPUT_SIZE_REG 0x380a
+#define OV8865_HTS_REG 0x380c
+#define OV8865_VTS_REG 0x380e
+#define OV8865_ISP_X_WIN_H_REG 0x3810
+#define OV8865_ISP_X_WIN_L_REG 0x3811
+#define OV8865_ISP_Y_WIN_L_REG 0x3813
+#define OV8865_X_INC_ODD_REG 0x3814
+#define OV8865_X_INC_EVEN_REG 0x3815
+#define OV8865_FORMAT1_REG 0x3820
+#define OV8865_FORMAT1_MIRROR_ARR BIT(1)
+#define OV8865_FORMAT1_MIRROR_DIG BIT(2)
+#define OV8865_FORMAT2_REG 0x3821
+#define OV8865_FORMAT2_MIRROR_ARR BIT(1)
+#define OV8865_FORMAT2_MIRROR_DIG BIT(2)
+#define OV8865_Y_INC_ODD_REG 0x382a
+#define OV8865_Y_INC_EVEN_REG 0x382b
+#define OV8865_BLC_NUM_OPTION_REG 0x3830
+#define OV8865_ZLINE_NUM_OPTION_REG 0x3836
+#define OV8865_RGBC_REG 0x3837
+#define OV8865_AUTO_SIZE_CTRL0_REG 0x3841
+#define OV8865_BOUNDARY_PIX_NUM_REG 0x3846
+
+/* OTP */
+
+#define OV8865_OTP_REG 0x3d85
+#define OV8865_OTP_SETT_STT_ADDR_H_REG 0x3d8c
+#define OV8865_OTP_SETT_STT_ADDR_L_REG 0x3d8d
+
+/* Black Level */
+
+#define OV8865_BLC_CTRL0_REG 0x4000
+#define OV8865_BLC_CTRL1_REG 0x4001
+#define OV8865_BLC_CTRL5_REG 0x4005
+#define OV8865_BLC_CTRLB_REG 0x400b
+#define OV8865_BLC_CTRLD_REG 0x400d
+#define OV8865_BLC_CTRL1B_REG 0x401b
+#define OV8865_BLC_CTRL1D_REG 0x401d
+#define OV8865_BLC_CTRL1F_REG 0x401f
+#define OV8865_ANCHOR_LEFT_START_H_REG 0x4020
+#define OV8865_ANCHOR_LEFT_START_L_REG 0x4021
+#define OV8865_ANCHOR_LEFT_END_H_REG 0x4022
+#define OV8865_ANCHOR_LEFT_END_L_REG 0x4023
+#define OV8865_ANCHOR_RIGHT_START_H_REG 0x4024
+#define OV8865_ANCHOR_RIGHT_START_L_REG 0x4025
+#define OV8865_ANCHOR_RIGHT_END_H_REG 0x4026
+#define OV8865_ANCHOR_RIGHT_END_L_REG 0x4027
+#define OV8865_TOP_ZLINE_ST_REG 0x4028
+#define OV8865_TOP_ZLINE_NUM_REG 0x4029
+#define OV8865_TOP_BKLINE_ST_REG 0x402a
+#define OV8865_TOP_BKLINE_NUM_REG 0x402b
+#define OV8865_BOT_ZLINE_ST_REG 0x402c
+#define OV8865_BOT_ZLINE_NUM_REG 0x402d
+#define OV8865_BOT_BLKLINE_ST_REG 0x402e
+#define OV8865_BOT_BLKLINE_NUM_REG 0x402f
+#define OV8865_BLC_OFFSET_LIMIT_REG 0x4034
+
+/* Format Control */
+
+#define OV8865_CLIP_MAX_HI_REG 0x4300
+#define OV8865_CLIP_MIN_HI_REG 0x4301
+#define OV8865_CLIP_LO_REG 0x4302
+
+#define OV8865_R_VFIFO_READ_START_REG 0x4601
+
+/* MIPI Control */
+
+#define OV8865_MIPI_CTRL13_REG 0x4813
+#define OV8865_CLK_PREPARE_MIN_REG 0x481f
+#define OV8865_PCLK_PERIOD_REG 0x4837
+#define OV8865_LANE_SEL01_REG 0x4850
+#define OV8865_LANE_SEL23_REG 0x4851
+
+/* LVDS Control */
+
+#define OV8865_LVDS_R0_REG 0x4b00
+#define OV8865_LVDS_BLK_TIMES_H_REG 0x4b0c
+#define OV8865_LVDS_BLK_TIMES_L_REG 0x4b0d
+
+/* DSP Control */
+
+#define OV8865_ISP_CTRL0_REG 0x5000
+#define OV8865_ISP_CTRL1_REG 0x5001
+#define OV8865_ISP_CTRL2_REG 0x5002
+
+#define OV8865_AVG_READOUT_REG 0x568a
+
+/* Pre DSP Control */
+
+#define OV8865_PRE_CTRL0 0x5e00
+#define OV8865_PRE_CTRL1 0x5e01
+
+/* OTP DPC Control */
+
+#define OV8865_OTP_CTRL0 0x5b00
+#define OV8865_OTP_CTRL1 0x5b01
+#define OV8865_OTP_CTRL2 0x5b02
+#define OV8865_OTP_CTRL3 0x5b03
+#define OV8865_OTP_CTRL5 0x5b05
+
+/* LENC Control */
+
+#define OV8865_LENC_G0_REG 0x5800
+#define OV8865_LENC_G1_REG 0x5801
+#define OV8865_LENC_G2_REG 0x5802
+#define OV8865_LENC_G3_REG 0x5803
+#define OV8865_LENC_G4_REG 0x5804
+#define OV8865_LENC_G5_REG 0x5805
+#define OV8865_LENC_G10_REG 0x5806
+#define OV8865_LENC_G11_REG 0x5807
+#define OV8865_LENC_G12_REG 0x5808
+#define OV8865_LENC_G13_REG 0x5809
+#define OV8865_LENC_G14_REG 0x580a
+#define OV8865_LENC_G15_REG 0x580b
+#define OV8865_LENC_G20_REG 0x580c
+#define OV8865_LENC_G21_REG 0x580d
+#define OV8865_LENC_G22_REG 0x580e
+#define OV8865_LENC_G23_REG 0x580f
+#define OV8865_LENC_G24_REG 0x5810
+#define OV8865_LENC_G25_REG 0x5811
+#define OV8865_LENC_G30_REG 0x5812
+#define OV8865_LENC_G31_REG 0x5813
+#define OV8865_LENC_G32_REG 0x5814
+#define OV8865_LENC_G33_REG 0x5815
+#define OV8865_LENC_G34_REG 0x5816
+#define OV8865_LENC_G35_REG 0x5817
+#define OV8865_LENC_G40_REG 0x5818
+#define OV8865_LENC_G41_REG 0x5819
+#define OV8865_LENC_G42_REG 0x581a
+#define OV8865_LENC_G43_REG 0x581b
+#define OV8865_LENC_G44_REG 0x581c
+#define OV8865_LENC_G45_REG 0x581d
+#define OV8865_LENC_G50_REG 0x581e
+#define OV8865_LENC_G51_REG 0x581f
+#define OV8865_LENC_G52_REG 0x5820
+#define OV8865_LENC_G53_REG 0x5821
+#define OV8865_LENC_G54_REG 0x5822
+#define OV8865_LENC_G55_REG 0x5823
+#define OV8865_LENC_BR0_REG 0x5824
+#define OV8865_LENC_BR1_REG 0x5825
+#define OV8865_LENC_BR2_REG 0x5826
+#define OV8865_LENC_BR3_REG 0x5827
+#define OV8865_LENC_BR4_REG 0x5828
+#define OV8865_LENC_BR10_REG 0x5829
+#define OV8865_LENC_BR11_REG 0x582a
+#define OV8865_LENC_BR12_REG 0x582b
+#define OV8865_LENC_BR13_REG 0x582c
+#define OV8865_LENC_BR14_REG 0x582d
+#define OV8865_LENC_BR20_REG 0x582e
+#define OV8865_LENC_BR21_REG 0x582f
+#define OV8865_LENC_BR22_REG 0x5830
+#define OV8865_LENC_BR23_REG 0x5831
+#define OV8865_LENC_BR24_REG 0x5832
+#define OV8865_LENC_BR30_REG 0x5833
+#define OV8865_LENC_BR31_REG 0x5834
+#define OV8865_LENC_BR32_REG 0x5835
+#define OV8865_LENC_BR33_REG 0x5836
+#define OV8865_LENC_BR34_REG 0x5837
+#define OV8865_LENC_BR40_REG 0x5838
+#define OV8865_LENC_BR41_REG 0x5839
+#define OV8865_LENC_BR42_REG 0x583a
+#define OV8865_LENC_BR43_REG 0x583b
+#define OV8865_LENC_BR44_REG 0x583c
+#define OV8865_LENC_BROFFSET_REG 0x583d
+
+enum ov8865_mode_id {
+ OV8865_MODE_QUXGA_3264_2448 = 0,
+ OV8865_MODE_6M_3264_1836,
+ OV8865_MODE_1080P_1920_1080,
+ OV8865_MODE_720P_1280_720,
+ OV8865_MODE_UXGA_1600_1200,
+ OV8865_MODE_SVGA_800_600,
+ OV8865_MODE_VGA_640_480,
+ OV8865_NUM_MODES,
+};
+
+
+enum ov8865_frame_rate {
+ OV8865_30_FPS = 0,
+ OV8865_90_FPS,
+ OV8865_NUM_FRAMERATES,
+};
+
+static const int ov8865_framerates[] = {
+ [OV8865_30_FPS] = 30,
+ [OV8865_90_FPS] = 90,
+};
+
+struct ov8865_pixfmt {
+ u32 code;
+ u32 colorspace;
+};
+
+static const struct ov8865_pixfmt ov8865_formats[] = {
+ { MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_COLORSPACE_RAW, },
+};
+
+/* regulator supplies */
+static const char * const ov8865_supply_names[] = {
+ "AVDD", /* Analog (2.8V) supply */
+ "DOVDD", /* Digital I/O (1,8V/2.8V) supply */
+ "VDD2", /* Digital Core (1.2V) supply */
+ "AFVDD",
+};
+
+#define OV8865_NUM_SUPPLIES ARRAY_SIZE(ov8865_supply_names)
+
+struct reg_value {
+ u16 reg_addr;
+ u8 val;
+ u32 delay_ms;
+};
+
+struct ov8865_mode_info {
+ enum ov8865_mode_id id;
+ u32 hact;
+ u32 htot;
+ u32 vact;
+ u32 vtot;
+ const struct reg_value *reg_data;
+ u32 reg_data_size;
+};
+
+struct ov8865_ctrls {
+ struct v4l2_ctrl_handler handler;
+ struct v4l2_ctrl *pixel_rate;
+ struct v4l2_ctrl *exposure;
+ struct v4l2_ctrl *gain;
+ struct v4l2_ctrl *hflip;
+ struct v4l2_ctrl *vflip;
+};
+
+struct ov8865_dev {
+ struct i2c_client *i2c_client;
+ struct v4l2_subdev sd;
+ struct media_pad pad;
+ struct v4l2_fwnode_endpoint ep;
+ struct clk *xclk;
+
+ struct regulator_bulk_data supplies[OV8865_NUM_SUPPLIES];
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *pwdn_gpio;
+ bool upside_down;
+
+ struct mutex lock;
+
+ int power_count;
+
+ struct v4l2_mbus_framefmt fmt;
+
+ const struct ov8865_mode_info *current_mode;
+ const struct ov8865_mode_info *last_mode;
+ enum ov8865_frame_rate current_fr;
+ struct v4l2_fract frame_interval;
+ struct ov8865_ctrls ctrls;
+
+ bool streaming;
+};
+
+static inline struct ov8865_dev *to_ov8865_dev(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct ov8865_dev, sd);
+}
+
+static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
+{
+ return &container_of(ctrl->handler, struct ov8865_dev,
+ ctrls.handler)->sd;
+}
+
+static const struct reg_value ov8865_init_setting_QUXGA[] = {
+ { OV8865_SW_RESET_REG, 0x01, 16 },
+ { OV8865_SW_STANDBY_REG, 0x00 },
+ { OV8865_SW_STANDBY_REG, 0x00 },
+ { OV8865_SW_STANDBY_REG, 0x00 },
+ { OV8865_SW_STANDBY_REG, 0x00 },
+ { 0x3638, 0xff },
+ { OV8865_PUMP_CLK_DIV_REG, 0x01 },
+ { OV8865_MIPI_SC_CTRL_REG, 0x01 },
+ { 0x3031, 0x0a },
+ { 0x3305, 0xf1 },
+ { 0x3308, 0x00 },
+ { 0x3309, 0x28 },
+ { 0x330a, 0x00 },
+ { 0x330b, 0x20 },
+ { 0x330c, 0x00 },
+ { 0x330d, 0x00 },
+ { 0x330e, 0x00 },
+ { 0x330f, 0x40 },
+ { 0x3307, 0x04 },
+ { 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 },
+ { OV8865_ASP_CTRL41_REG, 0x55 },
+ { OV8865_ASP_CTRL46_REG, 0x86 },
+ { OV8865_ASP_CTRL47_REG, 0x27 },
+ { OV8865_ASP_CTRL50_REG, 0x1b },
+ { OV8865_EXPOSURE_CTRL_HH_REG, 0x00 },
+ { OV8865_EXPOSURE_CTRL_H_REG, 0x4c },
+ { OV8865_EXPOSURE_CTRL_L_REG, 0x00 },
+ { OV8865_MANUAL_CTRL_REG, 0x00 },
+ { OV8865_GAIN_CTRL_H_REG, 0x02 },
+ { OV8865_GAIN_CTRL_L_REG, 0x00 },
+ { 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 },
+ { OV8865_OTP_REG, 0x06 },
+ { OV8865_OTP_SETT_STT_ADDR_H_REG, 0x75 },
+ { OV8865_OTP_SETT_STT_ADDR_L_REG, 0xef },
+ { 0x3f08, 0x0b },
+ { OV8865_CLIP_MAX_HI_REG, 0xff },
+ { OV8865_CLIP_MIN_HI_REG, 0x00 },
+ { OV8865_CLIP_LO_REG, 0x0f },
+ { 0x4500, 0x40 },
+ { 0x4503, 0x10 },
+ { OV8865_R_VFIFO_READ_START_REG, 0x74 },
+ { OV8865_CLK_PREPARE_MIN_REG, 0x32 },
+ { OV8865_PCLK_PERIOD_REG, 0x16 },
+ { OV8865_LANE_SEL01_REG, 0x10 },
+ { OV8865_LANE_SEL23_REG, 0x32 },
+ { OV8865_LVDS_R0_REG, 0x2a },
+ { OV8865_LVDS_BLK_TIMES_L_REG, 0x00 },
+ { 0x4d00, 0x04 },
+ { 0x4d01, 0x18 },
+ { 0x4d02, 0xc3 },
+ { 0x4d03, 0xff },
+ { 0x4d04, 0xff },
+ { 0x4d05, 0xff },
+ { OV8865_ISP_CTRL0_REG, 0x96 },
+ { OV8865_ISP_CTRL1_REG, 0x01 },
+ { OV8865_ISP_CTRL2_REG, 0x08 },
+ { 0x5901, 0x00 },
+ { OV8865_PRE_CTRL0, 0x00 },
+ { OV8865_PRE_CTRL1, 0x41 },
+ { OV8865_SW_STANDBY_REG, OV8865_SW_STANDBY_STANDBY_N },
+ { OV8865_OTP_CTRL0, 0x02 },
+ { OV8865_OTP_CTRL1, 0xd0 },
+ { OV8865_OTP_CTRL2, 0x03 },
+ { OV8865_OTP_CTRL3, 0xff },
+ { OV8865_OTP_CTRL5, 0x6c },
+ { 0x5780, 0xfc },
+ { 0x5781, 0xdf },
+ { 0x5782, 0x3f },
+ { 0x5783, 0x08 },
+ { 0x5784, 0x0c },
+ { 0x5786, 0x20 },
+ { 0x5787, 0x40 },
+ { 0x5788, 0x08 },
+ { 0x5789, 0x08 },
+ { 0x578a, 0x02 },
+ { 0x578b, 0x01 },
+ { 0x578c, 0x01 },
+ { 0x578d, 0x0c },
+ { 0x578e, 0x02 },
+ { 0x578f, 0x01 },
+ { 0x5790, 0x01 },
+ { OV8865_LENC_G0_REG, 0x1d },
+ { OV8865_LENC_G1_REG, 0x0e },
+ { OV8865_LENC_G2_REG, 0x0c },
+ { OV8865_LENC_G3_REG, 0x0c },
+ { OV8865_LENC_G4_REG, 0x0f },
+ { OV8865_LENC_G5_REG, 0x22 },
+ { OV8865_LENC_G10_REG, 0x0a },
+ { OV8865_LENC_G11_REG, 0x06 },
+ { OV8865_LENC_G12_REG, 0x05 },
+ { OV8865_LENC_G13_REG, 0x05 },
+ { OV8865_LENC_G14_REG, 0x07 },
+ { OV8865_LENC_G15_REG, 0x0a },
+ { OV8865_LENC_G20_REG, 0x06 },
+ { OV8865_LENC_G21_REG, 0x02 },
+ { OV8865_LENC_G22_REG, 0x00 },
+ { OV8865_LENC_G23_REG, 0x00 },
+ { OV8865_LENC_G24_REG, 0x03 },
+ { OV8865_LENC_G25_REG, 0x07 },
+ { OV8865_LENC_G30_REG, 0x06 },
+ { OV8865_LENC_G31_REG, 0x02 },
+ { OV8865_LENC_G32_REG, 0x00 },
+ { OV8865_LENC_G33_REG, 0x00 },
+ { OV8865_LENC_G34_REG, 0x03 },
+ { OV8865_LENC_G35_REG, 0x07 },
+ { OV8865_LENC_G40_REG, 0x09 },
+ { OV8865_LENC_G41_REG, 0x06 },
+ { OV8865_LENC_G42_REG, 0x04 },
+ { OV8865_LENC_G43_REG, 0x04 },
+ { OV8865_LENC_G44_REG, 0x06 },
+ { OV8865_LENC_G45_REG, 0x0a },
+ { OV8865_LENC_G50_REG, 0x19 },
+ { OV8865_LENC_G51_REG, 0x0d },
+ { OV8865_LENC_G52_REG, 0x0b },
+ { OV8865_LENC_G53_REG, 0x0b },
+ { OV8865_LENC_G54_REG, 0x0e },
+ { OV8865_LENC_G55_REG, 0x22 },
+ { OV8865_LENC_BR0_REG, 0x23 },
+ { OV8865_LENC_BR1_REG, 0x28 },
+ { OV8865_LENC_BR2_REG, 0x29 },
+ { OV8865_LENC_BR3_REG, 0x27 },
+ { OV8865_LENC_BR4_REG, 0x13 },
+ { OV8865_LENC_BR10_REG, 0x26 },
+ { OV8865_LENC_BR11_REG, 0x33 },
+ { OV8865_LENC_BR12_REG, 0x32 },
+ { OV8865_LENC_BR13_REG, 0x33 },
+ { OV8865_LENC_BR14_REG, 0x16 },
+ { OV8865_LENC_BR20_REG, 0x14 },
+ { OV8865_LENC_BR21_REG, 0x30 },
+ { OV8865_LENC_BR22_REG, 0x31 },
+ { OV8865_LENC_BR23_REG, 0x30 },
+ { OV8865_LENC_BR24_REG, 0x15 },
+ { OV8865_LENC_BR30_REG, 0x26 },
+ { OV8865_LENC_BR31_REG, 0x23 },
+ { OV8865_LENC_BR32_REG, 0x21 },
+ { OV8865_LENC_BR33_REG, 0x23 },
+ { OV8865_LENC_BR34_REG, 0x05 },
+ { OV8865_LENC_BR40_REG, 0x36 },
+ { OV8865_LENC_BR41_REG, 0x27 },
+ { OV8865_LENC_BR42_REG, 0x28 },
+ { OV8865_LENC_BR43_REG, 0x26 },
+ { OV8865_LENC_BR44_REG, 0x24 },
+ { OV8865_LENC_BROFFSET_REG, 0xdf },
+ { OV8865_SW_STANDBY_REG, 0x00 },
+};
+
+static const struct reg_value ov8865_setting_QUXGA[] = {
+ { OV8865_SW_STANDBY_REG, 0x00, 5 },
+ { 0x3501, 0x98 },
+ { 0x3502, 0x60 },
+ { 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 },
+ { 0x3f08, 0x16 },
+ { 0x4500, 0x68 },
+ { OV8865_R_VFIFO_READ_START_REG, 0x10 },
+ { OV8865_ISP_CTRL2_REG, 0x08 },
+ { 0x5901, 0x00 },
+ { OV8865_SW_STANDBY_REG, 0x00 },
+};
+
+static const struct reg_value ov8865_setting_6M[] = {
+ { OV8865_SW_STANDBY_REG, 0x00, 5 },
+ { 0x3501, 0x72 },
+ { 0x3502, 0x20 },
+ { 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 },
+ { 0x3f08, 0x16 },
+ { 0x4500, 0x68 },
+ { OV8865_R_VFIFO_READ_START_REG, 0x10 },
+ { OV8865_ISP_CTRL2_REG, 0x08 },
+ { 0x5901, 0x00 },
+ { OV8865_SW_STANDBY_REG, 0x00 },
+};
+
+
+static const struct reg_value ov8865_setting_UXGA[] = {
+ { OV8865_SW_STANDBY_REG, 0x00, 5 },
+ { 0x3501, 0x4c },
+ { 0x3502, 0x00 },
+ { 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 },
+ { 0x3f08, 0x0b },
+ { 0x4500, 0x40 },
+ { OV8865_R_VFIFO_READ_START_REG, 0x74 },
+ { OV8865_ISP_CTRL2_REG, 0x08 },
+ { 0x5901, 0x00 },
+ { OV8865_SW_STANDBY_REG, 0x00 },
+};
+
+static const struct reg_value ov8865_setting_SVGA[] = {
+ { OV8865_SW_STANDBY_REG, 0x00, 5 },
+ { 0x3501, 0x26 },
+ { 0x3502, 0x00 },
+ { 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, 0x18 },
+ { 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 },
+ { 0x3f08, 0x0b },
+ { 0x4500, 0x40 },
+ { OV8865_R_VFIFO_READ_START_REG, 0x50 },
+ { OV8865_ISP_CTRL2_REG, 0x0c },
+ { 0x5901, 0x04 },
+ { OV8865_SW_STANDBY_REG, 0x00 },
+};
+
+static const struct ov8865_mode_info ov8865_mode_init_data = {
+ .id = 0,
+ .hact = 3264,
+ .htot = 1944,
+ .vact = 2448,
+ .vtot = 2470,
+ .reg_data = ov8865_init_setting_QUXGA,
+ .reg_data_size = ARRAY_SIZE(ov8865_init_setting_QUXGA),
+};
+
+static const struct ov8865_mode_info ov8865_mode_data[OV8865_NUM_MODES] = {
+ {
+ .id = OV8865_MODE_QUXGA_3264_2448,
+ .hact = 3264,
+ .htot = 1944,
+ .vact = 2448,
+ .vtot = 2470,
+ .reg_data = ov8865_setting_QUXGA,
+ .reg_data_size = ARRAY_SIZE(ov8865_setting_QUXGA)
+ },
+ {
+ .id = OV8865_MODE_6M_3264_1836,
+ .hact = 3264,
+ .htot = 2582,
+ .vact = 1836,
+ .vtot = 1858,
+ .reg_data = ov8865_setting_6M,
+ .reg_data_size = ARRAY_SIZE(ov8865_setting_6M)
+ },
+ {
+ .id = OV8865_MODE_1080P_1920_1080,
+ .hact = 1920,
+ .htot = 2582,
+ .vact = 1080,
+ .vtot = 1858,
+ .reg_data = ov8865_setting_6M,
+ .reg_data_size = ARRAY_SIZE(ov8865_setting_6M)
+ },
+ {
+ .id = OV8865_MODE_720P_1280_720,
+ .hact = 1280,
+ .htot = 1923,
+ .vact = 720,
+ .vtot = 1248,
+ .reg_data = ov8865_setting_UXGA,
+ .reg_data_size = ARRAY_SIZE(ov8865_setting_UXGA)
+ },
+ {
+ .id = OV8865_MODE_UXGA_1600_1200,
+ .hact = 1600,
+ .htot = 1923,
+ .vact = 1200,
+ .vtot = 1248,
+ .reg_data = ov8865_setting_UXGA,
+ .reg_data_size = ARRAY_SIZE(ov8865_setting_UXGA)
+ },
+ {
+ .id = OV8865_MODE_SVGA_800_600,
+ .hact = 800,
+ .htot = 1250,
+ .vact = 600,
+ .vtot = 640,
+ .reg_data = ov8865_setting_SVGA,
+ .reg_data_size = ARRAY_SIZE(ov8865_setting_SVGA)
+ },
+ {
+ .id = OV8865_MODE_VGA_640_480,
+ .hact = 640,
+ .htot = 2582,
+ .vact = 480,
+ .vtot = 1858,
+ .reg_data = ov8865_setting_6M,
+ .reg_data_size = ARRAY_SIZE(ov8865_setting_6M)
+ },
+};
+
+static int ov8865_write_reg(struct ov8865_dev *sensor, u16 reg, u8 val)
+{
+ struct i2c_client *client = sensor->i2c_client;
+ struct i2c_msg msg = { 0 };
+ u8 buf[3];
+ int ret;
+
+ buf[0] = reg >> 8;
+ buf[1] = reg & 0xff;
+ buf[2] = val;
+
+ msg.addr = client->addr;
+ msg.flags = client->flags;
+ msg.buf = buf;
+ msg.len = sizeof(buf);
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret < 0) {
+ dev_err(&client->dev, "%s: error: reg=%x, val=%x\n",
+ __func__, reg, val);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ov8865_write_reg16(struct ov8865_dev *sensor, u16 reg, u16 val)
+{
+ int ret;
+
+ ret = ov8865_write_reg(sensor, reg, val >> 8);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, reg + 1, val & 0xff);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ov8865_read_reg(struct ov8865_dev *sensor, u16 reg, u8 *val)
+{
+ struct i2c_client *client = sensor->i2c_client;
+ struct i2c_msg msg[2] = { 0 };
+ u8 buf[2];
+ int ret = 0;
+
+ buf[0] = reg >> 8;
+ buf[1] = reg & 0xff;
+
+ msg[0].addr = client->addr;
+ msg[0].flags = client->flags;
+ msg[0].buf = buf;
+ msg[0].len = sizeof(buf);
+
+ msg[1].addr = client->addr;
+ /* Read data from the sensor to the controller */
+ msg[1].flags = I2C_M_RD;
+ msg[1].buf = buf;
+ msg[1].len = 1;
+
+ ret = i2c_transfer(client->adapter, msg, 2);
+ if (ret < 0) {
+ dev_err(&client->dev, "%s: error: reg=%x\n", __func__, reg);
+ return ret;
+ }
+
+ *val = buf[0];
+
+ return 0;
+}
+
+static int ov8865_read_reg16(struct ov8865_dev *sensor, u16 reg, u16 *val)
+{
+ u8 hi, lo;
+ int ret;
+
+ ret = ov8865_read_reg(sensor, reg, &hi);
+ if (ret)
+ return ret;
+
+ ret = ov8865_read_reg(sensor, reg + 1, &lo);
+ if (ret)
+ return ret;
+
+ *val = ((u16)hi << 8) | (u16)lo;
+
+ return 0;
+}
+
+static int ov8865_mod_reg(struct ov8865_dev *sensor, u16 reg, u8 mask, u8 val)
+{
+ u8 readval;
+ int ret;
+
+ ret = ov8865_read_reg(sensor, reg, &readval);
+ if (ret)
+ return ret;
+
+ readval &= ~mask;
+ val &= mask;
+ val |= readval;
+
+ ret = ov8865_write_reg(sensor, reg, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ov8865_set_timings(struct ov8865_dev *sensor,
+ const struct ov8865_mode_info *mode)
+{
+ int ret;
+ u8 isp_y_win_l, x_inc_odd, format2, y_inc_odd,
+ y_inc_even, blc_num_option, zline_num_option,
+ boundary_pix_num;
+
+ ret = ov8865_write_reg(sensor, OV8865_X_ADDR_START_H_REG, 0x00);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_X_ADDR_START_L_REG, 0x0c);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_Y_ADDR_START_H_REG, 0x00);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_Y_ADDR_START_L_REG, 0x0c);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_X_ADDR_END_H_REG, 0x0c);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_X_ADDR_END_L_REG, 0xd3);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_Y_ADDR_END_H_REG, 0x09);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_Y_ADDR_END_L_REG, 0xa3);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg16(sensor, OV8865_X_OUTPUT_SIZE_REG, mode->hact);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg16(sensor, OV8865_Y_OUTPUT_SIZE_REG, mode->vact);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg16(sensor, OV8865_HTS_REG, mode->htot);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg16(sensor, OV8865_VTS_REG, mode->vtot);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_ISP_X_WIN_H_REG, 0x00);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_ISP_X_WIN_L_REG, 0x04);
+ if (ret)
+ return ret;
+
+ if ((mode->id == OV8865_MODE_720P_1280_720) ||
+ (mode->id == OV8865_MODE_UXGA_1600_1200) ||
+ (mode->id == OV8865_MODE_SVGA_800_600)) {
+ isp_y_win_l = 0x04;
+ x_inc_odd = 0x03;
+ blc_num_option = 0x08;
+ zline_num_option = 0x02;
+ boundary_pix_num = 0x88;
+
+ } else {
+ isp_y_win_l = 0x02;
+ x_inc_odd = 0x01;
+ blc_num_option = 0x04;
+ zline_num_option = 0x01;
+ boundary_pix_num = 0x48;
+ }
+
+ ret = ov8865_write_reg(sensor, OV8865_ISP_Y_WIN_L_REG, isp_y_win_l);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_X_INC_ODD_REG, x_inc_odd);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_X_INC_EVEN_REG, 0x01);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_FORMAT1_REG, 0x00);
+ if (ret)
+ return ret;
+
+ if ((mode->id == OV8865_MODE_720P_1280_720) ||
+ (mode->id == OV8865_MODE_UXGA_1600_1200)) {
+ format2 = 0x67;
+ y_inc_odd = 0x03;
+ } else if (mode->id == OV8865_MODE_SVGA_800_600) {
+ format2 = 0x6f;
+ y_inc_odd = 0x05;
+ } else {
+ format2 = 0x46;
+ y_inc_odd = 0x01;
+ }
+
+ ret = ov8865_write_reg(sensor, OV8865_FORMAT2_REG, format2);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_Y_INC_ODD_REG, y_inc_odd);
+ if (ret)
+ return ret;
+
+ if (mode->id == OV8865_MODE_SVGA_800_600)
+ y_inc_even = 0x03;
+ else
+ y_inc_even = 0x01;
+
+ ret = ov8865_write_reg(sensor, OV8865_Y_INC_EVEN_REG, y_inc_even);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_BLC_NUM_OPTION_REG,
+ blc_num_option);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_ZLINE_NUM_OPTION_REG,
+ zline_num_option);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_RGBC_REG, 0x18);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_AUTO_SIZE_CTRL0_REG, 0xff);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_BOUNDARY_PIX_NUM_REG,
+ boundary_pix_num);
+
+ return 0;
+}
+
+static int ov8865_get_hts(struct ov8865_dev *sensor)
+{
+ u16 hts;
+ int ret;
+
+ ret = ov8865_read_reg16(sensor, OV8865_HTS_REG, &hts);
+ if (ret)
+ return ret;
+ return hts;
+}
+
+static int ov8865_load_regs(struct ov8865_dev *sensor,
+ const struct ov8865_mode_info *mode)
+{
+ const struct reg_value *regs = mode->reg_data;
+ unsigned int i;
+ u32 delay_ms = 0;
+ u16 reg_addr;
+ u8 val;
+ int ret = 0;
+
+ for (i = 0; i < mode->reg_data_size; i++, regs++) {
+ delay_ms = regs->delay_ms;
+ reg_addr = regs->reg_addr;
+ val = regs->val;
+
+ ret = ov8865_write_reg(sensor, reg_addr, val);
+ if (ret)
+ return ret;
+
+ if (delay_ms)
+ usleep_range(1000 * delay_ms, 1000 * delay_ms + 100);
+ }
+
+ return 0;
+}
+
+static const struct ov8865_mode_info *
+ov8865_find_mode(struct ov8865_dev *sensor, enum ov8865_frame_rate fr,
+ int width, int height, bool nearest)
+{
+ const struct ov8865_mode_info *mode;
+
+ mode = v4l2_find_nearest_size(ov8865_mode_data,
+ ARRAY_SIZE(ov8865_mode_data),
+ hact, vact, width, height);
+
+ if (!mode || (!nearest && (mode->hact != width || mode->vact !=
+ height)))
+ return NULL;
+
+ /* Only SVGA can operate 90 fps. */
+ if (fr == OV8865_90_FPS && !(mode->hact == 800 && mode->vact == 600))
+ return NULL;
+
+ return mode;
+}
+
+static u64 ov8865_calc_pixel_rate(struct ov8865_dev *sensor)
+{
+ u64 rate;
+
+ rate = sensor->current_mode->vtot * sensor->current_mode->htot;
+ rate *= ov8865_framerates[sensor->current_fr];
+
+ return rate;
+}
+
+static int ov8865_set_mode_direct(struct ov8865_dev *sensor,
+ const struct ov8865_mode_info *mode)
+{
+ int ret;
+
+ if (!mode->reg_data)
+ return -EINVAL;
+
+ /*Write capture setting*/
+ ret = ov8865_load_regs(sensor, mode);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ov8865_set_black_level(struct ov8865_dev *sensor)
+{
+ const struct ov8865_mode_info *mode = sensor->current_mode;
+ int ret;
+ u8 blc_ctrl1, left_start_h, left_start_l, left_end_h,
+ left_end_l, right_start_h, right_start_l,
+ right_end_h, right_end_l, bkline_num, bkline_st,
+ zline_st, zline_num, blkline_st;
+
+ ret = ov8865_write_reg(sensor, OV8865_BLC_CTRL0_REG, 0xf1);
+ if (ret)
+ return ret;
+
+ if ((mode->id == OV8865_MODE_QUXGA_3264_2448) ||
+ (mode->id == OV8865_MODE_6M_3264_1836) ||
+ (mode->id == OV8865_MODE_1080P_1920_1080) ||
+ (mode->id == OV8865_MODE_VGA_640_480))
+ blc_ctrl1 = 0x04;
+ else
+ blc_ctrl1 = 0x14;
+
+ ret = ov8865_write_reg(sensor, OV8865_BLC_CTRL1_REG, blc_ctrl1);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_BLC_CTRL5_REG, 0x10);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_BLC_CTRLB_REG, 0x0c);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_BLC_CTRLD_REG, 0x10);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_BLC_CTRL1B_REG, 0x00);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_BLC_CTRL1D_REG, 0x00);
+ if (ret)
+ return ret;
+
+ if ((mode->id == OV8865_MODE_QUXGA_3264_2448) ||
+ (mode->id == OV8865_MODE_6M_3264_1836) ||
+ (mode->id == OV8865_MODE_1080P_1920_1080) ||
+ (mode->id == OV8865_MODE_VGA_640_480)) {
+ left_start_h = 0x02;
+ left_start_l = 0x40;
+ left_end_h = 0x03;
+ left_end_l = 0x3f;
+ right_start_h = 0x07;
+ right_start_l = 0xc0;
+ right_end_h = 0x08;
+ right_end_l = 0xbf;
+ } else {
+ left_start_h = 0x01;
+ left_start_l = 0x20;
+ left_end_h = 0x01;
+ left_end_l = 0x9f;
+ right_start_h = 0x03;
+ right_start_l = 0xe0;
+ right_end_h = 0x04;
+ right_end_l = 0x5f;
+ }
+
+ ret = ov8865_write_reg(sensor, OV8865_ANCHOR_LEFT_START_H_REG,
+ left_start_h);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_ANCHOR_LEFT_START_L_REG,
+ left_start_l);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_ANCHOR_LEFT_END_H_REG,
+ left_end_h);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_ANCHOR_LEFT_END_L_REG,
+ left_end_l);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_ANCHOR_RIGHT_START_H_REG,
+ right_start_h);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_ANCHOR_RIGHT_START_L_REG,
+ right_start_l);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_ANCHOR_RIGHT_END_H_REG,
+ right_end_h);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_ANCHOR_RIGHT_END_L_REG,
+ right_end_l);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_TOP_ZLINE_ST_REG, 0x00);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_TOP_ZLINE_NUM_REG, 0x02);
+ if (ret)
+ return ret;
+
+ if (mode->id == OV8865_MODE_SVGA_800_600) {
+ bkline_st = 0x02;
+ bkline_num = 0x02;
+ zline_st = 0x00;
+ zline_num = 0x00;
+ blkline_st = 0x04;
+ } else {
+ bkline_st = 0x04;
+ bkline_num = 0x04;
+ zline_st = 0x02;
+ zline_num = 0x02;
+ blkline_st = 0x08;
+ }
+ ret = ov8865_write_reg(sensor, OV8865_TOP_BKLINE_ST_REG, bkline_st);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_TOP_BKLINE_NUM_REG, bkline_num);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_BOT_ZLINE_ST_REG, zline_st);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_BOT_ZLINE_NUM_REG, zline_num);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_BOT_BLKLINE_ST_REG, blkline_st);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_BOT_BLKLINE_NUM_REG, 0x02);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_BLC_CTRL1F_REG, 0x00);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_BLC_OFFSET_LIMIT_REG, 0x3f);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ov8865_set_pclk(struct ov8865_dev *sensor)
+{
+ int ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_PLL_CTRL2_REG, 0x1e);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_PLL_CTRL3_REG, 0x00);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_PLL_CTRL4_REG, 0x03);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_CLOCK_SEL_REG, 0x93);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ov8865_get_pclk(struct ov8865_dev *sensor)
+{
+ int ret;
+ u8 pll1_mult, m_div, mipi_div_r, mipi_div, pclk_div_r, pclk_div;
+ int ref_clk = OV8865_XCLK_FREQ / 1000000;
+
+ ret = ov8865_read_reg(sensor, OV8865_PLL_CTRL2_REG, &pll1_mult);
+ if (ret)
+ return ret;
+
+ ret = ov8865_read_reg(sensor, OV8865_PLL_CTRL3_REG, &m_div);
+ if (ret)
+ return ret;
+
+ m_div = m_div & 0x07;
+ ret = ov8865_read_reg(sensor, OV8865_PLL_CTRL4_REG, &mipi_div_r);
+ if (ret)
+ return ret;
+
+ mipi_div_r = mipi_div_r & 0x03;
+
+ if (mipi_div_r == 0x00)
+ mipi_div = 4;
+
+ if (mipi_div_r == 0x01)
+ mipi_div = 5;
+
+ if (mipi_div_r == 0x02)
+ mipi_div = 6;
+
+ if (mipi_div_r == 0x03)
+ mipi_div = 8;
+
+ ret = ov8865_read_reg(sensor, OV8865_CLOCK_SEL_REG, &pclk_div_r);
+ if (ret)
+ return ret;
+
+ pclk_div_r = (pclk_div_r & 0x08) >> 3;
+
+ if (pclk_div_r == 0)
+ pclk_div = 1;
+
+ if (pclk_div_r == 1)
+ pclk_div = 2;
+
+ return ref_clk * pll1_mult / (1 + m_div) / mipi_div / pclk_div;
+}
+
+static int ov8865_set_sclk(struct ov8865_dev *sensor)
+{
+ const struct ov8865_mode_info *mode = sensor->current_mode;
+ int ret;
+ u8 val;
+
+ if ((mode->id == OV8865_MODE_UXGA_1600_1200) ||
+ (mode->id == OV8865_MODE_720P_1280_720) ||
+ (mode->id == OV8865_MODE_SVGA_800_600))
+ val = 0x09;
+ else
+ val = 0x04;
+
+ ret = ov8865_write_reg(sensor, OV8865_PLL_CTRLF_REG, val);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_PLL_CTRL12_REG, 0x01);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_PLL_CTRL1E_REG, 0x0c);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_PLL_CTRLE_REG, 0x00);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ov8865_set_virtual_channel(struct ov8865_dev *sensor, u8 channel)
+{
+ u8 channel_id;
+ int ret;
+
+ ret = ov8865_read_reg(sensor, OV8865_MIPI_CTRL13_REG, &channel_id);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_MIPI_CTRL13_REG, channel_id |
+ channel);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ov8865_set_mode(struct ov8865_dev *sensor)
+{
+ const struct ov8865_mode_info *mode = sensor->current_mode;
+ int ret;
+
+ ret = ov8865_set_pclk(sensor);
+ if (ret < 0)
+ return ret;
+
+ ret = ov8865_set_sclk(sensor);
+ if (ret < 0)
+ return ret;
+
+ ret = ov8865_set_black_level(sensor);
+ if (ret)
+ return ret;
+
+ ret = ov8865_set_timings(sensor, mode);
+ if (ret)
+ return ret;
+
+ ret = ov8865_set_mode_direct(sensor, mode);
+ if (ret < 0)
+ return ret;
+
+ ret = ov8865_set_virtual_channel(sensor, 0);
+ if (ret < 0)
+ return ret;
+
+ sensor->last_mode = mode;
+ return 0;
+}
+
+static int ov8865_restore_mode(struct ov8865_dev *sensor)
+{
+ int ret;
+
+ ret = ov8865_load_regs(sensor, &ov8865_mode_init_data);
+ if (ret)
+ return ret;
+
+ sensor->last_mode = &ov8865_mode_init_data;
+
+ ret = ov8865_set_mode(sensor);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void ov8865_power(struct ov8865_dev *sensor, bool enable)
+{
+ gpiod_set_value_cansleep(sensor->pwdn_gpio, enable ? 0 : 1);
+}
+
+static void ov8865_reset(struct ov8865_dev *sensor, bool enable)
+{
+ gpiod_set_value_cansleep(sensor->reset_gpio, enable ? 0 : 1);
+}
+
+static int ov8865_set_power_on(struct ov8865_dev *sensor)
+{
+ struct i2c_client *client = sensor->i2c_client;
+ int ret = 0;
+
+ ov8865_power(sensor, false);
+ ov8865_reset(sensor, false);
+
+ ret = clk_prepare_enable(sensor->xclk);
+ if (ret) {
+ dev_err(&client->dev, "%s: failed to enable clock\n",
+ __func__);
+ return ret;
+ }
+
+ ov8865_power(sensor, true);
+
+ ret = regulator_bulk_enable(OV8865_NUM_SUPPLIES, sensor->supplies);
+ if (ret) {
+ dev_err(&client->dev, "%s: failed to enable regulators\n",
+ __func__);
+ goto err_power_off;
+ }
+
+ ov8865_reset(sensor, true);
+ usleep_range(10000, 12000);
+
+ return 0;
+
+err_power_off:
+ ov8865_power(sensor, false);
+ clk_disable_unprepare(sensor->xclk);
+ return ret;
+}
+
+static void ov8865_set_power_off(struct ov8865_dev *sensor)
+{
+ ov8865_power(sensor, false);
+ regulator_bulk_disable(OV8865_NUM_SUPPLIES, sensor->supplies);
+ clk_disable_unprepare(sensor->xclk);
+}
+
+static int ov8865_set_power(struct ov8865_dev *sensor, bool on)
+{
+ int ret = 0;
+
+ if (on) {
+ ret = ov8865_set_power_on(sensor);
+ if (ret)
+ return ret;
+
+ ret = ov8865_restore_mode(sensor);
+ if (ret)
+ goto err_power_off;
+ } else {
+ ov8865_set_power_off(sensor);
+ }
+
+ return 0;
+
+err_power_off:
+ ov8865_set_power_off(sensor);
+ return ret;
+}
+
+static int ov8865_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct ov8865_dev *sensor = to_ov8865_dev(sd);
+ int ret = 0;
+
+ mutex_lock(&sensor->lock);
+ if (sensor->power_count == !on) {
+ ret = ov8865_set_power(sensor, !!on);
+ if (ret)
+ goto out;
+ }
+
+ /* Update the power count. */
+ sensor->power_count += on ? 1 : -1;
+ WARN_ON(sensor->power_count < 0);
+out:
+ mutex_unlock(&sensor->lock);
+
+ if (on && !ret && sensor->power_count == 1) {
+ /* Initialize the hardware. */
+ ret = v4l2_ctrl_handler_setup(&sensor->ctrls.handler);
+ }
+
+ return ret;
+}
+
+static int ov8865_try_frame_interval(struct ov8865_dev *sensor,
+ struct v4l2_fract *fi,
+ u32 width, u32 height)
+{
+ const struct ov8865_mode_info *mode;
+ enum ov8865_frame_rate rate = OV8865_30_FPS;
+ int minfps, maxfps, best_fps, fps;
+ int i;
+
+ minfps = ov8865_framerates[OV8865_30_FPS];
+ maxfps = ov8865_framerates[OV8865_90_FPS];
+
+ if (fi->numerator == 0) {
+ fi->denominator = maxfps;
+ fi->numerator = 1;
+ rate = OV8865_90_FPS;
+ goto find_mode;
+ }
+
+ fps = clamp_val(DIV_ROUND_CLOSEST(fi->denominator, fi->numerator),
+ minfps, maxfps);
+
+ best_fps = minfps;
+ for (i = 0; i < ARRAY_SIZE(ov8865_framerates); i++) {
+ int curr_fps = ov8865_framerates[i];
+
+ if (abs(curr_fps - fps) < abs(best_fps - fps)) {
+ best_fps = curr_fps;
+ rate = i;
+ }
+ }
+
+ fi->numerator = 1;
+ fi->denominator = best_fps;
+
+find_mode:
+ mode = ov8865_find_mode(sensor, rate, width, height, false);
+
+ return mode ? rate : -EINVAL;
+}
+
+static int ov8865_try_fmt_internal(struct v4l2_subdev *sd,
+ struct v4l2_mbus_framefmt *fmt,
+ enum ov8865_frame_rate fr,
+ const struct ov8865_mode_info **new_mode)
+{
+ struct ov8865_dev *sensor = to_ov8865_dev(sd);
+ const struct ov8865_mode_info *mode;
+ int i;
+
+ mode = ov8865_find_mode(sensor, fr, fmt->width, fmt->height, true);
+ if (!mode)
+ return -EINVAL;
+
+ fmt->width = mode->hact;
+ fmt->height = mode->vact;
+
+ if (new_mode)
+ *new_mode = mode;
+
+ for (i = 0; i < ARRAY_SIZE(ov8865_formats); i++)
+ if (ov8865_formats[i].code == fmt->code)
+ break;
+
+ if (i == ARRAY_SIZE(ov8865_formats))
+ i = 0;
+
+ fmt->code = ov8865_formats[i].code;
+ fmt->colorspace = ov8865_formats[i].colorspace;
+ fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
+ fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+ fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
+
+ return 0;
+}
+
+static int ov8865_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct ov8865_dev *sensor = to_ov8865_dev(sd);
+ struct v4l2_mbus_framefmt *fmt;
+
+ if (format->pad != 0)
+ return -EINVAL;
+
+ mutex_lock(&sensor->lock);
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+ fmt = v4l2_subdev_get_try_format(&sensor->sd, cfg,
+ format->pad);
+ else
+ fmt = &sensor->fmt;
+
+ if (fmt)
+ format->format = *fmt;
+
+ mutex_unlock(&sensor->lock);
+
+ return fmt ? 0 : -EINVAL;
+}
+
+static int ov8865_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct ov8865_dev *sensor = to_ov8865_dev(sd);
+ const struct ov8865_mode_info *new_mode;
+ struct v4l2_mbus_framefmt *mbus_fmt = &format->format;
+ struct v4l2_mbus_framefmt *fmt;
+ int ret;
+
+ if (format->pad != 0)
+ return -EINVAL;
+
+ mutex_lock(&sensor->lock);
+
+ if (sensor->streaming) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ret = ov8865_try_fmt_internal(sd, mbus_fmt, sensor->current_fr,
+ &new_mode);
+ if (ret)
+ goto out;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+ fmt = v4l2_subdev_get_try_format(sd, cfg, 0);
+ else
+ fmt = &sensor->fmt;
+
+ if (fmt)
+ *fmt = *mbus_fmt;
+ else
+ ret = -EINVAL;
+
+ if (new_mode != sensor->current_mode)
+ sensor->current_mode = new_mode;
+
+ __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate,
+ ov8865_calc_pixel_rate(sensor));
+
+out:
+ mutex_unlock(&sensor->lock);
+ return ret;
+}
+
+static int ov8865_set_ctrl_hflip(struct ov8865_dev *sensor, int value)
+{
+ return ov8865_mod_reg(sensor, OV8865_FORMAT2_REG,
+ OV8865_FORMAT2_MIRROR_DIG |
+ OV8865_FORMAT2_MIRROR_ARR,
+ (!(value ^ sensor->upside_down)) ?
+ (OV8865_FORMAT2_MIRROR_DIG |
+ OV8865_FORMAT2_MIRROR_ARR) : 0);
+}
+
+static int ov8865_set_ctrl_vflip(struct ov8865_dev *sensor, int value)
+{
+ return ov8865_mod_reg(sensor, OV8865_FORMAT1_REG,
+ OV8865_FORMAT1_MIRROR_DIG |
+ OV8865_FORMAT1_MIRROR_ARR,
+ (value ^ sensor->upside_down) ?
+ (OV8865_FORMAT2_MIRROR_DIG |
+ OV8865_FORMAT2_MIRROR_ARR) : 0);
+}
+
+static int ov8865_get_exposure(struct ov8865_dev *sensor)
+{
+ int exp, ret, pclk, hts, line_time;
+ u8 temp;
+
+ ret = ov8865_read_reg(sensor, OV8865_EXPOSURE_CTRL_HH_REG, &temp);
+ if (ret)
+ return ret;
+ exp = ((int)temp & 0x0f) << 16;
+
+ ret = ov8865_read_reg(sensor, OV8865_EXPOSURE_CTRL_H_REG, &temp);
+ if (ret)
+ return ret;
+ exp |= ((int)temp << 8);
+
+ ret = ov8865_read_reg(sensor, OV8865_EXPOSURE_CTRL_L_REG, &temp);
+ if (ret)
+ return ret;
+ exp |= (int)temp;
+
+ ret = ov8865_get_pclk(sensor);
+ if (ret <= 0)
+ return ret;
+
+ pclk = ret;
+
+ ret = ov8865_get_hts(sensor);
+ if (ret <= 0)
+ return ret;
+
+ hts = ret;
+
+ line_time = hts / pclk;
+
+ /* The low 4 bits of exposure are the fractional part. And the unit is
+ * 1/16 of a line lecture time. The pclk and HTS are used to calculate
+ * this time. For V4L2, the value 1 of exposure stands for 100us of
+ * capture.
+ */
+ return (exp >> 4) * line_time / 16 / 100;
+}
+
+static int ov8865_get_gain(struct ov8865_dev *sensor)
+{
+ u16 gain;
+ int ret;
+
+ /* Linear gain. */
+ ret = ov8865_read_reg16(sensor, OV8865_GAIN_CTRL_H_REG, &gain);
+ if (ret)
+ return ret;
+
+ return gain & 0x1fff;
+}
+
+static int ov8865_set_ctrl_exp(struct ov8865_dev *sensor)
+{
+ struct ov8865_ctrls *ctrls = &sensor->ctrls;
+ int ret = 0, hts, pclk, line_time;
+ int exposure = ctrls->exposure->val;
+ /* The low 4 bits of exposure are the fractional part. And the unit is
+ * 1/16 of a line lecture time. The pclk and HTS are used to calculate
+ * this time. For V4L2, the value 1 of exposure stands for 100us of
+ * capture.
+ */
+
+ ret = ov8865_get_pclk(sensor);
+ if (ret <= 0)
+ return ret;
+ pclk = ret;
+
+ ret = ov8865_get_hts(sensor);
+ if (ret <= 0)
+ return ret;
+ hts = ret;
+
+ line_time = hts / pclk;
+
+ exposure = ctrls->exposure->val * 16 / line_time * 100;
+ exposure = (exposure << 4);
+
+ if (ctrls->exposure->is_new) {
+ ret = ov8865_write_reg(sensor, OV8865_EXPOSURE_CTRL_L_REG,
+ exposure & 0xff);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_EXPOSURE_CTRL_H_REG,
+ (exposure >> 8) & 0xff);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_EXPOSURE_CTRL_HH_REG,
+ (exposure >> 16) & 0x0f);
+ }
+
+ return ret;
+}
+
+static int ov8865_set_ctrl_gain(struct ov8865_dev *sensor)
+{
+ struct ov8865_ctrls *ctrls = &sensor->ctrls;
+ int ret = 0;
+ int val = ctrls->gain->val;
+
+ /* Linear gain. */
+ if (ctrls->gain->is_new)
+ ret = ov8865_write_reg16(sensor, OV8865_GAIN_CTRL_H_REG,
+ (u16)val & 0x1fff);
+ return ret;
+}
+
+static int ov8865_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
+ struct ov8865_dev *sensor = to_ov8865_dev(sd);
+ int val;
+
+ switch (ctrl->id) {
+ case V4L2_CID_GAIN:
+ val = ov8865_get_gain(sensor);
+ if (val < 0)
+ return val;
+ sensor->ctrls.gain->val = val;
+ break;
+ case V4L2_CID_EXPOSURE:
+ val = ov8865_get_exposure(sensor);
+ if (val < 0)
+ return val;
+ sensor->ctrls.exposure->val = val;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
+ struct ov8865_dev *sensor = to_ov8865_dev(sd);
+ int ret;
+
+ if (sensor->power_count == 0)
+ return 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_GAIN:
+ ret = ov8865_set_ctrl_gain(sensor);
+ break;
+ case V4L2_CID_EXPOSURE:
+ ret = ov8865_set_ctrl_exp(sensor);
+ break;
+ case V4L2_CID_HFLIP:
+ ret = ov8865_set_ctrl_hflip(sensor, ctrl->val);
+ break;
+ case V4L2_CID_VFLIP:
+ ret = ov8865_set_ctrl_vflip(sensor, ctrl->val);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops ov8865_ctrl_ops = {
+ .g_volatile_ctrl = ov8865_g_volatile_ctrl,
+ .s_ctrl = ov8865_s_ctrl,
+};
+
+static int ov8865_init_controls(struct ov8865_dev *sensor)
+{
+ const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops;
+ struct ov8865_ctrls *ctrls = &sensor->ctrls;
+ struct v4l2_ctrl_handler *hdl = &ctrls->handler;
+ int ret;
+
+ v4l2_ctrl_handler_init(hdl, 32);
+ hdl->lock = &sensor->lock;
+ ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE,
+ 0, INT_MAX, 1,
+ ov8865_calc_pixel_rate(sensor));
+ ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 1,
+ 2000, 1, 1);
+ ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, 1*16, 64*16 - 1,
+ 1, 1*16);
+ ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
+ ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, 0);
+ if (hdl->error) {
+ ret = hdl->error;
+ goto err_free_ctrls;
+ }
+
+ ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ sensor->sd.ctrl_handler = hdl;
+
+ return 0;
+
+err_free_ctrls:
+ v4l2_ctrl_handler_free(hdl);
+ return ret;
+}
+
+
+static int ov8865_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+
+ if (fse->pad != 0 || fse->index >= OV8865_NUM_MODES)
+ return -EINVAL;
+
+ fse->code = MEDIA_BUS_FMT_SBGGR10_1X10;
+ fse->min_width = ov8865_mode_data[fse->index].hact;
+ fse->max_width = fse->min_width;
+ fse->min_height = ov8865_mode_data[fse->index].vact;
+ fse->max_height = fse->min_height;
+
+ return 0;
+}
+
+static int ov8865_enum_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_interval_enum
+ *fie)
+{
+ struct ov8865_dev *sensor = to_ov8865_dev(sd);
+ struct v4l2_fract tpf;
+ int ret;
+
+ if (fie->pad != 0 || fie->index >= OV8865_NUM_FRAMERATES)
+ return -EINVAL;
+
+ tpf.numerator = 1;
+ tpf.denominator = ov8865_framerates[fie->index];
+
+ ret = ov8865_try_frame_interval(sensor, &tpf,
+ fie->width, fie->height);
+ if (ret < 0)
+ return -EINVAL;
+
+ fie->interval = tpf;
+
+ return 0;
+}
+
+static int ov8865_g_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_frame_interval *fi)
+{
+ struct ov8865_dev *sensor = to_ov8865_dev(sd);
+
+ mutex_lock(&sensor->lock);
+ fi->interval = sensor->frame_interval;
+ mutex_unlock(&sensor->lock);
+
+ return 0;
+}
+
+static int ov8865_s_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_frame_interval *fi)
+{
+ struct ov8865_dev *sensor = to_ov8865_dev(sd);
+ const struct ov8865_mode_info *mode;
+ int frame_rate, ret = 0;
+
+ if (fi->pad != 0)
+ return -EINVAL;
+
+ mutex_lock(&sensor->lock);
+
+ if (sensor->streaming) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ mode = sensor->current_mode;
+
+ frame_rate = ov8865_try_frame_interval(sensor, &fi->interval,
+ mode->hact, mode->vact);
+ if (frame_rate < 0) {
+ fi->interval = sensor->frame_interval;
+ goto out;
+ }
+
+ mode = ov8865_find_mode(sensor, frame_rate, mode->hact,
+ mode->vact, true);
+ if (!mode) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (mode != sensor->current_mode ||
+ frame_rate != sensor->current_fr) {
+ sensor->current_fr = frame_rate;
+ sensor->frame_interval = fi->interval;
+ sensor->current_mode = mode;
+
+ __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate,
+ ov8865_calc_pixel_rate(sensor));
+ }
+
+out:
+ mutex_unlock(&sensor->lock);
+ return ret;
+}
+
+static int ov8865_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->pad != 0 || code->index >= ARRAY_SIZE(ov8865_formats))
+ return -EINVAL;
+
+ code->code = ov8865_formats[code->index].code;
+
+ return 0;
+}
+
+static int ov8865_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct ov8865_dev *sensor = to_ov8865_dev(sd);
+ int ret = 0;
+
+ mutex_lock(&sensor->lock);
+
+ if (sensor->streaming == !enable) {
+ if (enable && ret)
+ goto out;
+
+ ret = ov8865_write_reg(sensor, OV8865_SW_STANDBY_REG, enable ?
+ OV8865_SW_STANDBY_STANDBY_N : 0x00);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write_reg(sensor, OV8865_MIPI_CTRL_REG,
+ enable ? 0x72 : 0x62);
+ if (ret)
+ goto out;
+
+ if (!ret)
+ sensor->streaming = enable;
+ }
+
+out:
+ mutex_unlock(&sensor->lock);
+ return ret;
+}
+
+static const struct v4l2_subdev_core_ops ov8865_core_ops = {
+ .s_power = ov8865_s_power,
+ .log_status = v4l2_ctrl_subdev_log_status,
+ .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_video_ops ov8865_video_ops = {
+ .g_frame_interval = ov8865_g_frame_interval,
+ .s_frame_interval = ov8865_s_frame_interval,
+ .s_stream = ov8865_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops ov8865_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 = {
+ .core = &ov8865_core_ops,
+ .video = &ov8865_video_ops,
+ .pad = &ov8865_pad_ops,
+};
+
+static int ov8865_get_regulators(struct ov8865_dev *sensor)
+{
+ int i;
+
+ for (i = 0; i < OV8865_NUM_SUPPLIES; i++)
+ sensor->supplies[i].supply = ov8865_supply_names[i];
+
+ return devm_regulator_bulk_get(&sensor->i2c_client->dev,
+ OV8865_NUM_SUPPLIES,
+ sensor->supplies);
+}
+
+static int ov8865_check_chip_id(struct ov8865_dev *sensor)
+{
+ struct i2c_client *client = sensor->i2c_client;
+ int ret = 0;
+ u8 chip_id_0, chip_id_1, chip_id_2;
+ u32 chip_id = 0x000000;
+
+ ret = ov8865_set_power_on(sensor);
+ if (ret)
+ return ret;
+
+ ret = ov8865_read_reg(sensor, OV8865_CHIP_ID_REG, &chip_id_0);
+ if (ret) {
+ dev_err(&client->dev, "%s: failed to reach chip identifier\n",
+ __func__);
+ goto power_off;
+ }
+
+ ret = ov8865_read_reg(sensor, OV8865_CHIP_ID_REG + 1, &chip_id_1);
+ if (ret) {
+ dev_err(&client->dev, "%s: failed to reach chip identifier\n",
+ __func__);
+ goto power_off;
+ }
+
+ ret = ov8865_read_reg(sensor, OV8865_CHIP_ID_REG + 2, &chip_id_2);
+ if (ret) {
+ dev_err(&client->dev, "%s: failed to reach chip identifier\n",
+ __func__);
+ goto power_off;
+ }
+
+ chip_id = ((u32)chip_id_0 << 16) | ((u32)chip_id_1 << 8) |
+ ((u32)chip_id_2);
+
+ if (chip_id != OV8865_CHIP_ID) {
+ dev_err(&client->dev, "%s: wrong chip identifier, expected 0x008865, got 0x%x\n", __func__, chip_id);
+ ret = -ENXIO;
+ }
+
+power_off:
+ ov8865_set_power_off(sensor);
+ return ret;
+}
+
+static int ov8865_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct fwnode_handle *endpoint;
+ struct ov8865_dev *sensor;
+ const struct ov8865_mode_info *default_mode;
+ struct v4l2_mbus_framefmt *fmt;
+ u32 rotation;
+ int ret = 0;
+
+ sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
+ if (!sensor)
+ return -ENOMEM;
+
+ sensor->i2c_client = client;
+
+ /*
+ * Default init sequence initialize sensor to
+ * RAW SBGGR10 3264x1836@30fps.
+ */
+
+ default_mode = &ov8865_mode_data[OV8865_MODE_QUXGA_3264_2448];
+
+ fmt = &sensor->fmt;
+ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10;
+ fmt->colorspace = V4L2_COLORSPACE_RAW;
+ fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
+ fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+ fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
+ fmt->width = default_mode->hact;
+ fmt->height = default_mode->vact;
+ fmt->field = V4L2_FIELD_NONE;
+ sensor->frame_interval.numerator = 1;
+ sensor->frame_interval.denominator = ov8865_framerates[OV8865_30_FPS];
+ sensor->current_fr = OV8865_30_FPS;
+ sensor->current_mode = default_mode;
+ sensor->last_mode = default_mode;
+
+ /* Optional indication of physical rotation of sensor. */
+ ret = fwnode_property_read_u32(dev_fwnode(&client->dev), "rotation",
+ &rotation);
+ if (!ret) {
+ switch (rotation) {
+ case 180:
+ sensor->upside_down = true;
+ /* fall through */
+ case 0:
+ break;
+ default:
+ dev_warn(dev, "%u degrees rotation is not supported, ignoring..\n",
+ rotation);
+ }
+ }
+
+ endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev),
+ NULL);
+ if (!endpoint) {
+ dev_err(dev, "endpoint node not found\n");
+ return -EINVAL;
+ }
+
+ ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep);
+ fwnode_handle_put(endpoint);
+ if (ret) {
+ dev_err(dev, "Could not parse endpoint\n");
+ return ret;
+ }
+
+ /* Get system clock (xclk). */
+ sensor->xclk = devm_clk_get(dev, "xclk");
+ if (IS_ERR(sensor->xclk)) {
+ dev_err(dev, "failed to get xclk\n");
+ return PTR_ERR(sensor->xclk);
+ }
+
+ ret = clk_set_rate(sensor->xclk, OV8865_XCLK_FREQ);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set xclk rate (24MHz)\n");
+ return ret;
+ }
+
+ /* Request optional power down pin. */
+ sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(sensor->pwdn_gpio))
+ return PTR_ERR(sensor->pwdn_gpio);
+
+ /* Request optional reset pin. */
+ sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(sensor->reset_gpio))
+ return PTR_ERR(sensor->reset_gpio);
+
+ v4l2_i2c_subdev_init(&sensor->sd, client, &ov8865_subdev_ops);
+ sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+ V4L2_SUBDEV_FL_HAS_EVENTS;
+ sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
+ sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+ ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
+ if (ret)
+ return ret;
+
+ ret = ov8865_get_regulators(sensor);
+ if (ret)
+ return ret;
+
+ mutex_init(&sensor->lock);
+
+ ret = ov8865_check_chip_id(sensor);
+ if (ret)
+ goto err_entity_cleanup;
+
+ ret = ov8865_init_controls(sensor);
+ if (ret)
+ goto err_entity_cleanup;
+
+ ret = v4l2_async_register_subdev(&sensor->sd);
+ if (ret)
+ goto err_free_ctrls;
+
+ return 0;
+
+err_free_ctrls:
+ v4l2_ctrl_handler_free(&sensor->ctrls.handler);
+err_entity_cleanup:
+ mutex_destroy(&sensor->lock);
+ media_entity_cleanup(&sensor->sd.entity);
+ return ret;
+}
+
+
+static int ov8865_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
+ struct ov8865_dev *sensor = to_ov8865_dev(sd);
+
+ v4l2_async_unregister_subdev(&sensor->sd);
+ mutex_destroy(&sensor->lock);
+ media_entity_cleanup(&sensor->sd.entity);
+ v4l2_ctrl_handler_free(&sensor->ctrls.handler);
+
+ return 0;
+}
+
+static const struct i2c_device_id ov8865_id[] = {
+ { "ov8865", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, ov8865_id);
+
+static const struct of_device_id ov8865_dt_ids[] = {
+ { .compatible = "ovti,ov8865" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ov8865_dt_ids);
+
+static struct i2c_driver ov8865_i2c_driver = {
+ .driver = {
+ .name = "ov8865",
+ .of_match_table = ov8865_dt_ids,
+ },
+ .id_table = ov8865_id,
+ .probe_new = ov8865_probe,
+ .remove = ov8865_remove,
+};
+
+module_i2c_driver(ov8865_i2c_driver);
+
+MODULE_DESCRIPTION("OV8865 MIPI Camera Subdev Driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kévin L'hôpital <kevin.lhopital@bootlin.com>");
--
2.17.1
\
 
 \ /
  Last update: 2020-08-28 15:21    [W:0.117 / U:0.840 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site