Rockchip平台之RK3566/3568 OS02N10

前言

        那段时间在做项目时,由于需要考虑降成本的问题,不仅要对400w摄像头sensor做降本兼容,同时以往机子使用的200w摄像头sensor也需要做同样的降本兼容。由于兼容的方法类似,博主直接在上一篇的基础上改改就成了新的一篇博客了。本文主要200W摄像头sensor OS02N10的兼容和适配。

RK3566 概述

        RK3566 采用四核 ARM Cortex-A55 CPU,主频高达 1.8GHz,具备较强的计算能力。同时,芯片集成了 ARM Mali-G52 GPU,支持 OpenGL ES 1.1/2.0/3.2、OpenCL 2.0 和 Vulkan 1.1,能够处理复杂的图形渲染任务。芯片支持多种视频编解码格式,包括 H.264、H.265、VP9 等,最高支持 4K@60fps 的视频解码和 1080p@60fps 的视频编码。

        此外,RK3566 还集成了 NPU(神经网络处理单元),支持 AI 计算,适用于人脸识别、图像处理等 AI 应用。RK3566 提供了丰富的接口,包括 USB 3.0、PCIe 2.1、SATA 3.0、千兆以太网等,支持多种外设连接。芯片还支持 DDR4/LPDDR4/LPDDR4X 内存,最大支持 8GB,能够满足高性能应用的需求。

OS02N10概述

        OS02N10是一款高性能图像传感器芯片,主要用于高清视频捕捉和图像处理应用。其设计适用于安防监控、智能家居、自动驾驶等领域,具备低功耗和高灵敏度的特性。

技术参数
  • 分辨率:支持1920×1080(1080P)高清输出,帧率可达60fps。

  • 像素尺寸:2.9μm×2.9μm,采用背照式(BSI)技术提升低光性能。

  • 动态范围:支持高达120dB的宽动态范围(WDR),适合高对比度场景。

  • 接口:支持MIPI CSI-2接口,数据传输速率高达1.5Gbps/lane。

核心特性

    低光照性能:通过BSI技术和先进的噪声抑制算法,在低光照环境下仍能输出清晰的图像。

    高集成度:内置自动曝光(AE)、自动白平衡(AWB)和自动对焦(AF)功能。

    功耗优化:工作功耗低于300mW,待机模式下功耗可降至10mW以下,适合电池供电设备。

应用场景
  • 安防监控:用于智能摄像头,支持夜间监控和人脸识别。

  • 车载系统:作为ADAS(高级驾驶辅助系统)的视觉传感器。

  • 消费电子:无人机、AR/VR设备的图像采集模块

硬件连接

        图1为400w像素摄像头os02n10 sensor的原理图,主要需要的引脚包括MIPI_CSI、MIPI_MCLK、IIC的SDA、SCL和SID,其中SID引脚主要用于选择OS02N10这个设备的IIC从机地址,以便成功挂载到rk3566的IIC总线上。这里一时半会找不到os02n10的原理图了,先用os04l10的原理图占坑,其实都是一样的这两个sensor的原理图。

图1.os02n10原理图

        图2为200w像素摄像头os02n10 sensor与rk3566主板进行连接的接口原理图,这两个部分通过24针接线进行连接。

图2.接口原理图

兼容实现

        摄像头sensor os02n10的兼容需要特别注意驱动文件+xml文件+json文件这三个部分,缺一不可,只有保证这三个部分都正确才能正常出图像,有图像之后才能进行下一步的ISP图像调优操作。博主这里回顾之前的兼容适配过程,把整个兼容过程分为下面几个部分进行阐述。

DTS设备树文件配置

        设备树(DTS)中对OS02N10摄像头模组的配置描述,定义了其I2C地址(0x3C)、时钟源、电源域、GPIO控制(复位、使能、电源等)、数据通道(MIPI lanes 1和2)以及模组属性(前置摄像头、镜头型号等)。硬件连接通过port节点与MIPI接口绑定。这里博主附上该设备数节点的定义以供参考。这里重点说明一下IIC地址的定义,可以看到原理图上写的地址与这里的地址完全没办法对上,这是因为rk3566平台的iic地址需要使用7位的形式,而不是8位,所以需要进行一下移位操作得到正确的IIC地址。这里注意一个问题,你会发现两个模组共用了一个iic地址,这在原则上是不允许的,但由于我们产品上只有单目摄像头,所以并不会冲突,如果是双目摄像头就会冲突,所以博主还是希望你能使用不同iic地址以作区分和兼容。

        这里再说明一点,就是在调试这个IIC地址的时候可能会用到i2ctools工具,这里博主使用32bit的工具,将对应工具通过u盘传入终端的/bin目录下,修改权限之后可以通过工具查看挂载的iic设备,并以此来进行相关调试和验证,验证相关驱动首先得确保iic从机地址正确并能被iic总线识别,然后再进行iic信号检测拉波形等后续操作。工具具体的使用方法参考https://blog.csdn.net/qq_46422460/article/details/141679812这个博客。

		os02n10: os02n10@3C {
		compatible = "ovti,os02n10";
		status = "okay";
		reg = <0x3C>;
		//clocks = <&cru CLK_CIF_OUT>;
		clocks = <&cru CLK_CAM0_OUT>;
		clock-names = "xvclk";
		power-domains = <&power RK3568_PD_VI>;
		pinctrl-names = "default";
		//pinctrl-0 = <&cif_clk>;
		pinctrl-0 = <&cam_clkout0>;
		avdd-supply = <&vcc_camera>;
		dovdd-supply = <&vcc_camera>;
		dvdd-supply = <&vcc_camera>;
		//reset pin control by hardware,used this pin switch to mipi input
		//0->FRONT camera, 1->REAR camera
		reset-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_HIGH>;
		pwdn-gpios = <&gpio3 RK_PD0 GPIO_ACTIVE_LOW>;
		power-gpios = <&gpio0 RK_PB0 GPIO_ACTIVE_HIGH>;
		lcd-gpios = <&gpio0 RK_PC7 GPIO_ACTIVE_HIGH>;
		rockchip,camera-module-index = <0>;
		rockchip,camera-module-facing = "front";
		rockchip,camera-module-name = "OS02N10";
		rockchip,camera-module-lens-name = "HX2716X";
		port {
			os02n10_out: endpoint {
				remote-endpoint = <&mipi_in_os02n10>;
				data-lanes = <1 2>;
			};
		};

        设备树节点 mipi_in_os02n10 描述 MIPI CSI-2 接口接收端与摄像头传感器 os02n10 的连接配置。关键字段 reg = <3> 表示该接收端节点的寄存器地址为 3,用于区分同一硬件模块中的多个接口实例。remote-endpoint = <&os02n10_out> 指向发送端节点 os02n10_out,建立双向数据链路,确保数据通道的正确映射。data-lanes = <1 2> 定义使用的 MIPI 数据通道为 1 和 2,传感器采用双通道传输数据。将传感器端口绑定到MIPI CSI-2接收器。

			mipi_in_os02n10: endpoint@4 {
				reg = <4>;
				remote-endpoint = <&os02n10_out>;
				data-lanes = <1 2>;
			};
驱动文件

        驱动文件的修改需对照datasheet OS02N10_CSP_DS_1.22 .pdf文档进行,摄像头sensor具体的指导文档和数据手册之类的需要找对应的厂商获取,兼容这个模组最重要的参考datasheet查看相关寄存器地址以及性能之类的。具体的驱动代码实现博主以及附上,其中的代码主要实现的是一些寄存器的读写和初始化操作,绑定设备树节点这些的操作。这里要非常注意摄像头初始化数组和对应寄存器的地址,其他的操作每个sensor其实都大差不差,尤其是(初始化数组最后空寄存器的使用)这个很重要,博主在这里就栽了,直接使用厂商给过来的初始化数组,一直以为这个就是没问题的也没去在意这里,结果后面发现少了空寄存器的结束条件一直在写寄存器,摄像头画面一直拉不起来,通常是由sensor厂商提供这部分初始化数组。

// SPDX-License-Identifier: GPL-2.0
/*
 * os02n10 driver by xzh
 *
 * Copyright (C) 2025 Newland Communication Co., Ltd.
 *
 * V0.0X01.0X01 first version.
 *
 * Pay special attention to adding {REG_NULL, 0x00} at the end of the register initialization array.
 */


#include <linux/clk.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/rk-camera-module.h>
#include <media/media-entity.h>
#include <media/v4l2-async.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h>
#include <linux/pinctrl/consumer.h>
#include <linux/rk-preisp.h>
#include "../platform/rockchip/isp/rkisp_tb_helper.h"

#define DRIVER_VERSION			KERNEL_VERSION(0, 0x01, 0x01)

#ifndef V4L2_CID_DIGITAL_GAIN
#define V4L2_CID_DIGITAL_GAIN		V4L2_CID_GAIN
#endif

/* Chip Identification */
#define OS02N10_CHIP_ID         0x5302  // Chip ID (P0:0x02=0x53, P0:0x03=0x02)
#define OS02N10_REG_CHIP_ID_H   0x02    // Chip ID High Byte (P0:0x02)
#define OS02N10_REG_CHIP_ID_L   0x03    // Chip ID Low Byte (P0:0x03)

/* Clock and Interface Configuration */
#define OS02N10_XVCLK_FREQ      24000000   // Typical input clock frequency (10-36MHz range)
#define BITS_PER_SAMPLE         10         // Supports 10-bit RAW output
#define MIPI_FREQ_360M          360000000  
#define OS02N10_LANES           2          // 2-lane MIPI interface
#define PIXEL_RATE_WITH_360M   (MIPI_FREQ_360M * OS02N10_LANES *2 / BITS_PER_SAMPLE)

/* Register Page Control */
#define OS02N10_REG_PAGE_SELECT 0xfd    // Page flag register (P6:0xFD) - For bank switching

/* Exposure Control */
#define OS02N10_REG_EXP_H       0x0e    // P1:0x0E - Exposure time high byte (TEXP_H)
#define OS02N10_REG_EXP_L       0x0f    // P1:0x0F - Exposure time low byte (TEXP_L)
#define OS02N10_EXPOSURE_MIN    2       // Minimum exposure (2 Time units)
#define OS02N10_EXPOSURE_STEP   1       // Exposure step size

/* Gain Control */
#define OS02N10_REG_AGAIN       0x24    // P1:0x24 - Analog gain control (RPC_L)
#define OS02N10_REG_DGAIN_H     0x1f    // P1:0x1F - Digital gain high byte (DIG_GAIN_H)
#define OS02N10_REG_DGAIN_L     0x20    // P1:0x20 - Digital gain low byte (DIG_GAIN_L)
#define OS02N10_GAIN_MIN        0x10    // Minimum gain (1x, 0x10=1x)
#define OS02N10_GAIN_MAX        0x2000  // Maximum gain (15.5x analog * 32x digital)
#define OS02N10_GAIN_STEP       1       // Gain step size
#define OS02N10_GAIN_DEFAULT    0x10    // Default gain (1x)

/* Timing Control */
#define OS02N10_REG_HTS_H       0x25    // P1:0x25 - Horizontal timing high (ROWTIME_H)
#define OS02N10_REG_HTS_L       0x26    // P1:0x26 - Horizontal timing low (ROWTIME_L)
#define OS02N10_REG_VTS_H       0x28    // P1:0x28 - Vertical timing high (FRMTIME_H)
#define OS02N10_REG_VTS_L       0x29    // P1:0x29 - Vertical timing low (FRMTIME_L)
#define OS02N10_REG_VBLANK_H    0x14    // P1:0x14 - Vertical blanking high (VBLANK_H)
#define OS02N10_REG_VBLANK_L    0x15    // P1:0x15 - Vertical blanking low (VBLANK_L)
#define OS02N10_VTS_MAX         0xffff  // Maximum VTS value

/* Control Registers */
#define OS02N10_REG_RESTART     0xfe    // P1:0xFE - Trigger register for new settings (write 0x02)
#define OS02N10_REG_CTRL_MODE   0x23    // P0:0x23 - MIPI enable*
#define OS02N10_MODE_SW_STANDBY 0x0     // Software standby mode*
#define OS02N10_MODE_STREAMING  0x01    // Normal streaming mode*

/* Reset Control */
#define OS02N10_REG_SOFTWARE_RESET 0xfc // Software reset register (write 0x01)
#define OS02N10_SOFTWARE_RESET_VAL 0x01 // Reset command value

/* Image Orientation */
#define OS02N10_FLIP_REG       0x12    // P1:0x12 - Mirror/flip control (READC)
#define MIRROR_BIT_MASK        BIT(1)  // Bit 1 controls mirror (mirror_pr)
#define FLIP_BIT_MASK          BIT(0)  // Bit 0 controls flip (updown_pr)

#define OS02N10_REG_BAYER_ORDER 0x4c   // Reserved

/* Helper Macros */
#define OS02N10_FETCH_EXP_H(VAL) (((VAL) >> 8) & 0xFF)
#define OS02N10_FETCH_EXP_L(VAL) ((VAL) & 0xFF)
#define OS02N10_FETCH_AGAIN(VAL) ((VAL) & 0xFF)
#define OS02N10_FETCH_DGAIN_H(VAL) (((VAL) >> 8) & 0xFF)
#define OS02N10_FETCH_DGAIN_L(VAL) ((VAL) & 0xFF)
#define SENSOR_ID(_msb, _lsb)   ((_msb) << 8 | (_lsb))

/* Device Identification */
#define OS02N10_NAME           "os02n10"

/* Platform-Specific Definitions */
#define OF_CAMERA_HDR_MODE     "rockchip,camera-hdr-mode"
#define OF_CAMERA_PINCTRL_STATE_DEFAULT "rockchip,camera_default"
#define OF_CAMERA_PINCTRL_STATE_SLEEP "rockchip,camera_sleep"

/* Special Commands */
#define REG_DELAY             0xEE      // Delay command*
#define REG_NULL              0xEF      // Null/placeholder command*

static const char * const OS02N10_supply_names[] = {
	"avdd",		/* Analog power */
	"dovdd",	/* Digital I/O power */
	"dvdd",         /* Digital core power */
};

#define OS02N10_NUM_SUPPLIES ARRAY_SIZE(OS02N10_supply_names)

enum os02n10_max_pad {
	PAD0,
	PAD1,
	PAD2,
	PAD3,
	PAD_MAX,
};

struct regval {
	u16 addr;
	u8 val;
};

struct os02n10_mode {
	u32 bus_fmt;
	u32 width;
	u32 height;
	struct v4l2_fract max_fps;
	u32 hts_def;
	u32 vts_def;
	u32 exp_def;
	const struct regval *reg_list;
	u32 hdr_mode;
	u32 vc[PAD_MAX];
};

struct os02n10 {
	struct i2c_client	*client;
	struct clk		*xvclk;
	struct gpio_desc	*reset_gpio;
	struct gpio_desc	*pwdn_gpio;
	struct regulator_bulk_data supplies[OS02N10_NUM_SUPPLIES];
	struct pinctrl		*pinctrl;
	struct pinctrl_state	*pins_default;
	struct pinctrl_state	*pins_sleep;
	struct v4l2_subdev	subdev;
	struct media_pad	pad;
	struct v4l2_ctrl_handler ctrl_handler;
	struct v4l2_ctrl	*exposure;
	struct v4l2_ctrl	*anal_gain;
	struct v4l2_ctrl	*digi_gain;
	struct v4l2_ctrl	*hblank;
	struct v4l2_ctrl	*vblank;
	struct v4l2_ctrl	*pixel_rate;
	struct v4l2_ctrl	*link_freq;
	struct mutex		mutex;
	bool			streaming;
	bool			power_on;
	const struct os02n10_mode *cur_mode;
	u32			cfg_num;
	u32			module_index;
	const char		*module_facing;
	const char		*module_name;
	const char		*len_name;
	bool			has_init_exp;
	struct preisp_hdrae_exp_s init_hdrae_exp;
	u8			flip;
};

#define to_os02n10(sd) container_of(sd, struct os02n10, subdev)

/*@@OS02N10_MIPI_2LANE_1920x1080_MCLK24M_720M_raw10_30fps_V11@@*/
/*Tline=272*2/18=30.222 ROW_CLK=18M HTS=272 VTS=1109 FPS=18*1000000/272/2/1109=29.836*/
static const struct regval os02n10_linear10bit_1920x1080_regs[] = {
    // System initialization
    {0xfc, 0x01},
    {0xfd, 0x00},
    {0xba, 0x02},
    {0xfd, 0x00},
    {0xb1, 0x14},  
    {0xba, 0x00},
    {0x1a, 0x00},  
    {0x1b, 0x03},

    // Exposure and gain settings
    {0xfd, 0x01},
    {0x0e, 0x00},  
    {0x0f, 0x21}, 
    {0x24, 0xff},  
    {0x2f, 0x30},
    {0xfe, 0x02},  
    {0x2b, 0xff},  
    {0x30, 0x00},
    {0x31, 0x16},  
    {0x32, 0x25},
    {0x33, 0xfb}, 

    // Timing configuration
    {0xfd, 0x01},
    {0x50, 0x03},
    {0x51, 0x07},
    {0x52, 0x04},
    {0x53, 0x05},
    {0x57, 0x40},
    {0x66, 0x04}, 
    {0x6d, 0x58},  
    {0x77, 0x01},
    {0x79, 0x32},
    {0x7c, 0x01},  
    {0x90, 0x3b},
    {0x91, 0x0b},  
    {0x92, 0x18},
    {0x95, 0x40},
    {0x99, 0x05},  
    {0xaa, 0x0e},  
    {0xab, 0x0c},
    {0xac, 0x10},  
    {0xad, 0x10},
    {0xae, 0x20},  
    {0xb0, 0x0e},  
    {0xb1, 0x0f},
    {0xb2, 0x1a},
    {0xb3, 0x1c},

    // Register fixes
    {0xfd, 0x00},
    {0xb0, 0x00},
    {0xb1, 0x14},
    {0xb2, 0x00},
    {0xb3, 0x10},

    // Black level calibration
    {0xfd, 0x03},
    {0x08, 0x00},  
    {0x09, 0x20}, 
    {0x0a, 0x02},
    {0x0b, 0x80},  
    {0x11, 0x41},  
    {0x12, 0x41},
    {0x13, 0x41},
    {0x14, 0x41},
    {0x17, 0x72},  
    {0x18, 0x6f},  
    {0x19, 0x70},  
    {0x1a, 0x6f}, 
    {0x1b, 0xc0},  
    {0x1d, 0x01},  
    {0x1f, 0x80},
    {0x20, 0x40},
    {0x21, 0x80},
    {0x22, 0x40},
    {0x23, 0x88},
    {0x4b, 0x06},
    {0x0e, 0x03},
    {0x58, 0x79},  
    {0x59, 0x08},  
    {0x5a, 0x32},  
    // BLE improve
    {0xfd, 0x03},
    {0x4c, 0x01},  
    {0x4d, 0x01},
    {0x4e, 0x01},  
    {0x4f, 0x02},

    // Analog settings
    {0xfd, 0x00},
    {0x13, 0xbe},  
    {0x14, 0x02},  
    {0x4c, 0x24},  
    {0xb6, 0x00},  
    {0xb7, 0x08},  
    {0xb9, 0xd6},  
    {0xc6, 0x95},  
    {0xc7, 0x77},  
    {0xc9, 0x22},  
    {0xca, 0x32},  
    {0xd7, 0xaa}, 
    {0xbc, 0x1f},  
    {0xbe, 0x78},
    {0xbf, 0xa5},
    {0xcb, 0x00},
    {0xcc, 0x00},
    {0xce, 0x20},
    {0xcf, 0x3f},
    {0xd0, 0x76},
    {0xd1, 0xec},

    {0x2b, 0x01}, //mipi clk mode

    // DPC configuration
    {0xfd, 0x04},
    {0x1b, 0x01},  

    // Digital windowing
    {0xfd, 0x03},
    {0x01, 0x04},
    {0x02, 0x07},
    {0x03, 0x80},
    {0x05, 0x04},
    {0x06, 0x04},
    {0x07, 0x38},
    {0xfd, 0x00},
    {0x1e, 0x0f},
    {0x1d, 0xa1},  
    {0x21, 0x04},
    {0x24, 0x02},
    {0x27, 0x07}, 
    {0x28, 0x80},  
    {0x29, 0x04},
    {0x2a, 0x38},  
    {0x2d, 0x04},
    {0x2e, 0x03},
    {0x2f, 0x0c},
    {0x31, 0x04},
    {0x32, 0x1a},
    {0x33, 0x04},
    {0x34, 0x05},  
    {0x3f, 0x40},
    {0x40, 0x94},

    // Additional settings
    {0xfd, 0x03},
    {0x26, 0x00},
    {0x2a, 0x21},
    {0x2e, 0x21},
    {0x33, 0x21},
    {0x2b, 0x16},
    {0x2f, 0x16},
    {0x34, 0x16},
    {0x28, 0x03},
    {0x2c, 0x03},
    {0x31, 0x03},
    {0x29, 0x04},
    {0x2d, 0x04},
    {0x32, 0x04},
    {0x35, 0x06},
    {0x39, 0x06},
    {0x3d, 0x06},
    {0x36, 0x06},
    {0x3a, 0x06},
    {0x3e, 0x06},
    {0x26, 0x01},
    {0x37, 0x04},
    {0x3b, 0x04},
    {0x3f, 0x04},
    {0x38, 0x03},
    {0x3c, 0x03},
    {0x40, 0x03},
    {0x43, 0x07},
    {0x44, 0x00},
    {0x45, 0x50},
    {0x46, 0x10},
    {0x47, 0xe2},
    {0x48, 0x00},
    {0x49, 0x00},
    {0x4a, 0x00},
    {0x41, 0x07},
    {0x42, 0x07},

    // Final configuration
    {0xfd, 0x00},
    {0x23, 0x01},  
    {0xfd, 0x01},
    {0xfb, 0x03},
	{REG_NULL, 0x00},
};

/*
 * The width and height must be configured to be
 * the same as the current output resolution of the sensor.
 * The input width of the isp needs to be 16 aligned.
 * The input height of the isp needs to be 8 aligned.
 * If the width or height does not meet the alignment rules,
 * you can configure the cropping parameters with the following function to
 * crop out the appropriate resolution.
 * struct v4l2_subdev_pad_ops {
 *	.get_selection
 * }
 */
static const struct os02n10_mode supported_modes[] = {
	{
		.bus_fmt = MEDIA_BUS_FMT_SBGGR10_1X10,
		.width = 1920,
		.height = 1080,
		.max_fps = {
			.numerator = 10000,
			.denominator = 300000,
		},
		.exp_def = 0x021,
		.hts_def = 0x0110 * 8,//272
		.vts_def = 0x0455,
		.reg_list = os02n10_linear10bit_1920x1080_regs,
		.hdr_mode = NO_HDR,
		.vc[PAD0] = V4L2_MBUS_CSI2_CHANNEL_0,
	},
};

static const s64 link_freq_menu_items[] = {
	MIPI_FREQ_360M,
};

/* sensor register write */
static int os02n10_write_reg(struct i2c_client *client, u8 reg, u8 val)
{
	struct i2c_msg msg;
	u8 buf[2];
	int ret;

	buf[0] = reg & 0xFF;
	buf[1] = 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)
		return 0;

	dev_err(&client->dev,
                 "write reg(0x%x val:0x%x) failed !\n", reg, val);

	return ret;
}

static int os02n10_write_array(struct i2c_client *client,
			       const struct regval *regs)
{
	u32 i; 
        int ret = 0;

	for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++) {
		ret = os02n10_write_reg(client, regs[i].addr, regs[i].val);
		if (regs[i].addr == REG_DELAY)
			usleep_range(5 * 1000, 6 * 1000);

		if (ret) {
			dev_err(&client->dev, "%s failed !\n", __func__);
			break;
		}
	}

	return ret;
}

/* sensor register read */
static int os02n10_read_reg(struct i2c_client *client, u8 reg, u8 *val)
{
	struct i2c_msg msg[2];
	u8 buf[1];
	int ret;

	buf[0] = 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;
	msg[1].flags = client->flags | I2C_M_RD;
	msg[1].buf = buf;
	msg[1].len = 1;

	ret = i2c_transfer(client->adapter, msg, 2);
	if (ret >= 0) {
		*val = buf[0];
		return 0;
	}

	dev_err(&client->dev,
		"os02n10 read reg(0x%x val:0x%x) failed !\n", reg, *val);

	return ret;
}

#if 0
static int os02n10_read_array(struct i2c_client *client,
			       const struct regval *regs)
{
	int i, ret = 0;
        u8 val = 0;

	for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++) {
        val = 0xff;
        if (regs[i].addr == 0xfd) {
    		ret = os02n10_write_reg(client, regs[i].addr, regs[i].val);
            ret |= os02n10_read_reg(client, regs[i].addr, &val);
            if (val != regs[i].val) {
                ret = os02n10_write_reg(client, regs[i].addr, regs[i].val);
                ret |= os02n10_read_reg(client, regs[i].addr, &val);
                if (val != regs[i].val) {
                    dev_err(&client->dev, "ret=%d, addr=0x%02x, val=0x%02x!!!!\n", ret, regs[i].addr, val);
                }
            }
        } else {
            val = 0xff;
            ret = os02n10_read_reg(client, regs[i].addr, &val);
        }
        dev_err(&client->dev, "ret=%d, addr=0x%02x, val=0x%02x\n", ret, regs[i].addr, val);
	}
	return ret;
}
#endif

static int os02n10_get_reso_dist(const struct os02n10_mode *mode,
				 struct v4l2_mbus_framefmt *framefmt)
{
	return abs(mode->width - framefmt->width) +
	       abs(mode->height - framefmt->height);
}

static const struct os02n10_mode *
os02n10_find_best_fit(struct os02n10 *os02n10, struct v4l2_subdev_format *fmt)
{
	struct v4l2_mbus_framefmt *framefmt = &fmt->format;
	int dist;
	int cur_best_fit = 0;
	int cur_best_fit_dist = -1;
	unsigned int i;

	for (i = 0; i < os02n10->cfg_num; i++) {
		dist = os02n10_get_reso_dist(&supported_modes[i], framefmt);
		if ((cur_best_fit_dist == -1 || dist <= cur_best_fit_dist) &&
			(supported_modes[i].bus_fmt == framefmt->code)) {
			cur_best_fit_dist = dist;
			cur_best_fit = i;
		}
	}

	return &supported_modes[cur_best_fit];
}

static int os02n10_set_fmt(struct v4l2_subdev *sd,
			   struct v4l2_subdev_pad_config *cfg,
			   struct v4l2_subdev_format *fmt)
{
	struct os02n10 *os02n10 = to_os02n10(sd);
	const struct os02n10_mode *mode;
	s64 h_blank, vblank_def;
	u64 dst_link_freq = 0;
	u64 dst_pixel_rate = 0;

	mutex_lock(&os02n10->mutex);

	mode = os02n10_find_best_fit(os02n10, fmt);
	fmt->format.code = mode->bus_fmt;
	fmt->format.width = mode->width;
	fmt->format.height = mode->height;
	fmt->format.field = V4L2_FIELD_NONE;
	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
		*v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format;
#else
		mutex_unlock(&os02n10->mutex);
		return -ENOTTY;
#endif
	} else {
		os02n10->cur_mode = mode;
		h_blank = mode->hts_def - mode->width;
		__v4l2_ctrl_modify_range(os02n10->hblank, h_blank,
					 h_blank, 1, h_blank);

		vblank_def = mode->vts_def - mode->height;
		__v4l2_ctrl_modify_range(os02n10->vblank, vblank_def,
					 OS02N10_VTS_MAX - mode->height,
					 1, vblank_def);
		if (mode->hdr_mode == NO_HDR) {
			if (mode->bus_fmt == MEDIA_BUS_FMT_SBGGR10_1X10) {
				dst_link_freq = 0;
				dst_pixel_rate = PIXEL_RATE_WITH_360M;
			}
		}
		__v4l2_ctrl_s_ctrl_int64(os02n10->pixel_rate,
					 dst_pixel_rate);
		__v4l2_ctrl_s_ctrl(os02n10->link_freq,
					   dst_link_freq);
	}

	mutex_unlock(&os02n10->mutex);

	return 0;
}

static int os02n10_get_fmt(struct v4l2_subdev *sd,
			   struct v4l2_subdev_pad_config *cfg,
			   struct v4l2_subdev_format *fmt)
{
	struct os02n10 *os02n10 = to_os02n10(sd);
	const struct os02n10_mode *mode = os02n10->cur_mode;

	mutex_lock(&os02n10->mutex);
	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
		fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
#else
		mutex_unlock(&os02n10->mutex);
		return -ENOTTY;
#endif
	} else {
		fmt->format.width = mode->width;
		fmt->format.height = mode->height;
		fmt->format.code = mode->bus_fmt;
		fmt->format.field = V4L2_FIELD_NONE;
		if (fmt->pad < PAD_MAX && mode->hdr_mode != NO_HDR)
			fmt->reserved[0] = mode->vc[fmt->pad];
		else
			fmt->reserved[0] = mode->vc[PAD0];
	}
	mutex_unlock(&os02n10->mutex);

	return 0;
}

static int os02n10_enum_mbus_code(struct v4l2_subdev *sd,
				  struct v4l2_subdev_pad_config *cfg,
				  struct v4l2_subdev_mbus_code_enum *code)
{
	struct os02n10 *os02n10 = to_os02n10(sd);

	if (code->index != 0)
		return -EINVAL;
	code->code = os02n10->cur_mode->bus_fmt;

	return 0;
}

static int os02n10_enum_frame_sizes(struct v4l2_subdev *sd,
				    struct v4l2_subdev_pad_config *cfg,
				    struct v4l2_subdev_frame_size_enum *fse)
{
	struct os02n10 *os02n10 = to_os02n10(sd);

	if (fse->index >= os02n10->cfg_num)
		return -EINVAL;

	if (fse->code != supported_modes[fse->index].bus_fmt)
		return -EINVAL;

	fse->min_width  = supported_modes[fse->index].width;
	fse->max_width  = supported_modes[fse->index].width;
	fse->max_height = supported_modes[fse->index].height;
	fse->min_height = supported_modes[fse->index].height;

	return 0;
}

static int os02n10_g_frame_interval(struct v4l2_subdev *sd,
				    struct v4l2_subdev_frame_interval *fi)
{
	struct os02n10 *os02n10 = to_os02n10(sd);
	const struct os02n10_mode *mode = os02n10->cur_mode;

	fi->interval = mode->max_fps;

	return 0;
}

static int os02n10_g_mbus_config(struct v4l2_subdev *sd, 
				 struct v4l2_mbus_config *config)
{
	struct os02n10 *os02n10 = to_os02n10(sd);
	const struct os02n10_mode *mode = os02n10->cur_mode;
	u32 val = 0;

	if (mode->hdr_mode == NO_HDR)
		val = 1 << (OS02N10_LANES - 1) |
		      V4L2_MBUS_CSI2_CHANNEL_0 |
		      V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;

	config->type = V4L2_MBUS_CSI2;
	config->flags = val;

	return 0;
}

static void os02n10_get_module_inf(struct os02n10 *os02n10,
				   struct rkmodule_inf *inf)
{
	memset(inf, 0, sizeof(*inf));
	strscpy(inf->base.sensor, OS02N10_NAME, sizeof(inf->base.sensor));
	strscpy(inf->base.module, os02n10->module_name, sizeof(inf->base.module));
	strscpy(inf->base.lens, os02n10->len_name, sizeof(inf->base.lens));
}

static long os02n10_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
{
	struct os02n10 *os02n10 = to_os02n10(sd);
	struct rkmodule_hdr_cfg *hdr_cfg;
	long ret = 0;
	u32 stream = 0;

	switch (cmd) {
	case RKMODULE_GET_MODULE_INFO:
		os02n10_get_module_inf(os02n10, (struct rkmodule_inf *)arg);
		break;
	case RKMODULE_SET_HDR_CFG:
		hdr_cfg = (struct rkmodule_hdr_cfg *)arg;
		if (hdr_cfg->hdr_mode != 0)
			ret = -1;
		break;
	case RKMODULE_GET_HDR_CFG:
		hdr_cfg = (struct rkmodule_hdr_cfg *)arg;
		hdr_cfg->esp.mode = HDR_NORMAL_VC;
		hdr_cfg->hdr_mode = os02n10->cur_mode->hdr_mode;
		break;
	case RKMODULE_SET_QUICK_STREAM:
		stream = *((u32 *)arg);
		if (stream)
		{
			ret = os02n10_write_reg(os02n10->client, OS02N10_REG_PAGE_SELECT, 0x00);
			ret = os02n10_write_reg(os02n10->client, OS02N10_REG_CTRL_MODE,
						OS02N10_MODE_STREAMING);
		}
		else
		{
			ret = os02n10_write_reg(os02n10->client, OS02N10_REG_PAGE_SELECT, 0x00);
			ret = os02n10_write_reg(os02n10->client, OS02N10_REG_CTRL_MODE,
						OS02N10_MODE_SW_STANDBY);
		}

		break;
	default:
		ret = -ENOIOCTLCMD;
		break;
	}

	return ret;
}

#ifdef CONFIG_COMPAT
static long os02n10_compat_ioctl32(struct v4l2_subdev *sd,
				   unsigned int cmd, unsigned long arg)
{
	void __user *up = compat_ptr(arg);
	struct rkmodule_inf *inf;
	struct rkmodule_hdr_cfg *hdr;
	long ret;
	u32 stream = 0;

	switch (cmd) {
	case RKMODULE_GET_MODULE_INFO:
		inf = kzalloc(sizeof(*inf), GFP_KERNEL);
		if (!inf) {
			ret = -ENOMEM;
			return ret;
		}

		ret = os02n10_ioctl(sd, cmd, inf);
		if (!ret) {
			ret = copy_to_user(up, inf, sizeof(*inf));
			if (ret)
				ret = -EFAULT;
		}
		kfree(inf);
		break;
	case RKMODULE_GET_HDR_CFG:
		hdr = kzalloc(sizeof(*hdr), GFP_KERNEL);
		if (!hdr) {
			ret = -ENOMEM;
			return ret;
		}

		ret = os02n10_ioctl(sd, cmd, hdr);
		if (!ret) {
			ret = copy_to_user(up, hdr, sizeof(*hdr));
			if (ret)
				ret = -EFAULT;
		}
		kfree(hdr);
		break;
	case RKMODULE_SET_HDR_CFG:
		hdr = kzalloc(sizeof(*hdr), GFP_KERNEL);
		if (!hdr) {
			ret = -ENOMEM;
			return ret;
		}

		if (copy_from_user(hdr, up, sizeof(*hdr)))
			return -EFAULT;

		ret = os02n10_ioctl(sd, cmd, hdr);
		kfree(hdr);
		break;
	case RKMODULE_SET_QUICK_STREAM:
		if (copy_from_user(&stream, up, sizeof(u32)))
			return -EFAULT;

		ret = os02n10_ioctl(sd, cmd, &stream);
		break;
	default:
		ret = -ENOIOCTLCMD;
		break;
	}

	return ret;
}
#endif

static int __os02n10_start_stream(struct os02n10 *os02n10)
{
	int ret = 0;
	ret |= os02n10_write_array(os02n10->client, os02n10->cur_mode->reg_list);
	if (ret) {
		dev_err(&os02n10->client->dev,
			"write array failed in start stream\n");
		return ret;
	}

	/* In case these controls are set before streaming */
	ret = __v4l2_ctrl_handler_setup(&os02n10->ctrl_handler);
	if (ret)
		return ret;
	if (os02n10->has_init_exp && os02n10->cur_mode->hdr_mode != NO_HDR) {
		ret = os02n10_ioctl(&os02n10->subdev, PREISP_CMD_SET_HDRAE_EXP,
		                    &os02n10->init_hdrae_exp);
		if (ret) {
			dev_err(&os02n10->client->dev,
			        "init exp fail in hdr mode\n");
			return ret;
		}
	}

	ret |= os02n10_write_reg(os02n10->client, OS02N10_REG_PAGE_SELECT, 0x00);
	ret |= os02n10_write_reg(os02n10->client, OS02N10_REG_CTRL_MODE,
				 OS02N10_MODE_STREAMING);

	ret |= os02n10_write_reg(os02n10->client, OS02N10_REG_PAGE_SELECT, 0x01);
 	ret |= os02n10_write_reg(os02n10->client, 0x22, 0x03);
	/*os02n10 read registers array from kernel log*/
    //os02n10_read_array(os02n10->client, os02n10->cur_mode->reg_list);

	return ret;
}

static int __os02n10_stop_stream(struct os02n10 *os02n10)
{
	int ret = 0;

	os02n10->has_init_exp = false;
	ret = os02n10_write_reg(os02n10->client, OS02N10_REG_PAGE_SELECT, 0x00);
	return os02n10_write_reg(os02n10->client, OS02N10_REG_CTRL_MODE, 
                                 OS02N10_MODE_SW_STANDBY);
}

static int os02n10_s_stream(struct v4l2_subdev *sd, int on)
{
	struct os02n10 *os02n10 = to_os02n10(sd);
	struct i2c_client *client = os02n10->client;
	int ret = 0;
	mutex_lock(&os02n10->mutex);
	on = !!on;
	if (on == os02n10->streaming)
		goto unlock_and_return;

	if (on) {
		ret = pm_runtime_get_sync(&client->dev);
		if (ret < 0) {
			pm_runtime_put_noidle(&client->dev);
			goto unlock_and_return;
		}

		ret = __os02n10_start_stream(os02n10);
		if (ret) {
			v4l2_err(sd, "start stream failed while write regs\n");
			pm_runtime_put(&client->dev);
			goto unlock_and_return;
		}
	} else {
		__os02n10_stop_stream(os02n10);
		pm_runtime_put(&client->dev);
	}

	os02n10->streaming = on;

unlock_and_return:
	mutex_unlock(&os02n10->mutex);

	return ret;
}

static int os02n10_s_power(struct v4l2_subdev *sd, int on)
{
	struct os02n10 *os02n10 = to_os02n10(sd);
	struct i2c_client *client = os02n10->client;
	int ret = 0;

	mutex_lock(&os02n10->mutex);

	/* If the power state is not modified - no work to do. */
	if (os02n10->power_on == !!on)
		goto unlock_and_return;

	if (on) {
		ret = pm_runtime_get_sync(&client->dev);
		if (ret < 0) {
			pm_runtime_put_noidle(&client->dev);
			goto unlock_and_return;
		}

		ret |= os02n10_write_reg(os02n10->client,
					 OS02N10_REG_SOFTWARE_RESET,
					 OS02N10_SOFTWARE_RESET_VAL);
		usleep_range(100, 200);

		os02n10->power_on = true;
	} else {
		pm_runtime_put(&client->dev);
		os02n10->power_on = false;
	}

unlock_and_return:
	mutex_unlock(&os02n10->mutex);

	return ret;
}

static int __os02n10_power_on(struct os02n10 *os02n10)
{
	int ret;
	struct device *dev = &os02n10->client->dev;

	if (!IS_ERR_OR_NULL(os02n10->pins_default)) {
		ret = pinctrl_select_state(os02n10->pinctrl,
					   os02n10->pins_default);
		if (ret < 0)
			dev_err(dev, "could not set pins\n");
	}
	ret = clk_set_rate(os02n10->xvclk, OS02N10_XVCLK_FREQ);
	if (ret < 0)
		dev_warn(dev, "Failed to set xvclk rate (24MHz)\n");
	if (clk_get_rate(os02n10->xvclk) != OS02N10_XVCLK_FREQ)
		dev_warn(dev, "xvclk mismatched, modes are based on 24MHz\n");
	ret = clk_prepare_enable(os02n10->xvclk);
	if (ret < 0) {
		dev_err(dev, "Failed to enable xvclk\n");
		return ret;
	}

	if (!IS_ERR(os02n10->pwdn_gpio))
		gpiod_direction_output(os02n10->pwdn_gpio, 0);

	if (!IS_ERR(os02n10->reset_gpio))
		gpiod_direction_output(os02n10->reset_gpio, 0);

	ret = regulator_bulk_enable(OS02N10_NUM_SUPPLIES, os02n10->supplies);
	if (ret < 0) {
		dev_err(dev, "Failed to enable regulators\n");
		goto disable_clk;
	}

	/* From spec: delay from power stable to pwdn off: 5ms */
	usleep_range(5000, 6000);
	if (!IS_ERR(os02n10->pwdn_gpio))
		gpiod_direction_output(os02n10->pwdn_gpio, 1);

	/* From spec: delay from pwdn off to reset off */
	usleep_range(4000, 5000);
	if (!IS_ERR(os02n10->reset_gpio))
		gpiod_direction_output(os02n10->reset_gpio, 1);

	/* From spec: 5ms for SCCB initialization */
	usleep_range(9000, 10000);
	return 0;

disable_clk:
	clk_disable_unprepare(os02n10->xvclk);

	return ret;
}

static void __os02n10_power_off(struct os02n10 *os02n10)
{
	int ret;
	struct device *dev = &os02n10->client->dev;

	if (!IS_ERR(os02n10->pwdn_gpio))
		gpiod_direction_output(os02n10->pwdn_gpio, 0);

	clk_disable_unprepare(os02n10->xvclk);

	if (!IS_ERR(os02n10->reset_gpio))
		gpiod_direction_output(os02n10->reset_gpio, 0);
	if (!IS_ERR_OR_NULL(os02n10->pins_sleep)) {
		ret = pinctrl_select_state(os02n10->pinctrl,
					   os02n10->pins_sleep);
		if (ret < 0)
			dev_dbg(dev, "could not set pins\n");
	}
	regulator_bulk_disable(OS02N10_NUM_SUPPLIES, os02n10->supplies);
}

static int os02n10_runtime_resume(struct device *dev)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct os02n10 *os02n10 = to_os02n10(sd);

	return __os02n10_power_on(os02n10);
}

static int os02n10_runtime_suspend(struct device *dev)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct os02n10 *os02n10 = to_os02n10(sd);

	__os02n10_power_off(os02n10);

	return 0;
}

#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
static int os02n10_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
{
	struct os02n10 *os02n10 = to_os02n10(sd);
	struct v4l2_mbus_framefmt *try_fmt =
		v4l2_subdev_get_try_format(sd, fh->pad, 0);
	const struct os02n10_mode *def_mode = &supported_modes[0];

	mutex_lock(&os02n10->mutex);
	/* Initialize try_fmt */
	try_fmt->width = def_mode->width;
	try_fmt->height = def_mode->height;
	try_fmt->code = def_mode->bus_fmt;
	try_fmt->field = V4L2_FIELD_NONE;

	mutex_unlock(&os02n10->mutex);
	/* No crop or compose */

	return 0;
}
#endif

static int os02n10_enum_frame_interval(struct v4l2_subdev *sd,
				       struct v4l2_subdev_pad_config *cfg,
				       struct v4l2_subdev_frame_interval_enum *fie)
{
	struct os02n10 *os02n10 = to_os02n10(sd);

	if (fie->index >= os02n10->cfg_num)
		return -EINVAL;

	fie->code = supported_modes[fie->index].bus_fmt;
	fie->width = supported_modes[fie->index].width;
	fie->height = supported_modes[fie->index].height;
	fie->interval = supported_modes[fie->index].max_fps;
	fie->reserved[0] = supported_modes[fie->index].hdr_mode;
	return 0;
}

static const struct dev_pm_ops os02n10_pm_ops = {
	SET_RUNTIME_PM_OPS(os02n10_runtime_suspend,
	os02n10_runtime_resume, NULL)
};

#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
static const struct v4l2_subdev_internal_ops os02n10_internal_ops = {
	.open = os02n10_open,
};
#endif

static const struct v4l2_subdev_core_ops os02n10_core_ops = {
	.s_power = os02n10_s_power,
	.ioctl = os02n10_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl32 = os02n10_compat_ioctl32,
#endif
};

static const struct v4l2_subdev_video_ops os02n10_video_ops = {
	.s_stream = os02n10_s_stream,
	.g_frame_interval = os02n10_g_frame_interval,
	.g_mbus_config = os02n10_g_mbus_config,
};

static const struct v4l2_subdev_pad_ops os02n10_pad_ops = {
	.enum_mbus_code = os02n10_enum_mbus_code,
	.enum_frame_size = os02n10_enum_frame_sizes,
	.enum_frame_interval = os02n10_enum_frame_interval,
	.get_fmt = os02n10_get_fmt,
	.set_fmt = os02n10_set_fmt,
};

static const struct v4l2_subdev_ops os02n10_subdev_ops = {
	.core	= &os02n10_core_ops,
	.video	= &os02n10_video_ops,
	.pad	= &os02n10_pad_ops,
};


static void os02n10_get_gain_reg(u32 total_gain, u32 *again, u32 *dgain)
{
	/* again max is 15.5 gain convert thread is 15.5 * 1 * 64 = 992*/
	if (total_gain < 0x40) {
		*again = 0x10;
		*dgain = 0x0040;
	} else if (total_gain < 992) {
		*again = total_gain >> 2;
		*dgain = 0x0040;
	} else if (total_gain < 31744) {	//15.5 * 32 * 64
		*again = 0xF8;
		*dgain = total_gain * 64 / 992;
	} else {
		*again = 0xF8;
		*dgain = 0x07ff;
	}
}

static int os02n10_set_ctrl(struct v4l2_ctrl *ctrl)
{
	struct os02n10 *os02n10 = container_of(ctrl->handler,
				  struct os02n10, ctrl_handler);
	struct i2c_client *client = os02n10->client;
	s64 max;
	int ret = 0;
	u32 again = 0, dgain = 0;

	/* Propagate change of current control to all related controls */
	switch (ctrl->id) {
	case V4L2_CID_VBLANK:
		/* Update max exposure while meeting expected vblanking */
		max = os02n10->cur_mode->height + ctrl->val - 9;
		__v4l2_ctrl_modify_range(os02n10->exposure,
					 os02n10->exposure->minimum, max,
					 os02n10->exposure->step,
					 os02n10->exposure->default_value);
		break;
	}

	if (!pm_runtime_get_if_in_use(&client->dev))
		return 0;

	switch (ctrl->id) {
	case V4L2_CID_EXPOSURE:
		ret = os02n10_write_reg(os02n10->client,
					OS02N10_REG_PAGE_SELECT, 0x1);
		ret |= os02n10_write_reg(os02n10->client,
					 OS02N10_REG_EXP_H,
					 OS02N10_FETCH_EXP_H(ctrl->val));
		ret |= os02n10_write_reg(os02n10->client,
					 OS02N10_REG_EXP_L,
					 OS02N10_FETCH_EXP_L(ctrl->val));
		ret |= os02n10_write_reg(os02n10->client,
					 OS02N10_REG_RESTART, 0x02);
		dev_dbg(&client->dev, "set exposure 0x%x\n", ctrl->val);
		break;
	case V4L2_CID_ANALOGUE_GAIN:
		os02n10_get_gain_reg(ctrl->val, &again, &dgain);

		// again write
		ret = os02n10_write_reg(os02n10->client,
					OS02N10_REG_PAGE_SELECT, 0x01);
		ret |= os02n10_write_reg(os02n10->client,
					 OS02N10_REG_AGAIN,
					 OS02N10_FETCH_AGAIN(again));
		ret |= os02n10_write_reg(os02n10->client,
					 OS02N10_REG_RESTART, 0x02);

		// dgain write
		ret |= os02n10_write_reg(os02n10->client,
					OS02N10_REG_PAGE_SELECT, 0x01);
		ret |= os02n10_write_reg(os02n10->client,
					 OS02N10_REG_DGAIN_H,
					 OS02N10_FETCH_DGAIN_H(dgain));
		ret |= os02n10_write_reg(os02n10->client,
					 OS02N10_REG_DGAIN_L,
					 OS02N10_FETCH_DGAIN_L(dgain));
		ret |= os02n10_write_reg(os02n10->client,
					 OS02N10_REG_RESTART, 0x02);

		dev_dbg(&client->dev, "set gain 0x%x, again 0x%x, dgain 0x%x\n",
		        ctrl->val, again, dgain);
		break;
	case V4L2_CID_VBLANK:
		ret = os02n10_write_reg(os02n10->client,
		                        OS02N10_REG_PAGE_SELECT, 0x01);
		ret |= os02n10_write_reg(os02n10->client,
		                         OS02N10_REG_VBLANK_H, 
                                        (ctrl->val >> 8) & 0xFF);
		ret |= os02n10_write_reg(os02n10->client,
		                         OS02N10_REG_VBLANK_L, 
                                         ctrl->val & 0xFF);
		ret |= os02n10_write_reg(os02n10->client,
		                         OS02N10_REG_RESTART, 0x02);
		dev_dbg(&client->dev, "set vblank 0x%x\n", ctrl->val);
		break;
	case V4L2_CID_TEST_PATTERN:
		break;
	case V4L2_CID_HFLIP:
		if (ctrl->val)
			os02n10->flip |= MIRROR_BIT_MASK;
		else
			os02n10->flip &= ~MIRROR_BIT_MASK;

		ret = os02n10_write_reg(os02n10->client,
					OS02N10_REG_PAGE_SELECT, 0x01);
		ret |= os02n10_write_reg(os02n10->client,
					 OS02N10_FLIP_REG, os02n10->flip);
		ret |= os02n10_write_reg(os02n10->client,
					 OS02N10_REG_RESTART, 0x02);
		dev_dbg(&client->dev, "set hflip 0x%x\n", os02n10->flip);
		break;
	case V4L2_CID_VFLIP:
		if (ctrl->val)
			os02n10->flip |= FLIP_BIT_MASK;
		else
			os02n10->flip &= ~FLIP_BIT_MASK;

		ret = os02n10_write_reg(os02n10->client,
					OS02N10_REG_PAGE_SELECT, 0x01);
		ret |= os02n10_write_reg(os02n10->client,
					 OS02N10_FLIP_REG, os02n10->flip);
		dev_dbg(&client->dev, "set vflip 0x%x\n", os02n10->flip);
		break;
	default:
		dev_warn(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n",
				 __func__, ctrl->id, ctrl->val);
		break;
	}
	pm_runtime_put(&client->dev);

	return ret;
}

static const struct v4l2_ctrl_ops os02n10_ctrl_ops = {
	.s_ctrl = os02n10_set_ctrl,
};

static int os02n10_initialize_controls(struct os02n10 *os02n10)
{
	const struct os02n10_mode *mode;
	struct v4l2_ctrl_handler *handler;
	s64 exposure_max, vblank_def;
	u32 h_blank;
	int ret;
	u64 dst_link_freq = 0;
	u64 dst_pixel_rate = 0;

	handler = &os02n10->ctrl_handler;
	mode = os02n10->cur_mode;
	ret = v4l2_ctrl_handler_init(handler, 9);
	if (ret)
		return ret;
	handler->lock = &os02n10->mutex;

	os02n10->link_freq = v4l2_ctrl_new_int_menu(handler, NULL,
						    V4L2_CID_LINK_FREQ,
						    1, 0,
						    link_freq_menu_items);

	if (os02n10->cur_mode->bus_fmt == MEDIA_BUS_FMT_SBGGR10_1X10) {
		dst_link_freq = 0;
		dst_pixel_rate = PIXEL_RATE_WITH_360M;
	}
	/* pixel rate = link frequency * 2 * lanes / BITS_PER_SAMPLE */
	os02n10->pixel_rate = v4l2_ctrl_new_std(handler, NULL,
						V4L2_CID_PIXEL_RATE,
						0, PIXEL_RATE_WITH_360M,
						1, dst_pixel_rate);

	__v4l2_ctrl_s_ctrl(os02n10->link_freq, dst_link_freq);

	h_blank = mode->hts_def - mode->width;
	os02n10->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK,
					    h_blank, h_blank, 1, h_blank);
	if (os02n10->hblank)
		os02n10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;

	vblank_def = mode->vts_def - mode->height;
	os02n10->vblank = v4l2_ctrl_new_std(handler, &os02n10_ctrl_ops,
					    V4L2_CID_VBLANK, vblank_def,
					    OS02N10_VTS_MAX - mode->height,
					    1, vblank_def);

	exposure_max = mode->vts_def - 9;
	os02n10->exposure = v4l2_ctrl_new_std(handler, &os02n10_ctrl_ops,
					      V4L2_CID_EXPOSURE, OS02N10_EXPOSURE_MIN,
					      exposure_max, OS02N10_EXPOSURE_STEP,
					      mode->exp_def);

	os02n10->anal_gain = v4l2_ctrl_new_std(handler, &os02n10_ctrl_ops,
					       V4L2_CID_ANALOGUE_GAIN, OS02N10_GAIN_MIN,
					       OS02N10_GAIN_MAX, OS02N10_GAIN_STEP,
					       OS02N10_GAIN_DEFAULT);

	v4l2_ctrl_new_std(handler, &os02n10_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0);

	v4l2_ctrl_new_std(handler, &os02n10_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0);

	os02n10->flip = 0;

	if (handler->error) {
		ret = handler->error;
		dev_err(&os02n10->client->dev,
			"Failed to init controls(%d)\n", ret);
		goto err_free_handler;
	}

	os02n10->subdev.ctrl_handler = handler;
	os02n10->has_init_exp = false;

	return 0;

err_free_handler:
	v4l2_ctrl_handler_free(handler);

	return ret;
}

static int os02n10_check_sensor_id(struct os02n10 *os02n10,
				   struct i2c_client *client)
{
	struct device *dev = &os02n10->client->dev;
	u8 id_h = 0, id_l = 0;
	u32 id = 0;
	int ret;

	ret = os02n10_read_reg(client, OS02N10_REG_CHIP_ID_H, &id_h);
	ret |= os02n10_read_reg(client, OS02N10_REG_CHIP_ID_L, &id_l);

	id = SENSOR_ID(id_h, id_l);
	if (id != OS02N10_CHIP_ID) {
		dev_err(dev, "Unexpected sensor id(%06x), ret(%d)\n", id, ret);
		return -ENODEV;
	}
	dev_info(dev, "Detected OV%06x sensor\n", OS02N10_CHIP_ID);	
	return 0;
}

static int os02n10_configure_regulators(struct os02n10 *os02n10)
{
	unsigned int i;

	for (i = 0; i < OS02N10_NUM_SUPPLIES; i++)
		os02n10->supplies[i].supply = OS02N10_supply_names[i];

	return devm_regulator_bulk_get(&os02n10->client->dev,
					OS02N10_NUM_SUPPLIES,
					os02n10->supplies);
}

static int os02n10_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
	struct device *dev = &client->dev;
	struct device_node *node = dev->of_node;
	struct os02n10 *os02n10;
	struct v4l2_subdev *sd;
	char facing[2];
	int ret;
    u32 i, hdr_mode = 0;

	dev_info(dev, "driver version: %02x.%02x.%02x",
		 DRIVER_VERSION >> 16,
		 (DRIVER_VERSION & 0xff00) >> 8,
		 DRIVER_VERSION & 0x00ff);

	os02n10 = devm_kzalloc(dev, sizeof(*os02n10), GFP_KERNEL);
	if (!os02n10)
		return -ENOMEM;

	ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX,
				   &os02n10->module_index);
	ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING,
				       &os02n10->module_facing);
	ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME,
				       &os02n10->module_name);
	ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME,
				       &os02n10->len_name);
	if (ret) {
		dev_err(dev, "could not get module information!\n");
		return -EINVAL;
	}

	ret = of_property_read_u32(node, OF_CAMERA_HDR_MODE, &hdr_mode);
	if (ret) {
		hdr_mode = NO_HDR;
		dev_warn(dev, " Get hdr mode failed! no hdr default\n");
	}
	os02n10->cfg_num = ARRAY_SIZE(supported_modes);
	for (i = 0; i < os02n10->cfg_num; i++) {
		if (hdr_mode == supported_modes[i].hdr_mode) {
			os02n10->cur_mode = &supported_modes[i];
			break;
		}
	}
	os02n10->client = client;

	os02n10->xvclk = devm_clk_get(dev, "xvclk");
	if (IS_ERR(os02n10->xvclk)) {
		dev_err(dev, "Failed to get xvclk\n");
		return -EINVAL;
	}

	os02n10->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_ASIS);
	if (IS_ERR(os02n10->reset_gpio))
		dev_warn(dev, "Failed to get reset-gpios\n");

	os02n10->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_ASIS);
	if (IS_ERR(os02n10->pwdn_gpio))
		dev_warn(dev, "Failed to get pwdn-gpios\n");

	os02n10->pinctrl = devm_pinctrl_get(dev);
	if (!IS_ERR(os02n10->pinctrl)) {
		os02n10->pins_default =
			pinctrl_lookup_state(os02n10->pinctrl,
				 OF_CAMERA_PINCTRL_STATE_DEFAULT);
		if (IS_ERR(os02n10->pins_default))
			dev_err(dev, "could not get default pinstate\n");

		os02n10->pins_sleep =
			pinctrl_lookup_state(os02n10->pinctrl,
				 OF_CAMERA_PINCTRL_STATE_SLEEP);
		if (IS_ERR(os02n10->pins_sleep))
			dev_err(dev, "could not get sleep pinstate\n");
	} else {
		dev_err(dev, "no pinctrl\n");
	}

	ret = os02n10_configure_regulators(os02n10);
	if (ret) {
		dev_err(dev, "Failed to get power regulators\n");
		return ret;
	}

	mutex_init(&os02n10->mutex);

	sd = &os02n10->subdev;
	v4l2_i2c_subdev_init(sd, client, &os02n10_subdev_ops);
	ret = os02n10_initialize_controls(os02n10);	
	if (ret)
		goto err_destroy_mutex;

	ret = __os02n10_power_on(os02n10);
	if (ret)
		goto err_free_handler;

	ret = os02n10_check_sensor_id(os02n10, client);
	if (ret)
		goto err_power_off;

#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
	sd->internal_ops = &os02n10_internal_ops;
	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
#endif
#if defined(CONFIG_MEDIA_CONTROLLER)
	os02n10->pad.flags = MEDIA_PAD_FL_SOURCE;
	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
	ret = media_entity_pads_init(&sd->entity, 1, &os02n10->pad);
	if (ret < 0)
		goto err_power_off;
#endif

	memset(facing, 0, sizeof(facing));
	if (strcmp(os02n10->module_facing, "back") == 0)
		facing[0] = 'b';
	else
		facing[0] = 'f';

	snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s",
		 os02n10->module_index, facing,
		 OS02N10_NAME, dev_name(sd->dev));
	ret = v4l2_async_register_subdev_sensor_common(sd);
	if (ret) {
		dev_err(dev, "v4l2 async register subdev failed\n");
		goto err_clean_entity;
	}

	pm_runtime_set_active(dev);
	pm_runtime_enable(dev);
	pm_runtime_idle(dev);
	
	return 0;

err_clean_entity:
#if defined(CONFIG_MEDIA_CONTROLLER)
	media_entity_cleanup(&sd->entity);
#endif
err_power_off:
	__os02n10_power_off(os02n10);
err_free_handler:
	v4l2_ctrl_handler_free(&os02n10->ctrl_handler);
err_destroy_mutex:
	mutex_destroy(&os02n10->mutex);

	return ret;
}

static int os02n10_remove(struct i2c_client *client)
{
	struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct os02n10 *os02n10 = to_os02n10(sd);

	v4l2_async_unregister_subdev(sd);
#if defined(CONFIG_MEDIA_CONTROLLER)
	media_entity_cleanup(&sd->entity);
#endif
	v4l2_ctrl_handler_free(&os02n10->ctrl_handler);
	mutex_destroy(&os02n10->mutex);

	pm_runtime_disable(&client->dev);
	if (!pm_runtime_status_suspended(&client->dev))
		__os02n10_power_off(os02n10);
	pm_runtime_set_suspended(&client->dev);

	return 0;
}

#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id os02n10_of_match[] = {
	{ .compatible = "ovti,os02n10" },
	{},
};
MODULE_DEVICE_TABLE(of, os02n10_of_match);
#endif

static const struct i2c_device_id os02n10_match_id[] = {
	{ "ovti,os02n10", 0 },
	{ },
};

static struct i2c_driver os02n10_i2c_driver = {
	.driver = {
		.name = OS02N10_NAME,
		.pm = &os02n10_pm_ops,
		.of_match_table = of_match_ptr(os02n10_of_match),
	},
	.probe		= &os02n10_probe,
	.remove		= &os02n10_remove,
	.id_table	= os02n10_match_id,
};

static int __init sensor_mod_init(void)
{
	return i2c_add_driver(&os02n10_i2c_driver);
}

static void __exit sensor_mod_exit(void)
{
	i2c_del_driver(&os02n10_i2c_driver);
}

device_initcall_sync(sensor_mod_init);
module_exit(sensor_mod_exit);

MODULE_DESCRIPTION("OmniVision os02n10 sensor driver");
MODULE_LICENSE("GPL v2");
xml文件+json文件

        完成了以上的配置之后执行dmesg |grep (对应的sensor名字)之后可以看到对应的sensor型号已经被识别到了。这个时候虽然可以正常打开摄像头应用,但是你会发现摄像头画面呈现灰色,无法出现正常图像。这是因为你还缺少了xml文件+json文件这两个文件。这里由于这两个文件的篇幅属实很大,所以就不把这两个文件完整贴上了。博主修改的xml文件是这个camera3_profiles_rk356x_virtual_dual.xml,具体的修改如图3所示,添加了两条,你可以看到很多sensor都在这里定义了,你可以找一个像素对应的复制粘贴修改一下sensor名字,具体在SDK中的位置均放在文章末尾以供参考。

图3

        图4展示的就是os02n10_OS02N10_HX2716X.json这个JSON文件的部分内容,可以看到这个主要分为白天场景和夜晚场景,如果没有夜视的需求,只需要关注白天场景即可,展开的话里面就是调整图像的一些参数设置,里面内容非常非常多!关于这部分的调整需要主控RK专门的ISP工程师去tuning,如果你只是想验证图像画面,可以先在对应的位置找一个JSON文件修改一下其中的分辨率,再起一个和这款sensor对应的名字,可以参考我的命名,以便设备树,驱动文件、xml文件+json文件通过sensor名字对应起来。

图4

总结

        这是博主发布的第三篇博客啦,由于兼容的方法类似,所以博主偷了个小懒,在上一篇的基础上改改东西就又成了新的一篇博客,哈哈哈,其他的都大差不差的。目的还是用于记录自己的学习和成长过程,对于其中理解不到位的地方还请各位大佬评论指正,大家共同学习,一起进步。文末附上笔者在该SDK下的功能实现位置,以便再次学习查看。

        modified:   device/rockchip/rk356x/etc/camera/hardware/rockchip/camera/etc/camera/camera3_profiles_rk356x_virtual_dual.xml
        modified:   external/camera_engine_rkaiq/hal_interface2.0/rkaiq_3A_server/rkisp_3A_server.cpp
        new file:   external/camera_engine_rkaiq/iqfiles/isp21/os02n10_OS02N10_HX2716X.json
        modified:   kernel/arch/arm64/boot/dts/rockchip/rk3566pro-evb2-lp4x-v10.dtsi
        modified:   kernel/arch/arm64/configs/rockchip_defconfig
        modified:   kernel/drivers/media/i2c/Kconfig
        modified:   kernel/drivers/media/i2c/Makefile
        new file:   kernel/drivers/media/i2c/os02n10.c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值