linux ov02k10 camera 驱动 代码注释以及驱动简介(腾讯元宝版本)
好的,我们来逐段解析这个Linux 图像传感器驱动代码,并添加详细的中文注释。这个驱动用于控制 Omnivision OV02K10 图像传感器芯片。您说得对,我提供的代码注释确实没有完整覆盖整个驱动程序。让我继续完成剩余部分的详细注释:完整驱动程序总结这个驱动程序完整实现了对OmniVision OV02K10图像传感器的控制,主要包括以下功能:初始化和探测:电源管理:视频流控制:参数控制:格式
好的,我们来逐段解析这个 ov02k10 Linux 图像传感器驱动代码,并添加详细的中文注释。这个驱动用于控制 Omnivision OV02K10 图像传感器芯片。
// SPDX-License-Identifier: GPL-2.0
/*
* ov02k10 driver - OV02K10 图像传感器驱动
*
* Copyright (C) 2020 Rockchip Electronics Co., Ltd. - 版权信息,瑞芯微电子
*
* V0.0X01.0X00 first version, only linear mode ready. - 版本历史:初始版本,仅支持线性模式
* V0.0X01.0X01 both linear and HDR modes are ready. - 添加了HDR模式支持
* V0.0X01.0X02 add quick stream on/off - 添加了快速启动/停止流功能
*/
#include <linux/clk.h> // 时钟管理
#include <linux/device.h> // 设备模型核心
#include <linux/delay.h> // 延时函数
#include <linux/gpio/consumer.h> // GPIO 消费者接口(使用GPIO)
#include <linux/i2c.h> // I2C 总线支持
#include <linux/module.h> // 模块基本支持
#include <linux/pm_runtime.h> // 运行时电源管理
#include <linux/regulator/consumer.h> // 电压调节器(电源)支持
#include <linux/sysfs.h> // sysfs 文件系统支持(用于调试接口)
#include <linux/slab.h> // 内存分配函数
#include <linux/version.h> // 内核版本信息
#include <linux/rk-camera-module.h> // Rockchip 相机模块特定信息
#include <media/media-entity.h> // Media Controller 媒体实体
#include <media/v4l2-async.h> // V4L2 异步子设备支持
#include <media/v4l2-ctrls.h> // V4L2 控制框架
#include <media/v4l2-subdev.h> // V4L2 子设备框架
#include <linux/pinctrl/consumer.h> // 引脚控制(Pin Control)子系统
#include <linux/rk-preisp.h> // Rockchip Pre-ISP (图像预处理) 相关定义
#define DRIVER_VERSION KERNEL_VERSION(0, 0x01, 0x02) // 驱动版本号 (主.次.修订)
// 如果内核未定义数字增益控制ID,则使用模拟增益ID作为替代
#ifndef V4L2_CID_DIGITAL_GAIN
#define V4L2_CID_DIGITAL_GAIN V4L2_CID_GAIN
#endif
// MIPI CSI-2 数据传输速率定义 (单位: Hz)
#define MIPI_FREQ_360M 360000000
#define MIPI_FREQ_480M 480000000
// 基于MIPI速率计算的像素输出速率 (单位: 像素/秒)
// 公式解释: (MIPI频率 * 2 [DDR]) / (每像素位数 * 通道数) * (并行数据位数 / 串行化因子)
// 假设 12位/像素, 2通道, 12位并行转2对差分线(串行化因子通常为6或7,这里按12算可能简化)
#define PIXEL_RATE_WITH_360M (MIPI_FREQ_360M * 2 / 12 * 2)
#define PIXEL_RATE_WITH_480M (MIPI_FREQ_480M * 2 / 12 * 2)
// 设备树属性名,用于获取摄像头HDR模式配置
#define OF_CAMERA_HDR_MODE "rockchip,camera-hdr-mode"
// 传感器外部时钟 (XCLK) 频率 (24MHz)
#define OV02K10_XVCLK_FREQ 24000000
// 传感器芯片ID (0x530243)
#define CHIP_ID 0x530243
// 读取芯片ID的寄存器地址
#define OV02K10_REG_CHIP_ID 0x300a
// 控制模式寄存器 (流模式/待机模式)
#define OV02K10_REG_CTRL_MODE 0x0100
#define OV02K10_MODE_SW_STANDBY 0x0 // 软件待机模式
#define OV02K10_MODE_STREAMING BIT(0) // 流模式 (开始输出图像数据)
// 曝光时间相关定义
#define OV02K10_EXPOSURE_MIN 1 // 最小曝光行数
#define OV02K10_EXPOSURE_STEP 1 // 曝光调整步长
#define OV02K10_VTS_MAX 0xffff // 垂直总行数(VTS)最大值
// 长曝光、中曝光、短曝光(用于HDR)的高字节寄存器地址
#define OV02K10_REG_EXP_LONG_H 0x3501
#define OV02K10_REG_EXP_MID_H 0x3541
#define OV02K10_REG_EXP_VS_H 0x3581 // 注意: 注释是VS(超短?),但代码里是MID和VS混合使用
// HCG(高转换增益)/LCG(低转换增益)切换寄存器
#define OV02K10_REG_HCG_SWITCH 0x376C
// 长、中、短曝光的模拟增益寄存器地址
#define OV02K10_REG_AGAIN_LONG_H 0x3508
#define OV02K10_REG_AGAIN_MID_H 0x3548
#define OV02K10_REG_AGAIN_VS_H 0x3588
// 长、中、短曝光的数字增益寄存器地址
#define OV02K10_REG_DGAIN_LONG_H 0x350A
#define OV02K10_REG_DGAIN_MID_H 0x354A
#define OV02K10_REG_DGAIN_VS_H 0x358A
// 增益控制范围定义
#define OV02K10_GAIN_MIN 0x10 // 最小增益值 (模拟增益起始点)
#define OV02K10_GAIN_MAX 0xF7C // 最大增益值 (模拟+数字增益组合)
#define OV02K10_GAIN_STEP 1 // 增益调整步长
#define OV02K10_GAIN_DEFAULT 0x10 // 默认增益值
// 传感器寄存器组更新机制相关定义
#define OV02K10_GROUP_UPDATE_ADDRESS 0x3208 // 组更新命令寄存器地址
#define OV02K10_GROUP_UPDATE_START_DATA 0x00 // 开始组更新命令值
#define OV02K10_GROUP_UPDATE_END_DATA 0x10 // 结束组更新命令值
#define OV02K10_GROUP_UPDATE_LAUNCH 0xA0 // 启动组更新命令值
// 软件复位寄存器
#define OV02K10_SOFTWARE_RESET_REG 0x0103
// 辅助宏:从32位曝光值中提取高8位和低8位
#define OV02K10_FETCH_MSB_BYTE_EXP(VAL) (((VAL) >> 8) & 0xFF)
#define OV02K10_FETCH_LSB_BYTE_EXP(VAL) ((VAL)&0xFF)
// 辅助宏:处理增益值到寄存器格式的转换 (具体作用需结合代码上下文)
#define OV02K10_FETCH_LSB_GAIN(VAL) (((VAL) << 4) & 0xf0)
#define OV02K10_FETCH_MSB_GAIN(VAL) (((VAL) >> 4) & 0x1f)
// 测试图案控制寄存器
#define OV02K10_REG_TEST_PATTERN 0x50C0
#define OV02K10_TEST_PATTERN_ENABLE 0x80 // 使能测试图案
#define OV02K10_TEST_PATTERN_DISABLE 0x0 // 禁用测试图案
// 垂直总行数(VTS)寄存器
#define OV02K10_REG_VTS 0x380e
// 特殊值,标记寄存器列表结束
#define REG_NULL 0xFFFF
// 寄存器值长度定义
#define OV02K10_REG_VALUE_08BIT 1 // 8位值
#define OV02K10_REG_VALUE_16BIT 2 // 16位值
#define OV02K10_REG_VALUE_24BIT 3 // 24位值
// MIPI CSI-2 数据通道数 (2通道)
#define OV02K10_LANES 2
// 设备树中引脚控制状态名称
#define OF_CAMERA_PINCTRL_STATE_DEFAULT "rockchip,camera_default" // 默认状态
#define OF_CAMERA_PINCTRL_STATE_SLEEP "rockchip,camera_sleep" // 睡眠状态
// 传感器名称
#define OV02K10_NAME "ov02k10"
// 镜像/翻转控制寄存器及位掩码
#define OV02K10_FLIP_REG 0x3820
#define MIRROR_BIT_MASK BIT(1) // 水平镜像位掩码
#define FLIP_BIT_MASK (BIT(2) | BIT(3)) // 垂直翻转位掩码 (注意代码中使用了BIT(2)|BIT(3))
// 定义使用系统调试接口 (sysfs)
//#define USED_SYS_DEBUG // 默认被注释掉,实际未启用
// 传感器电源名称数组 (模拟电源、数字IO电源、数字核心电源)
static const char *const ov02k10_supply_names[] = {
"avdd", // Analog power (模拟电源)
"dovdd", // Digital I/O power (数字IO电源)
"dvdd", // Digital core power (数字核心电源)
};
#define OV02K10_NUM_SUPPLIES ARRAY_SIZE(ov02k10_supply_names) // 电源数量
/****************************** 数据结构定义 ******************************/
// 寄存器值结构体 (地址 + 值)
struct regval {
u16 addr; // 寄存器地址
u8 val; // 要写入的值
};
// 传感器工作模式结构体
struct ov02k10_mode {
u32 bus_fmt; // 媒体总线格式 (MEDIA_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; // HDR模式 (NO_HDR, HDR_X2等)
u32 vc[PAD_MAX]; // 虚拟通道映射 (用于HDR多帧数据流)
};
// 传感器主数据结构体
struct ov02k10 {
struct i2c_client *client; // I2C客户端结构体
struct clk *xvclk; // 外部时钟 (XCLK)
struct gpio_desc *power_gpio; // 电源使能GPIO描述符
struct gpio_desc *reset_gpio; // 硬件复位GPIO描述符
struct gpio_desc *pwdn_gpio; // 掉电(Power Down) GPIO描述符
struct regulator_bulk_data supplies[OV02K10_NUM_SUPPLIES]; // 电源调节器数组
struct pinctrl *pinctrl; // 引脚控制状态
struct pinctrl_state *pins_default; // 默认引脚状态
struct pinctrl_state *pins_sleep; // 睡眠引脚状态
struct v4l2_subdev subdev; // V4L2子设备
struct media_pad pad; // Media Controller 媒体接口
struct v4l2_ctrl_handler ctrl_handler; // V4L2控制处理器
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 *test_pattern; // 测试图案控制
struct v4l2_ctrl *pixel_rate; // 像素输出速率控制
struct v4l2_ctrl *link_freq; // MIPI链路频率控制
struct v4l2_ctrl *h_flip; // 水平镜像控制
struct v4l2_ctrl *v_flip; // 垂直翻转控制
struct mutex mutex; // 互斥锁,保护并发访问
bool streaming; // 流状态标志 (是否在输出数据)
bool power_on; // 电源状态标志
const struct ov02k10_mode *cur_mode; // 当前工作模式指针
u32 cfg_num; // 支持的配置模式数量
u32 module_index; // 相机模块索引 (用于多摄)
const char *module_facing; // 模块朝向 ("back", "front")
const char *module_name; // 模块名称
const char *len_name; // 镜头名称
bool has_init_exp; // 是否有初始曝光设置 (用于Pre-ISP HDR)
struct preisp_hdrae_exp_s init_hdrae_exp; // Pre-ISP HDR初始曝光设置
bool long_hcg; // 长曝光帧是否使用高转换增益(HCG)
bool middle_hcg; // 中曝光帧是否使用高转换增益(HCG)
bool short_hcg; // 短曝光帧是否使用高转换增益(HCG)
u32 flip; // 当前镜像/翻转寄存器值缓存
};
// 辅助宏:从v4l2_subdev获取ov02k10结构体
#define to_ov02k10(sd) container_of(sd, struct ov02k10, subdev)
/****************************** 寄存器配置表 ******************************/
/*
* Xclk 24Mhz - 全局寄存器配置 (适用于所有模式的基础配置)
*/
static const struct regval ov02k10_global_regs[] = {
// ... (大量具体的寄存器地址和值)
{REG_NULL, 0x00}, // 列表结束标记
};
/* 线性模式, 12-bit, 1920x1080 分辨率寄存器配置 */
static const struct regval ov02k10_linear12bit_1920x1080_regs[] = {
// ... (该模式特有的寄存器配置)
{REG_NULL, 0x00}, // 列表结束标记
};
/* HDR模式, 12-bit, 1920x1080 分辨率寄存器配置 */
static const struct regval ov02k10_hdr12bit_1920x1080_regs[] = {
// ... (该模式特有的寄存器配置,用于2帧HDR)
{REG_NULL, 0x00}, // 列表结束标记
};
/****************************** 支持的传感器模式 ******************************/
/*
* 宽度和高度必须配置为与传感器当前输出分辨率相同。
* ISP的输入宽度需要16字节对齐。
* ISP的输入高度需要8字节对齐。
* 如果宽度或高度不满足对齐规则,可以使用以下函数配置裁剪参数以裁剪出合适的分辨率。
* struct v4l2_subdev_pad_ops {
* .get_selection
* }
*/
static const struct ov02k10_mode supported_modes[] = {
{ // 模式 0: 线性模式 (非HDR)
.bus_fmt = MEDIA_BUS_FMT_SBGGR12_1X12, // RAW Bayer 12bit, 1x12打包
.width = 1920,
.height = 1080,
.max_fps = { // 最大帧率 30fps (10000/300000 = 1/30)
.numerator = 10000,
.denominator = 300000,
},
.exp_def = 0x067a, // 默认曝光行数
.hts_def = 0x04c8 * 2, // 默认水平总像素(包括消隐)
.vts_def = 0x0b7c, // 默认垂直总行数(包括消隐)
.reg_list = ov02k10_linear12bit_1920x1080_regs, // 寄存器配置表
.hdr_mode = NO_HDR, // 非HDR模式
.vc[PAD0] = V4L2_MBUS_CSI2_CHANNEL_0, // 数据输出到虚拟通道0
},
{ // 模式 1: 2帧HDR模式 (长+短曝光)
.bus_fmt = MEDIA_BUS_FMT_SBGGR12_1X12, // RAW Bayer 12bit, 1x12打包
.width = 1920,
.height = 1080,
.max_fps = { // 最大帧率 30fps
.numerator = 10000,
.denominator = 300000,
},
.exp_def = 0x026c, // 默认曝光行数 (可能是短曝光)
.hts_def = 0x0420 * 2, // 默认水平总像素
.vts_def = 0x06a8, // 默认垂直总行数
.reg_list = ov02k10_hdr12bit_1920x1080_regs, // HDR寄存器配置表
.hdr_mode = HDR_X2, // 2帧HDR模式
.vc[PAD0] = V4L2_MBUS_CSI2_CHANNEL_1, // 通道1用途 (注释与实际代码可能需核对)
.vc[PAD1] = V4L2_MBUS_CSI2_CHANNEL_0, // L(长曝光)-> CSI WR0 (注释)
.vc[PAD2] = V4L2_MBUS_CSI2_CHANNEL_1, // 通道1用途
.vc[PAD3] = V4L2_MBUS_CSI2_CHANNEL_1, // M(中曝光?)/S(短曝光?)-> CSI WR2 (注释)
},
};
// MIPI链路频率菜单项 (对应上面定义的频率)
static const s64 link_freq_menu_items[] = {
MIPI_FREQ_360M, // 360 MHz (用于线性模式)
MIPI_FREQ_480M, // 480 MHz (用于HDR模式,更高带宽)
};
// 测试图案菜单选项文本
static const char *const ov02k10_test_pattern_menu[] = {
"Disabled", // 禁用
"Vertical Color Bar Type 1", // 垂直彩条类型1
"Vertical Color Bar Type 2", // 垂直彩条类型2
"Vertical Color Bar Type 3", // 垂直彩条类型3
"Vertical Color Bar Type 4" // 垂直彩条类型4
};
/****************************** I2C 寄存器读写函数 ******************************/
/* 写寄存器 (最多同时写4个字节的值)
* @client: I2C 客户端
* @reg: 16位寄存器地址
* @len: 要写入的数据长度 (1, 2, 3, 4)
* @val: 要写入的数据值
* 返回: 0 成功, 负值 失败
*/
static int ov02k10_write_reg(struct i2c_client *client, u16 reg,
u32 len, u32 val)
{
u32 buf_i, val_i;
u8 buf[6]; // 缓冲区: 2字节地址 + 最多4字节数据
u8 *val_p;
__be32 val_be; // 大端格式的32位值 (用于处理字节序)
if (len > 4) // 长度检查
return -EINVAL;
// 填充寄存器地址 (高字节在前)
buf[0] = reg >> 8; // 地址高8位
buf[1] = reg & 0xff; // 地址低8位
// 将值转换为大端字节序 (网络字节序)
val_be = cpu_to_be32(val);
val_p = (u8 *)&val_be; // 指向大端值字节的指针
// 根据长度决定从大端值的哪个字节开始复制
buf_i = 2; // 缓冲区索引从2开始 (地址占前2字节)
val_i = 4 - len; // 数据起始字节索引 (大端: 高位在前)
// 复制数据字节到缓冲区
while (val_i < 4)
buf[buf_i++] = val_p[val_i++];
// 通过I2C发送数据 (地址 + 数据)
if (i2c_master_send(client, buf, len + 2) != len + 2)
return -EIO; // 发送失败
return 0; // 成功
}
/* 写寄存器数组
* @client: I2C 客户端
* @regs: 寄存器值数组 (struct regval)
* 返回: 0 成功, 负值 失败 (遇到第一个失败即返回)
*/
static int ov02k10_write_array(struct i2c_client *client,
const struct regval *regs)
{
u32 i;
int ret = 0;
// 遍历数组直到遇到 REG_NULL 标记
for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++) {
// 写单个寄存器 (地址 + 8位值)
ret |= ov02k10_write_reg(client, regs[i].addr,
OV02K10_REG_VALUE_08BIT, regs[i].val);
}
return ret;
}
/* 读寄存器 (最多同时读4个字节的值)
* @client: I2C 客户端
* @reg: 16位寄存器地址
* @len: 要读取的数据长度 (1, 2, 3, 4)
* @val: 指向存储读取值的u32变量的指针
* 返回: 0 成功, 负值 失败
*/
static int ov02k10_read_reg(struct i2c_client *client,
u16 reg,
unsigned int len,
u32 *val)
{
struct i2c_msg msgs[2]; // 需要两个I2C消息: 写地址 + 读数据
u8 *data_be_p;
__be32 data_be = 0; // 存储读取的大端数据
__be16 reg_addr_be = cpu_to_be16(reg); // 地址转为大端
int ret;
if (len > 4 || !len) // 长度检查
return -EINVAL;
data_be_p = (u8 *)&data_be; // 指向大端存储的指针
/* 消息 0: 写寄存器地址 */
msgs[0].addr = client->addr; // I2C设备地址
msgs[0].flags = 0; // 写标志
msgs[0].len = 2; // 地址长度 (2字节)
msgs[0].buf = (u8 *)®_addr_be; // 地址数据 (大端)
/* 消息 1: 从寄存器读数据 */
msgs[1].addr = client->addr; // I2C设备地址
msgs[1].flags = I2C_M_RD; // 读标志
msgs[1].len = len; // 要读取的数据长度
msgs[1].buf = &data_be_p[4 - len]; // 数据存储位置 (大端高位在前)
// 执行I2C传输
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret != ARRAY_SIZE(msgs)) // 检查是否成功发送了两个消息
return -EIO;
// 将大端数据转换为主机字节序并存入*val
*val = be32_to_cpu(data_be);
return 0;
}
/****************************** 模式选择与格式设置 ******************************/
/* 计算目标分辨率与某个模式的分辨率差异 (用于找最匹配模式)
* @mode: 候选模式
* @framefmt: 请求的帧格式
* 返回: 宽度差 + 高度差 (绝对值和)
*/
static int ov02k10_get_reso_dist(const struct ov02k10_mode *mode,
struct v4l2_mbus_framefmt *framefmt)
{
return abs(mode->width - framefmt->width) +
abs(mode->height - framefmt->height);
}
/* 寻找与请求格式最匹配的传感器模式
* @ov02k10: ov02k10 设备结构体
* @fmt: 包含请求格式的 v4l2_subdev_format 结构体
* 返回: 指向最佳匹配模式的指针
*/
static const struct ov02k10_mode *
ov02k10_find_best_fit(struct ov02k10 *ov02k10, 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; // 当前最小差异 (初始为-1)
unsigned int i;
// 遍历所有支持的模式
for (i = 0; i < ov02k10->cfg_num; i++) {
// 计算当前模式与请求格式的分辨率差异
dist = ov02k10_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]; // 返回最佳匹配模式
}
/* V4L2子设备操作: 设置格式 (S_FMT)
* 应用调用或驱动内部设置格式时调用
*/
static int ov02k10_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_format *fmt)
{
struct ov02k10 *ov02k10 = to_ov02k10(sd);
const struct ov02k10_mode *mode;
s64 h_blank, vblank_def;
u64 dst_link_freq = 0;
u64 dst_pixel_rate = 0;
mutex_lock(&ov02k10->mutex); // 加锁保护
// 1. 寻找最佳匹配模式
mode = ov02k10_find_best_fit(ov02k10, fmt);
// 2. 用找到的模式填充返回的格式信息
fmt->format.code = mode->bus_fmt; // 总线格式
fmt->format.width = mode->width; // 宽度
fmt->format.height = mode->height; // 高度
fmt->format.field = V4L2_FIELD_NONE; // 逐行扫描
// 3. 处理格式设置请求
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
// TRY 格式 (仅用于预览/测试,不实际配置硬件)
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
// 将格式存储到try配置中
*v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format;
#else
mutex_unlock(&ov02k10->mutex);
return -ENOTTY; // 不支持 TRY 格式
#endif
} else {
// ACTIVE 格式 (实际设置硬件)
ov02k10->cur_mode = mode; // 更新当前模式
// 计算并更新 hblank (水平消隐) 控制范围
h_blank = mode->hts_def - mode->width; // 默认消隐像素数
__v4l2_ctrl_modify_range(ov02k10->hblank, h_blank,
h_blank, 1, h_blank); // 范围设为固定值
// 计算并更新 vblank (垂直消隐) 控制范围
vblank_def = mode->vts_def - mode->height; // 默认消隐行数
__v4l2_ctrl_modify_range(ov02k10->vblank, vblank_def,
OV02K10_VTS_MAX - mode->height,
1, vblank_def); // 范围: 默认值 ~ 最大值-高度
// 根据模式 (HDR/非HDR) 设置链路频率和像素率控制值
if (mode->hdr_mode == NO_HDR) {
dst_link_freq = 0; // 菜单索引0 -> 360M
dst_pixel_rate = PIXEL_RATE_WITH_360M;
} else if (mode->hdr_mode == HDR_X2) {
dst_link_freq = 1; // 菜单索引1 -> 480M
dst_pixel_rate = PIXEL_RATE_WITH_480M;
}
// 设置像素率控制值
__v4l2_ctrl_s_ctrl_int64(ov02k10->pixel_rate,
dst_pixel_rate);
// 设置链路频率控制值
__v4l2_ctrl_s_ctrl(ov02k10->link_freq,
dst_link_freq);
}
mutex_unlock(&ov02k10->mutex); // 解锁
return 0;
}
/* V4L2子设备操作: 获取格式 (G_FMT)
* 获取当前或TRY格式
*/
static int ov02k10_get_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_format *fmt)
{
struct ov02k10 *ov02k10 = to_ov02k10(sd);
const struct ov02k10_mode *mode = ov02k10->cur_mode; // 当前模式
mutex_lock(&ov02k10->mutex); // 加锁
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
// 获取TRY格式
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
#else
mutex_unlock(&ov02k10->mutex);
return -ENOTTY; // 不支持 TRY 格式
#endif
} else {
// 获取ACTIVE格式 (硬件实际使用的格式)
fmt->format.width = mode->width; // 当前宽度
fmt->format.height = mode->height; // 当前高度
fmt->format.code = mode->bus_fmt; // 当前总线格式
fmt->format.field = V4L2_FIELD_NONE; // 当前扫描方式
// 对于HDR模式,在reserved[0]中返回虚拟通道信息
if (fmt->pad < PAD_MAX && mode->hdr_mode != NO_HDR)
fmt->reserved[0] = mode->vc[fmt->pad];
else
fmt->reserved[0] = mode->vc[PAD0]; // 非HDR或默认通道
}
mutex_unlock(&ov02k10->mutex); // 解锁
return 0;
}
/* V4L2子设备操作: 枚举媒体总线格式 (ENUM_MBUS_CODE)
* 列出传感器支持的RAW格式 (这里只支持一种)
*/
static int ov02k10_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_mbus_code_enum *code)
{
struct ov02k10 *ov02k10 = to_ov02k10(sd);
if (code->index != 0) // 只支持一种格式 (索引0)
return -EINVAL;
code->code = ov02k10->cur_mode->bus_fmt; // 返回当前模式的总线格式
return 0;
}
/* V4L2子设备操作: 枚举帧尺寸 (ENUM_FRAME_SIZE)
* 列出传感器支持的分辨率
*/
static int ov02k10_enum_frame_sizes(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_frame_size_enum *fse)
{
struct ov02k10 *ov02k10 = to_ov02k10(sd);
// 检查索引是否有效
if (fse->index >= ov02k10->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;
}
/****************************** 测试图案控制 ******************************/
/* 启用或禁用测试图案
* @ov02k10: ov02k10 设备结构体
* @pattern: 测试图案类型 (0=禁用, 1~4=不同类型彩条)
* 返回: 0 成功, 负值 失败
*/
static int ov02k10_enable_test_pattern(struct ov02k10 *ov02k10, u32 pattern)
{
u32 val;
// 计算要写入寄存器的值
if (pattern)
val = (pattern - 1) | OV02K10_TEST_PATTERN_ENABLE; // 类型值 + 使能位
else
val = OV02K10_TEST_PATTERN_DISABLE; // 禁用
// 写入测试图案控制寄存器
return ov02k10_write_reg(ov02k10->client, OV02K10_REG_TEST_PATTERN,
OV02K10_REG_VALUE_08BIT, val);
}
/****************************** 帧间隔控制 ******************************/
/* V4L2子设备操作: 获取帧间隔 (G_FRAME_INTERVAL)
* 返回当前模式的帧间隔
*/
static int ov02k10_g_frame_interval(struct v4l2_subdev *sd,
struct v4l2_subdev_frame_interval *fi)
{
struct ov02k10 *ov02k10 = to_ov02k10(sd);
const struct ov02k10_mode *mode = ov02k10->cur_mode; // 当前模式
fi->interval = mode->max_fps; // 填充帧间隔 (分子/分母结构)
return 0;
}
/****************************** MIPI 配置 ******************************/
/* V4L2子设备操作: 获取媒体总线配置 (G_MBUS_CONFIG)
* 描述传感器到CSI接收器的物理连接特性
*/
static int ov02k10_g_mbus_config(struct v4l2_subdev *sd, unsigned int pad_id,
struct v4l2_mbus_config *config)
{
struct ov02k10 *ov02k10 = to_ov02k10(sd);
const struct ov02k10_mode *mode = ov02k10->cur_mode; // 当前模式
u32 val = 0; // 用于构建标志位
// 根据模式 (HDR/非HDR) 设置不同的标志
if (mode->hdr_mode == NO_HDR)
val = 1 << (OV02K10_LANES - 1) | // 通道数掩码 (2通道: 1<<(2-1)=0x02)
V4L2_MBUS_CSI2_CHANNEL_0 | // 数据使用通道0
V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; // 使用连续时钟
if (mode->hdr_mode == HDR_X2)
val = 1 << (OV02K10_LANES - 1) | // 通道数掩码
V4L2_MBUS_CSI2_CHANNEL_0 | // 通道0 (可能用于控制信息)
V4L2_MBUS_CSI2_CONTINUOUS_CLOCK |
V4L2_MBUS_CSI2_CHANNEL_1; // 通道1 (用于图像数据)
// 填充返回的配置结构体
config->type = V4L2_MBUS_CSI2_DPHY; // MIPI CSI-2 D-PHY 类型
config->flags = val; // 组合后的标志位
return 0;
}
/****************************** 模块信息 ******************************/
/* 填充模块信息结构体 (用于Rockchip特定模块信息)
*/
static void ov02k10_get_module_inf(struct ov02k10 *ov02k10,
struct rkmodule_inf *inf)
{
memset(inf, 0, sizeof(*inf)); // 清空结构体
// 复制传感器名称
strlcpy(inf->base.sensor, OV02K10_NAME, sizeof(inf->base.sensor));
// 复制模块名称
strlcpy(inf->base.module, ov02k10->module_name,
sizeof(inf->base.module));
// 复制镜头名称
strlcpy(inf->base.lens, ov02k10->len_name, sizeof(inf->base.lens));
}
/****************************** HDR 曝光控制 (Pre-ISP) ******************************/
/* 设置 HDR 曝光参数 (由Pre-ISP调用)
* @ov02k10: ov02k10 设备结构体
* @ae: 包含长、中、短曝光和增益信息的结构体
* 返回: 0 成功, 负值 失败
*/
static int ov02k10_set_hdrae(struct ov02k10 *ov02k10,
struct preisp_hdrae_exp_s *ae)
{
u32 l_exp_time, m_exp_time, s_exp_time; // 长、中、短曝光时间
u32 l_a_gain, m_a_gain, s_a_gain; // 长、中、短模拟增益
u32 l_d_gain = 1024; // 长曝光数字增益 (默认1.0)
u32 m_d_gain = 1024; // 中曝光数字增益 (默认1.0)
int ret = 0;
u8 l_cg_mode = 0; // 长曝光转换增益模式 (HCG/LCG)
u8 m_cg_mode = 0; // 中曝光转换增益模式
u8 s_cg_mode = 0; // 短曝光转换增益模式
u32 gain_switch = 0; // 从寄存器读取的当前增益切换状态
u8 is_need_switch = 0; // 是否需要更新增益切换寄存器
// 处理初始曝光设置 (在流启动前记录)
if (!ov02k10->has_init_exp && !ov02k10->streaming) {
ov02k10->init_hdrae_exp = *ae; // 保存初始设置
ov02k10->has_init_exp = true; // 标记已有初始设置
dev_dbg(&ov02k10->client->dev, "ov02k10 don't stream, record exp for hdr!\n");
return ret;
}
// 从Pre-ISP结构体中提取参数
l_exp_time = ae->long_exp_reg;
m_exp_time = ae->middle_exp_reg;
s_exp_time = ae->short_exp_reg;
l_a_gain = ae->long_gain_reg;
m_a_gain = ae->middle_gain_reg;
s_a_gain = ae->short_gain_reg;
l_cg_mode = ae->long_cg_mode;
m_cg_mode = ae->middle_cg_mode;
s_cg_mode = ae->short_cg_mode;
dev_dbg(&ov02k10->client->dev,
"rev exp:M_exp:0x%x,0x%x,cg %d,S_exp:0x%x,0x%x,cg %d\n",
m_exp_time, m_a_gain, m_cg_mode,
s_exp_time, s_a_gain, s_cg_mode);
// OV02K10 的 HDR_X2 模式使用长帧和短帧 (代码里用Middle和VS表示)
if (ov02k10->cur_mode->hdr_mode == HDR_X2) {
// 将Pre-ISP的中/短映射到传感器的长/短
l_a_gain = m_a_gain;
l_exp_time = m_exp_time;
l_cg_mode = m_cg_mode;
m_a_gain = s_a_gain;
m_exp_time = s_exp_time;
m_cg_mode = s_cg_mode;
}
// 读取当前的增益切换寄存器状态
ret = ov02k10_read_reg(ov02k10->client, OV02K10_REG_HCG_SWITCH,
OV02K10_REG_VALUE_08BIT, &gain_switch);
// 检查长曝光是否需要切换增益模式 (HCG <-> LCG)
if (ov02k10->long_hcg && l_cg_mode == GAIN_MODE_LCG) {
gain_switch |= 0x10; // 设置位使能LCG
ov02k10->long_hcg = false; // 更新状态
is_need_switch++; // 标记需要更新寄存器
} else if (!ov02k10->long_hcg && l_cg_mode == GAIN_MODE_HCG) {
gain_switch &= 0xef; // 清除位使能HCG
ov02k10->long_hcg = true; // 更新状态
is_need_switch++; // 标记需要更新寄存器
}
// 检查中曝光是否需要切换增益模式 (HCG <-> LCG)
if (ov02k10->middle_hcg && m_cg_mode == GAIN_MODE_LCG) {
gain_switch |= 0x20; // 设置位使能LCG
ov02k10->middle_hcg = false; // 更新状态
is_need_switch++; // 标记需要更新寄存器
} else if (!ov02k10->middle_hcg && m_cg_mode == GAIN_MODE_HCG) {
gain_switch &= 0xdf; // 清除位使能HCG
ov02k10->middle_hcg = true; // 更新状态
is_need_switch++; // 标记需要更新寄存器
}
// 处理模拟增益和数字增益:
// 如果模拟增益超过最大值(248), 超出部分用数字增益补偿
if (l_a_gain > 248) {
l_d_gain = l_a_gain * 1024 / 248; // 计算所需的数字增益
l_a_gain = 248; // 模拟增益设为最大值
}
if (m_a_gain > 248) {
m_d_gain = m_a_gain * 1024 / 248; // 计算所需的数字增益
m_a_gain = 248; // 模拟增益设为最大值
}
// 写入长曝光参数
ret |= ov02k10_write_reg(ov02k10->client,
OV02K10_REG_AGAIN_LONG_H, // 长曝光模拟增益寄存器
OV02K10_REG_VALUE_16BIT,
(l_a_gain << 4) & 0xff0); // 格式转换
ret |= ov02k10_write_reg(ov02k10->client,
OV02K10_REG_DGAIN_LONG_H, // 长曝光数字增益寄存器
OV02K10_REG_VALUE_24BIT,
(l_d_gain << 6) & 0xfffc0); // 格式转换
ret |= ov02k10_write_reg(ov02k10->client,
OV02K10_REG_EXP_LONG_H, // 长曝光时间寄存器
OV02K10_REG_VALUE_16BIT,
l_exp_time);
// 写入中曝光参数 (在HDR_X2模式下代表短曝光)
ret |= ov02k10_write_reg(ov02k10->client,
OV02K10_REG_AGAIN_MID_H, // 中曝光模拟增益寄存器
OV02K10_REG_VALUE_16BIT,
(m_a_gain << 4) & 0xff0); // 格式转换
ret |= ov02k10_write_reg(ov02k10->client,
OV02K10_REG_DGAIN_MID_H, // 中曝光数字增益寄存器
OV02K10_REG_VALUE_24BIT,
(m_d_gain << 6) & 0xfffc0); // 格式转换
ret |= ov02k10_write_reg(ov02k10->client,
OV02K10_REG_EXP_MID_H, // 中曝光时间寄存器
OV02K10_REG_VALUE_16BIT,
m_exp_time);
// 如果增益模式发生了变化,需要更新增益切换寄存器
if (is_need_switch) {
// 使用组更新机制确保参数同时生效
ret |= ov02k10_write_reg(ov02k10->client,
OV02K10_GROUP_UPDATE_ADDRESS, // 组更新命令寄存器
OV02K10_REG_VALUE_08BIT,
OV02K10_GROUP_UPDATE_START_DATA); // 开始组更新
ret |= ov02k10_write_reg(ov02k10->client,
OV02K10_REG_HCG_SWITCH, // 增益切换寄存器
OV02K10_REG_VALUE_08BIT,
gain_switch); // 写入新的增益模式
ret |= ov02k10_write_reg(ov02k10->client,
OV02K10_GROUP_UPDATE_ADDRESS,
OV02K10_REG_VALUE_08BIT,
OV02K10_GROUP_UPDATE_END_DATA); // 结束组更新
ret |= ov02k10_write_reg(ov02k10->client,
OV02K10_GROUP_UPDATE_ADDRESS,
OV02K10_REG_VALUE_08BIT,
OV02K10_GROUP_UPDATE_LAUNCH); // 启动组更新
}
return ret;
}
/****************************** 转换增益控制 ******************************/
/* 设置转换增益模式 (HCG/LCG)
* @ov02k10: ov02k10 设备结构体
* @cg: 指向增益模式值的指针 (GAIN_MODE_LCG / GAIN_MODE_HCG)
* 返回: 0 成功, 负值 失败
*/
static int ov02k10_set_conversion_gain(struct ov02k10 *ov02k10, u32 *cg)
{
int ret = 0;
struct i2c_client *client = ov02k10->client;
u32 cur_cg = *cg; // 目标增益模式
u32 val = 0; // 寄存器当前值
s32 is_need_change = 0; // 是否需要改变
dev_dbg(&ov02k10->client->dev, "set conversion gain %d\n", cur_cg);
mutex_lock(&ov02k10->mutex); // 加锁保护
// 读取当前的增益切换寄存器值
ret = ov02k10_read_reg(client,
OV02K10_REG_HCG_SWITCH,
OV02K10_REG_VALUE_08BIT,
&val);
// 检查长曝光是否需要切换
if (ov02k10->long_hcg && cur_cg == GAIN_MODE_LCG) {
val |= 0x10; // 设置位 (LCG)
is_need_change++; // 标记需要改变
ov02k10->long_hcg = false; // 更新状态
} else if (!ov02k10->long_hcg && cur_cg == GAIN_MODE_HCG) {
val &= 0xef; // 清除位 (HCG)
is_need_change++; // 标记需要改变
ov02k10->long_hcg = true; // 更新状态
}
// 如果需要改变
if (is_need_change) {
// 使用组更新机制
ret |= ov02k10_write_reg(client,
OV02K10_GROUP_UPDATE_ADDRESS,
OV02K10_REG_VALUE_08BIT,
OV02K10_GROUP_UPDATE_START_DATA); // 开始组更新
ret |= ov02k10_write_reg(client,
OV02K10_REG_HCG_SWITCH,
OV02K10_REG_VALUE_08BIT,
val); // 写入新值
ret |= ov02k10_write_reg(client,
OV02K10_GROUP_UPDATE_ADDRESS,
OV02K10_REG_VALUE_08BIT,
OV02K10_GROUP_UPDATE_END_DATA); // 结束组更新
ret |= ov02k10_write_reg(client,
OV02K10_GROUP_UPDATE_ADDRESS,
OV02K10_REG_VALUE_08BIT,
OV02K10_GROUP_UPDATE_LAUNCH); // 启动组更新
}
mutex_unlock(&ov02k10->mutex); // 解锁
dev_dbg(&client->dev, "set conversion gain %d, (reg,val)=(0x%x,0x%x)\n",
cur_cg, OV02K10_REG_HCG_SWITCH, val);
return ret;
}
/****************************** Sysfs 调试接口 (可选) ******************************/
// 注意: USED_SYS_DEBUG 默认被注释掉,以下代码在实际编译时通常不包含
#ifdef USED_SYS_DEBUG
// 通过sysfs设置转换增益的接口函数
// 用法: echo 0 > /sys/.../cam_s_cg (0=LCG, 1=HCG)
static ssize_t set_conversion_gain_status(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct ov02k10 *ov02k10 = to_ov02k10(sd);
int status = 0;
int ret = 0;
// 将字符串输入转换为整数
ret = kstrtoint(buf, 0, &status);
// 检查输入值有效性 (0 或 1)
if (!ret && status >= 0 && status < 2)
ov02k10_set_conversion_gain(ov02k10, &status); // 调用设置函数
else
dev_err(dev, "input 0 for LCG, 1 for HCG, cur %d\n", status); // 错误提示
return count;
}
// 定义 sysfs 属性
static struct device_attribute attributes[] = {
__ATTR(cam_s_cg, S_IWUSR, NULL, set_conversion_gain_status), // 可写属性
};
// 在设备上创建 sysfs 接口
static int add_sysfs_interfaces(struct device *dev)
{
int i;
for (i = 0; i < ARRAY_SIZE(attributes); i++)
if (device_create_file(dev, attributes + i)) // 创建属性文件
goto undo; // 失败则回滚
return 0;
undo: // 回滚: 删除已创建的属性文件
for (i--; i >= 0 ; i--)
device_remove_file(dev, attributes + i);
dev_err(dev, "%s: failed to create sysfs interface\n", __func__);
return -ENODEV;
}
#endif // USED_SYS_DEBUG
/****************************** IOCTL 处理 ******************************/
/* 自定义 IOCTL 处理函数
* 处理应用或内核其他模块发来的控制命令
*/
static long ov02k10_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
{
struct ov02k10 *ov02k10 = to_ov02k10(sd);
struct rkmodule_hdr_cfg *hdr_cfg;
long ret = 0;
u32 i, h, w;
u64 dst_link_freq = 0;
u64 dst_pixel_rate = 0;
u32 stream = 0;
switch (cmd) {
case PREISP_CMD_SET_HDRAE_EXP: // 设置Pre-ISP HDR曝光
return ov02k10_set_hdrae(ov02k10, arg);
case RKMODULE_SET_HDR_CFG: // 设置HDR模式
hdr_cfg = (struct rkmodule_hdr_cfg *)arg;
w = ov02k10->cur_mode->width;
h = ov02k10->cur_mode->height;
// 遍历支持的模式,寻找匹配分辨率和新HDR模式的配置
for (i = 0; i < ov02k10->cfg_num; i++) {
if (w == supported_modes[i].width &&
h == supported_modes[i].height &&
supported_modes[i].hdr_mode == hdr_cfg->hdr_mode) {
ov02k10->cur_mode = &supported_modes[i]; // 更新当前模式
break;
}
}
if (i == ov02k10->cfg_num) { // 未找到匹配模式
dev_err(&ov02k10->client->dev,
"not find hdr mode:%d %dx%d config\n",
hdr_cfg->hdr_mode, w, h);
ret = -EINVAL;
} else {
// 计算新的消隐值
w = ov02k10->cur_mode->hts_def - ov02k10->cur_mode->width; // 新hblank
h = ov02k10->cur_mode->vts_def - ov02k10->cur_mode->height; // 新vblank
// 更新hblank控制范围
__v4l2_ctrl_modify_range(ov02k10->hblank, w, w, 1, w);
// 更新vblank控制范围
__v4l2_ctrl_modify_range(ov02k10->vblank, h,
OV02K10_VTS_MAX - ov02k10->cur_mode->height,
1, h);
// 根据新HDR模式设置链路频率和像素率
if (ov02k10->cur_mode->hdr_mode == NO_HDR) {
dst_link_freq = 0; // 360M
dst_pixel_rate = PIXEL_RATE_WITH_360M;
} else if (ov02k10->cur_mode->hdr_mode == HDR_X2) {
dst_link_freq = 1; // 480M
dst_pixel_rate = PIXEL_RATE_WITH_480M;
}
// 设置像素率控制值
__v4l2_ctrl_s_ctrl_int64(ov02k10->pixel_rate,
dst_pixel_rate);
// 设置链路频率控制值
__v4l2_ctrl_s_ctrl(ov02k10->link_freq,
dst_link_freq);
dev_info(&ov02k10->client->dev,
"sensor mode: %d\n",
ov02k10->cur_mode->hdr_mode);
}
break;
case RKMODULE_GET_MODULE_INFO: // 获取模块信息
ov02k10_get_module_inf(ov02k10, (struct rkmodule_inf *)arg);
break;
case RKMODULE_GET_HDR_CFG: // 获取当前HDR配置
hdr_cfg = (struct rkmodule_hdr_cfg *)arg;
hdr_cfg->esp.mode = HDR_NORMAL_VC; // 固定值
hdr_cfg->hdr_mode = ov02k10->cur_mode->hdr_mode; // 当前HDR模式
break;
case RKMODULE_SET_CONVERSION_GAIN: // 设置转换增益
ret = ov02k10_set_conversion_gain(ov02k10, (u32 *)arg);
break;
case RKMODULE_SET_QUICK_STREAM: // 快速启动/停止流 (不重新配置所有寄存器)
stream = *((u32 *)arg); // 获取流状态 (1=启动, 0=停止)
if (stream) // 启动流
ret = ov02k10_write_reg(ov02k10->client, OV02K10_REG_CTRL_MODE,
OV02K10_REG_VALUE_08BIT, OV02K10_MODE_STREAMING);
else // 停止流
ret = ov02k10_write_reg(ov02k10->client, OV02K10_REG_CTRL_MODE,
OV02K10_REG_VALUE_08BIT, OV02K10_MODE_SW_STANDBY);
break;
default:
ret = -ENOIOCTLCMD; // 不支持的命令
break;
}
return ret;
}
/****************************** 兼容32位IOCTL (CONFIG_COMPAT) ******************************/
// 这部分代码确保驱动在64位内核上也能正确处理32位应用程序发出的ioctl命令。
#ifdef CONFIG_COMPAT
static long ov02k10_compat_ioctl32(struct v4l2_subdev *sd,
unsigned int cmd, unsigned long arg)
{
void __user *up = compat_ptr(arg); // 将32位地址转换为内核可用的64位地址
struct rkmodule_inf *inf;
struct rkmodule_awb_cfg *cfg;
struct rkmodule_hdr_cfg *hdr;
struct preisp_hdrae_exp_s *hdrae;
long ret;
u32 cg = 0;
u32 stream = 0;
switch (cmd) {
case RKMODULE_GET_MODULE_INFO: // 获取模块信息 (32位兼容)
... // (分配内存, 调用ioctl, 复制结果回用户空间)
break;
case RKMODULE_AWB_CFG: // 设置AWB (32位兼容)
... // (从用户空间复制数据, 调用ioctl)
break;
case RKMODULE_GET_HDR_CFG: // 获取HDR配置 (32位兼容)
... // (分配内存, 调用ioctl, 复制结果回用户空间)
break;
case RKMODULE_SET_HDR_CFG: // 设置HDR模式 (32位兼容)
... // (从用户空间复制数据, 调用ioctl)
break;
case PREISP_CMD_SET_HDRAE_EXP: // 设置HDR曝光 (32位兼容)
... // (从用户空间复制数据, 调用ioctl)
break;
case RKMODULE_SET_CONVERSION_GAIN: // 设置转换增益 (32位兼容)
... // (从用户空间复制数据, 调用ioctl)
break;
case RKMODULE_SET_QUICK_STREAM: // 快速启停流 (32位兼容)
... // (从用户空间复制数据, 调用ioctl)
break;
default:
ret = -ENOIOCTLCMD; // 不支持的命令
break;
}
return ret;
}
#endif // CONFIG_COMPAT
/****************************** 初始化转换增益 ******************************/
/* 初始化转换增益模式 (通常在流启动时调用)
*/
static int ov02k10_init_conversion_gain(struct ov02k10 *ov02k10)
{
int ret = 0;
struct i2c_client *client = ov02k10->client;
u32 val = 0;
// 读取当前的增益切换寄存器值
ret = ov02k10_read_reg(client,
OV02K10_REG_HCG_SWITCH,
OV02K10_REG_VALUE_08BIT,
&val);
// 设置所有增益模式为LCG (低转换增益) (设置0x70位)
val |= 0x70;
// 写回寄存器
ret |= ov02k10_write_reg(client,
OV02K10_REG_HCG_SWITCH,
OV02K10_REG_VALUE_08BIT,
val);
// 更新内部状态标记 (均为LCG)
ov02k10->long_hcg = false;
ov02k10->middle_hcg = false;
ov02k10->short_hcg = false;
return ret;
}
/****************************** 流控制 (启动/停止) ******************************/
/* 内部函数: 启动传感器数据流
* 1. 写入全局寄存器配置
* 2. 写入当前模式的寄存器配置
* 3. 初始化转换增益
* 4. 应用所有V4L2控件的设置
* 5. 如果有初始HDR曝光设置,应用它
* 6. 发送启动流命令
*/
static int __ov02k10_start_stream(struct ov02k10 *ov02k10)
{
int ret;
// 1. 写入基础/全局寄存器配置
ret = ov02k10_write_array(ov02k10->client, ov02k10_global_regs);
if (ret) {
dev_err(&ov02k10->client->dev,
"could not set init registers\n");
return ret;
}
// 2. 写入当前模式的特定寄存器配置
ret = ov02k10_write_array(ov02k10->client, ov02k10->cur_mode->reg_list);
if (ret)
return ret;
// 3. 初始化转换增益模式 (LCG)
ret = ov02k10_init_conversion_gain(ov02k10);
if (ret)
return ret;
// 4. 应用所有V4L2控件的当前值 (曝光、增益、测试图等)
ret = __v4l2_ctrl_handler_setup(&ov02k10->ctrl_handler);
if (ret)
return ret;
// 5. 如果在流启动前有记录初始HDR曝光设置,现在应用它
if (ov02k10->has_init_exp && ov02k10->cur_mode->hdr_mode != NO_HDR) {
ret = ov02k10_ioctl(&ov02k10->subdev,
PREISP_CMD_SET_HDRAE_EXP,
&ov02k10->init_hdrae_exp);
if (ret) {
dev_err(&ov02k10->client->dev,
"init exp fail in hdr mode\n");
return ret;
}
}
// 6. 发送启动流命令
return ov02k10_write_reg(ov02k10->client, OV02K10_REG_CTRL_MODE,
OV02K10_REG_VALUE_08BIT, OV02K10_MODE_STREAMING);
}
/* 内部函数: 停止传感器数据流
* 1. 清除初始曝光记录标志
* 2. 发送进入软件待机模式命令
*/
static int __ov02k10_stop_stream(struct ov02k10 *ov02k10)
{
ov02k10->has_init_exp = false; // 清除标志
return ov02k10_write_reg(ov02k10->client, OV02K10_REG_CTRL_MODE,
OV02K10_REG_VALUE_08BIT, OV02K10_MODE_SW_STANDBY);
}
/* V4L2子设备操作: 启动/停止流 (S_STREAM)
* 应用调用VIDIOC_STREAMON/OFF时触发
*/
static int ov02k10_s_stream(struct v4l2_subdev *sd, int on)
{
struct ov02k10 *ov02k10 = to_ov02k10(sd);
struct i2c_client *client = ov02k10->client;
int ret = 0;
mutex_lock(&ov02k10->mutex); // 加锁保护
on = !!on; // 确保on是0或1
if (on == ov02k10->streaming) // 状态未改变,直接返回
goto unlock_and_return;
if (on) { // 启动流
// 获取运行时PM引用计数 (可能唤醒设备)
ret = pm_runtime_get_sync(&client->dev);
if (ret < 0) {
pm_runtime_put_noidle(&client->dev); // 出错时递减计数
goto unlock_and_return;
}
// 调用内部启动流函数
ret = __ov02k10_start_stream(ov02k10);
if (ret) {
v4l2_err(sd, "start stream failed while write regs\n");
pm_runtime_put(&client->dev); // 出错时释放PM
goto unlock_and_return;
}
} else { // 停止流
__ov02k10_stop_stream(ov02k10); // 内部停止流函数
pm_runtime_put(&client->dev); // 释放运行时PM引用计数
}
ov02k10->streaming = on; // 更新流状态标志
unlock_and_return:
mutex_unlock(&ov02k10->mutex); // 解锁
return ret;
}
/****************************** 电源管理 ******************************/
/* V4L2子设备操作: 电源控制 (S_POWER)
* 应用打开/关闭设备节点时可能调用
*/
static int ov02k10_s_power(struct v4l2_subdev *sd, int on)
{
struct ov02k10 *ov02k10 = to_ov02k10(sd);
struct i2c_client *client = ov02k10->client;
int ret = 0;
mutex_lock(&ov02k10->mutex); // 加锁
// 检查电源状态是否已改变
if (ov02k10->power_on == !!on)
goto unlock_and_return; // 状态未变,直接返回
if (on) { // 上电
// 获取运行时PM引用计数 (可能唤醒设备)
ret = pm_runtime_get_sync(&client->dev);
if (ret < 0) {
pm_runtime_put_noidle(&client->dev);
goto unlock_and_return;
}
// 发送软件复位命令 (可选,这里执行了)
ret |= ov02k10_write_reg(ov02k10->client,
OV02K10_SOFTWARE_RESET_REG,
OV02K10_REG_VALUE_08BIT,
0x01);
usleep_range(100, 200); // 等待复位完成
ov02k10->power_on = true; // 更新电源状态
} else { // 下电
pm_runtime_put(&client->dev); // 释放运行时PM引用计数
ov02k10->power_on = false; // 更新电源状态
}
unlock_and_return:
mutex_unlock(&ov02k10->mutex); // 解锁
return ret;
}
/* 计算给定时钟周期数对应的延时时间 (微秒)
* @cycles: 时钟周期数
* 返回: 对应的微秒数 (向上取整)
*/
static inline u32 ov02k10_cal_delay(u32 cycles)
{
return DIV_ROUND_UP(cycles, OV02K10_XVCLK_FREQ / 1000 / 1000); // 周期数 / (频率/10^6)
}
/* 内部函数: 实际上电序列
* 1. 设置引脚默认状态
* 2. 设置并启用外部时钟 (XVCLK)
* 3. 控制电源GPIO (如果存在)
* 4. 启用所有电源调节器
* 5. 控制复位GPIO (如果存在)
* 6. 控制掉电(PWDN) GPIO (如果存在)
* 7. 等待传感器内部初始化完成
*/
static int __ov02k10_power_on(struct ov02k10 *ov02k10)
{
int ret;
u32 delay_us;
struct device *dev = &ov02k10->client->dev;
// 1. 设置引脚控制状态为默认 (例如配置I2C引脚复用)
if (!IS
您说得对,我提供的代码注释确实没有完整覆盖整个驱动程序。让我继续完成剩余部分的详细注释:
/****************************** 运行时电源管理 ******************************/
/* 运行时电源管理: 恢复 (唤醒设备)
* 当设备从休眠状态唤醒时调用
*/
static int ov02k10_runtime_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct ov02k10 *ov02k10 = to_ov02k10(sd);
return __ov02k10_power_on(ov02k10); // 执行完整的上电序列
}
/* 运行时电源管理: 挂起 (进入低功耗状态)
* 当设备进入休眠状态时调用
*/
static int ov02k10_runtime_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct ov02k10 *ov02k10 = to_ov02k10(sd);
__ov02k10_power_off(ov02k10); // 执行完整的下电序列
return 0;
}
/****************************** V4L2 子设备内部操作 ******************************/
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
/* 打开子设备时初始化 try 格式
* 当应用打开设备节点时调用
*/
static int ov02k10_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
{
struct ov02k10 *ov02k10 = to_ov02k10(sd);
struct v4l2_mbus_framefmt *try_fmt =
v4l2_subdev_get_try_format(sd, fh->pad, 0);
const struct ov02k10_mode *def_mode = &supported_modes[0]; // 默认模式
mutex_lock(&ov02k10->mutex);
/* 初始化 try 格式为默认模式 */
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(&ov02k10->mutex);
return 0;
}
#endif
/****************************** 帧间隔枚举 ******************************/
/* V4L2子设备操作: 枚举帧间隔 (ENUM_FRAME_INTERVAL)
* 列出传感器在特定分辨率下支持的帧率
*/
static int ov02k10_enum_frame_interval(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_frame_interval_enum *fie)
{
struct ov02k10 *ov02k10 = to_ov02k10(sd);
// 检查索引是否有效
if (fie->index >= ov02k10->cfg_num)
return -EINVAL;
// 检查请求的总线格式是否匹配该模式
if (fie->code != supported_modes[fie->index].bus_fmt)
return -EINVAL;
// 填充帧间隔信息
fie->width = supported_modes[fie->index].width;
fie->height = supported_modes[fie->index].height;
fie->interval = supported_modes[fie->index].max_fps; // 帧间隔 = 1/帧率
return 0;
}
/****************************** 裁剪区域获取 ******************************/
/* V4L2子设备操作: 获取选择区域 (CROP)
* 返回传感器的有效像素区域
*/
static int ov02k10_get_selection(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_selection *sel)
{
// 只支持获取有效像素区域边界
if (sel->target == V4L2_SEL_TGT_CROP_BOUNDS) {
sel->r.left = 0;
sel->r.width = 1920; // 传感器最大宽度
sel->r.top = 0;
sel->r.height = 1080; // 传感器最大高度
return 0;
}
return -EINVAL; // 不支持其他选择目标
}
/****************************** 电源管理操作 ******************************/
static const struct dev_pm_ops ov02k10_pm_ops = {
SET_RUNTIME_PM_OPS(ov02k10_runtime_suspend,
ov02k10_runtime_resume, NULL)
};
/****************************** V4L2 子设备操作集合 ******************************/
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
static const struct v4l2_subdev_internal_ops ov02k10_internal_ops = {
.open = ov02k10_open, // 打开子设备时的初始化
};
#endif
/* 核心操作: 电源控制、IOCTL处理 */
static const struct v4l2_subdev_core_ops ov02k10_core_ops = {
.s_power = ov02k10_s_power, // 电源控制
.ioctl = ov02k10_ioctl, // IOCTL处理
#ifdef CONFIG_COMPAT
.compat_ioctl32 = ov02k10_compat_ioctl32, // 32位兼容IOCTL
#endif
};
/* 视频操作: 流控制、帧间隔获取 */
static const struct v4l2_subdev_video_ops ov02k10_video_ops = {
.s_stream = ov02k10_s_stream, // 启动/停止流
.g_frame_interval = ov02k10_g_frame_interval, // 获取帧间隔
};
/* 板级操作: 格式控制、裁剪区域获取等 */
static const struct v4l2_subdev_pad_ops ov02k10_pad_ops = {
.enum_mbus_code = ov02k10_enum_mbus_code, // 枚举媒体总线格式
.enum_frame_size = ov02k10_enum_frame_sizes, // 枚举帧尺寸
.enum_frame_interval = ov02k10_enum_frame_interval, // 枚举帧间隔
.get_fmt = ov02k10_get_fmt, // 获取当前格式
.set_fmt = ov02k10_set_fmt, // 设置格式
.get_selection = ov02k10_get_selection, // 获取裁剪区域
.get_mbus_config = ov02k10_g_mbus_config, // 获取媒体总线配置
};
/* 整合所有操作集合 */
static const struct v4l2_subdev_ops ov02k10_subdev_ops = {
.core = &ov02k10_core_ops, // 核心操作
.video = &ov02k10_video_ops, // 视频操作
.pad = &ov02k10_pad_ops, // 板级操作
};
/****************************** 控制操作 ******************************/
/* V4L2控制设置回调
* 当应用修改控制值时调用
*/
static int ov02k10_set_ctrl(struct v4l2_ctrl *ctrl)
{
struct ov02k10 *ov02k10 = container_of(ctrl->handler,
struct ov02k10, ctrl_handler);
struct i2c_client *client = ov02k10->client;
s64 max;
int ret = 0;
u32 again, dgain;
u32 val = 0;
/* 处理控制之间的依赖关系 */
switch (ctrl->id) {
case V4L2_CID_VBLANK: // 垂直消隐控制
/* 根据新的消隐值更新最大曝光范围 */
max = ov02k10->cur_mode->height + ctrl->val - 8;
__v4l2_ctrl_modify_range(ov02k10->exposure,
ov02k10->exposure->minimum, max,
ov02k10->exposure->step,
ov02k10->exposure->default_value);
break;
}
// 确保传感器已上电
if (!pm_runtime_get_if_in_use(&client->dev))
return 0;
/* 处理各种控制命令 */
switch (ctrl->id) {
case V4L2_CID_EXPOSURE: // 曝光控制
ret = ov02k10_write_reg(ov02k10->client,
OV02K10_REG_EXP_LONG_H,
OV02K10_REG_VALUE_16BIT,
ctrl->val);
break;
case V4L2_CID_ANALOGUE_GAIN: // 模拟增益控制
/* 计算模拟增益和数字增益的组合 */
if (ctrl->val > 248) {
dgain = ctrl->val * 1024 / 248;
again = 248;
} else {
dgain = 1024;
again = ctrl->val;
}
ret = ov02k10_write_reg(ov02k10->client,
OV02K10_REG_AGAIN_LONG_H,
OV02K10_REG_VALUE_16BIT,
(again << 4) & 0xff0);
ret |= ov02k10_write_reg(ov02k10->client,
OV02K10_REG_DGAIN_LONG_H,
OV02K10_REG_VALUE_24BIT,
(dgain << 6) & 0xfffc0);
break;
case V4L2_CID_VBLANK: // 垂直消隐
ret = ov02k10_write_reg(ov02k10->client, OV02K10_REG_VTS,
OV02K10_REG_VALUE_16BIT,
ctrl->val + ov02k10->cur_mode->height);
break;
case V4L2_CID_TEST_PATTERN: // 测试图案
ret = ov02k10_enable_test_pattern(ov02k10, ctrl->val);
break;
case V4L2_CID_HFLIP: // 水平镜像
ret = ov02k10_read_reg(ov02k10->client, OV02K10_FLIP_REG,
OV02K10_REG_VALUE_08BIT,
&val);
if (ctrl->val)
val |= MIRROR_BIT_MASK;
else
val &= ~MIRROR_BIT_MASK;
ret = ov02k10_write_reg(ov02k10->client, OV02K10_FLIP_REG,
OV02K10_REG_VALUE_08BIT,
val);
if (ret == 0)
ov02k10->flip = val; // 更新缓存
break;
case V4L2_CID_VFLIP: // 垂直翻转
ret = ov02k10_read_reg(ov02k10->client, OV02K10_FLIP_REG,
OV02K10_REG_VALUE_08BIT,
&val);
if (ctrl->val)
val |= FLIP_BIT_MASK;
else
val &= ~FLIP_BIT_MASK;
ret = ov02k10_write_reg(ov02k10->client, OV02K10_FLIP_REG,
OV02K10_REG_VALUE_08BIT,
val);
if (ret == 0)
ov02k10->flip = val; // 更新缓存
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;
}
/* V4L2控制操作结构体 */
static const struct v4l2_ctrl_ops ov02k10_ctrl_ops = {
.s_ctrl = ov02k10_set_ctrl, // 设置控制回调
};
/****************************** 初始化控制 ******************************/
/* 初始化V4L2控制处理程序
* 创建并配置所有支持的V4L2控制项
*/
static int ov02k10_initialize_controls(struct ov02k10 *ov02k10)
{
const struct ov02k10_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 = &ov02k10->ctrl_handler;
mode = ov02k10->cur_mode;
// 初始化控制处理程序 (预留9个控制项空间)
ret = v4l2_ctrl_handler_init(handler, 9);
if (ret)
return ret;
handler->lock = &ov02k10->mutex; // 使用设备的互斥锁
// 创建链路频率控制 (菜单类型)
ov02k10->link_freq = v4l2_ctrl_new_int_menu(handler, NULL,
V4L2_CID_LINK_FREQ,
1, 0, link_freq_menu_items);
// 根据当前模式设置像素率
if (mode->hdr_mode == NO_HDR) {
dst_link_freq = 0;
dst_pixel_rate = PIXEL_RATE_WITH_360M;
} else if (mode->hdr_mode == HDR_X2) {
dst_link_freq = 1;
dst_pixel_rate = PIXEL_RATE_WITH_480M;
}
// 创建像素率控制
ov02k10->pixel_rate = v4l2_ctrl_new_std(handler, NULL,
V4L2_CID_PIXEL_RATE,
0, PIXEL_RATE_WITH_480M,
1, dst_pixel_rate);
// 设置链路频率和像素率初始值
__v4l2_ctrl_s_ctrl(ov02k10->link_freq, dst_link_freq);
__v4l2_ctrl_s_ctrl_int64(ov02k10->pixel_rate, dst_pixel_rate);
// 创建水平消隐控制 (只读)
h_blank = mode->hts_def - mode->width;
ov02k10->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK,
h_blank, h_blank, 1, h_blank);
if (ov02k10->hblank)
ov02k10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
// 创建垂直消隐控制
vblank_def = mode->vts_def - mode->height;
ov02k10->vblank = v4l2_ctrl_new_std(handler, &ov02k10_ctrl_ops,
V4L2_CID_VBLANK, vblank_def,
OV02K10_VTS_MAX - mode->height,
1, vblank_def);
// 创建曝光控制
exposure_max = mode->vts_def - 8;
ov02k10->exposure = v4l2_ctrl_new_std(handler, &ov02k10_ctrl_ops,
V4L2_CID_EXPOSURE, OV02K10_EXPOSURE_MIN,
exposure_max, OV02K10_EXPOSURE_STEP,
mode->exp_def);
// 创建模拟增益控制
ov02k10->anal_gain = v4l2_ctrl_new_std(handler, &ov02k10_ctrl_ops,
V4L2_CID_ANALOGUE_GAIN, OV02K10_GAIN_MIN,
OV02K10_GAIN_MAX, OV02K10_GAIN_STEP,
OV02K10_GAIN_DEFAULT);
// 创建测试图案控制 (菜单类型)
ov02k10->test_pattern = v4l2_ctrl_new_std_menu_items(handler,
&ov02k10_ctrl_ops, V4L2_CID_TEST_PATTERN,
ARRAY_SIZE(ov02k10_test_pattern_menu) - 1,
0, 0, ov02k10_test_pattern_menu);
// 创建水平镜像控制
ov02k10->h_flip = v4l2_ctrl_new_std(handler, &ov02k10_ctrl_ops,
V4L2_CID_HFLIP, 0, 1, 1, 0);
// 创建垂直翻转控制
ov02k10->v_flip = v4l2_ctrl_new_std(handler, &ov02k10_ctrl_ops,
V4L2_CID_VFLIP, 0, 1, 1, 0);
ov02k10->flip = 0; // 初始翻转状态
// 检查控制初始化错误
if (handler->error) {
ret = handler->error;
dev_err(&ov02k10->client->dev,
"Failed to init controls(%d)\n", ret);
goto err_free_handler;
}
// 关联控制处理程序到子设备
ov02k10->subdev.ctrl_handler = handler;
// 初始化HDR相关状态
ov02k10->has_init_exp = false;
ov02k10->long_hcg = false;
ov02k10->middle_hcg = false;
ov02k10->short_hcg = false;
return 0;
err_free_handler:
v4l2_ctrl_handler_free(handler); // 错误时释放控制处理程序
return ret;
}
/****************************** 传感器ID检查 ******************************/
/* 检查传感器ID是否匹配
* 通过读取芯片ID寄存器验证传感器是否正确连接
*/
static int ov02k10_check_sensor_id(struct ov02k10 *ov02k10,
struct i2c_client *client)
{
struct device *dev = &ov02k10->client->dev;
u32 id = 0;
int ret;
// 读取24位芯片ID
ret = ov02k10_read_reg(client, OV02K10_REG_CHIP_ID,
OV02K10_REG_VALUE_24BIT, &id);
if (ret)
return ret;
// 验证ID是否匹配
if (id != CHIP_ID) {
dev_err(dev, "Unexpected sensor id(%06x), ret(%d)\n", id, ret);
return -ENODEV; // 设备不匹配
}
dev_info(dev, "Detected OV%06x sensor\n", CHIP_ID);
return 0;
}
/****************************** 电源调节器配置 ******************************/
/* 配置传感器所需的电源调节器
* 从设备树获取电源名称并初始化
*/
static int ov02k10_configure_regulators(struct ov02k10 *ov02k10)
{
unsigned int i;
// 设置电源名称
for (i = 0; i < OV02K10_NUM_SUPPLIES; i++)
ov02k10->supplies[i].supply = ov02k10_supply_names[i];
// 批量获取电源调节器
return devm_regulator_bulk_get(&ov02k10->client->dev,
OV02K10_NUM_SUPPLIES,
ov02k10->supplies);
}
/****************************** 探测函数 (驱动初始化) ******************************/
/* 驱动探测函数
* 当匹配的设备在I2C总线上被发现时调用
*/
static int ov02k10_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct device_node *node = dev->of_node;
struct ov02k10 *ov02k10;
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);
// 分配设备结构体内存
ov02k10 = devm_kzalloc(dev, sizeof(*ov02k10), GFP_KERNEL);
if (!ov02k10)
return -ENOMEM;
// 从设备树读取模块信息
ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX,
&ov02k10->module_index);
ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING,
&ov02k10->module_facing);
ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME,
&ov02k10->module_name);
ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME,
&ov02k10->len_name);
if (ret) {
dev_err(dev, "could not get module information!\n");
return -EINVAL;
}
// 从设备树读取HDR模式配置
ret = of_property_read_u32(node, OF_CAMERA_HDR_MODE,
&hdr_mode);
if (ret) {
hdr_mode = NO_HDR; // 默认无HDR
dev_warn(dev, "Get hdr mode failed! no hdr default\n");
}
// 根据HDR模式选择初始工作模式
ov02k10->cfg_num = ARRAY_SIZE(supported_modes);
for (i = 0; i < ov02k10->cfg_num; i++) {
if (hdr_mode == supported_modes[i].hdr_mode) {
ov02k10->cur_mode = &supported_modes[i];
break;
}
}
ov02k10->client = client; // 保存I2C客户端
// 获取外部时钟
ov02k10->xvclk = devm_clk_get(dev, "xvclk");
if (IS_ERR(ov02k10->xvclk)) {
dev_err(dev, "Failed to get xvclk\n");
return -EINVAL;
}
// 获取电源使能GPIO
ov02k10->power_gpio = devm_gpiod_get(dev, "power", GPIOD_OUT_LOW);
if (IS_ERR(ov02k10->power_gpio))
dev_warn(dev, "Failed to get power-gpios\n");
// 获取复位GPIO
ov02k10->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(ov02k10->reset_gpio))
dev_warn(dev, "Failed to get reset-gpios\n");
// 获取掉电(PWDN) GPIO
ov02k10->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_OUT_LOW);
if (IS_ERR(ov02k10->pwdn_gpio))
dev_warn(dev, "Failed to get pwdn-gpios\n");
// 获取引脚控制状态
ov02k10->pinctrl = devm_pinctrl_get(dev);
if (!IS_ERR(ov02k10->pinctrl)) {
// 获取默认引脚状态
ov02k10->pins_default =
pinctrl_lookup_state(ov02k10->pinctrl,
OF_CAMERA_PINCTRL_STATE_DEFAULT);
if (IS_ERR(ov02k10->pins_default))
dev_err(dev, "could not get default pinstate\n");
// 获取睡眠引脚状态
ov02k10->pins_sleep =
pinctrl_lookup_state(ov02k10->pinctrl,
OF_CAMERA_PINCTRL_STATE_SLEEP);
if (IS_ERR(ov02k10->pins_sleep))
dev_err(dev, "could not get sleep pinstate\n");
} else {
dev_err(dev, "no pinctrl\n");
}
// 配置电源调节器
ret = ov02k10_configure_regulators(ov02k10);
if (ret) {
dev_err(dev, "Failed to get power regulators\n");
return ret;
}
// 初始化互斥锁
mutex_init(&ov02k10->mutex);
// 初始化V4L2子设备
sd = &ov02k10->subdev;
v4l2_i2c_subdev_init(sd, client, &ov02k10_subdev_ops);
// 初始化控制处理程序
ret = ov02k10_initialize_controls(ov02k10);
if (ret)
goto err_destroy_mutex;
// 上电传感器
ret = __ov02k10_power_on(ov02k10);
if (ret)
goto err_free_handler;
// 检查传感器ID
ret = ov02k10_check_sensor_id(ov02k10, client);
if (ret)
goto err_power_off;
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
// 设置子设备内部操作
sd->internal_ops = &ov02k10_internal_ops;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
#endif
#if defined(CONFIG_MEDIA_CONTROLLER)
// 初始化媒体实体
ov02k10->pad.flags = MEDIA_PAD_FL_SOURCE;
sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
ret = media_entity_pads_init(&sd->entity, 1, &ov02k10->pad);
if (ret < 0)
goto err_power_off;
#endif
// 设置子设备名称
memset(facing, 0, sizeof(facing));
if (strcmp(ov02k10->module_facing, "back") == 0)
facing[0] = 'b'; // 后置摄像头
else
facing[0] = 'f'; // 前置摄像头
snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s",
ov02k10->module_index, facing,
OV02K10_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);
#ifdef USED_SYS_DEBUG
// 添加sysfs调试接口
add_sysfs_interfaces(dev);
#endif
return 0;
// 错误处理
err_clean_entity:
#if defined(CONFIG_MEDIA_CONTROLLER)
media_entity_cleanup(&sd->entity);
#endif
err_power_off:
__ov02k10_power_off(ov02k10);
err_free_handler:
v4l2_ctrl_handler_free(&ov02k10->ctrl_handler);
err_destroy_mutex:
mutex_destroy(&ov02k10->mutex);
return ret;
}
/****************************** 移除函数 ******************************/
/* 驱动移除函数
* 当设备断开或模块卸载时调用
*/
static int ov02k10_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct ov02k10 *ov02k10 = to_ov02k10(sd);
// 注销异步子设备
v4l2_async_unregister_subdev(sd);
#if defined(CONFIG_MEDIA_CONTROLLER)
// 清理媒体实体
media_entity_cleanup(&sd->entity);
#endif
// 释放控制处理程序
v4l2_ctrl_handler_free(&ov02k10->ctrl_handler);
// 销毁互斥锁
mutex_destroy(&ov02k10->mutex);
// 禁用运行时电源管理
pm_runtime_disable(&client->dev);
if (!pm_runtime_status_suspended(&client->dev))
__ov02k10_power_off(ov02k10); // 确保设备下电
pm_runtime_set_suspended(&client->dev);
return 0;
}
/****************************** 设备树匹配表 ******************************/
#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id ov02k10_of_match[] = {
{ .compatible = "ovti,ov02k10" }, // 设备树兼容字符串
{},
};
MODULE_DEVICE_TABLE(of, ov02k10_of_match);
#endif
/****************************** I2C设备ID表 ******************************/
static const struct i2c_device_id ov02k10_match_id[] = {
{ "ovti,ov02k10", 0 }, // 设备名称
{ },
};
MODULE_DEVICE_TABLE(i2c, ov02k10_match_id);
/****************************** I2C驱动结构 ******************************/
static struct i2c_driver ov02k10_i2c_driver = {
.driver = {
.name = OV02K10_NAME, // 驱动名称
.pm = &ov02k10_pm_ops, // 电源管理操作
.of_match_table = of_match_ptr(ov02k10_of_match), // 设备树匹配表
},
.probe = &ov02k10_probe, // 探测函数
.remove = &ov02k10_remove, // 移除函数
.id_table = ov02k10_match_id, // 设备ID表
};
/****************************** 模块初始化和退出 ******************************/
#ifdef CONFIG_ROCKCHIP_THUNDER_BOOT
// 迅雷启动环境直接注册驱动
module_i2c_driver(ov02k10_i2c_driver);
#else
// 标准Linux启动环境
static int __init sensor_mod_init(void)
{
return i2c_add_driver(&ov02k10_i2c_driver);
}
static void __exit sensor_mod_exit(void)
{
i2c_del_driver(&ov02k10_i2c_driver);
}
// 使用device_initcall_sync确保在设备初始化阶段加载
device_initcall_sync(sensor_mod_init);
module_exit(sensor_mod_exit);
#endif
MODULE_DESCRIPTION("OmniVision ov02k10 sensor driver");
MODULE_LICENSE("GPL v2");
完整驱动程序总结
这个驱动程序完整实现了对OmniVision OV02K10图像传感器的控制,主要包括以下功能:
-
初始化和探测:
- 从设备树获取硬件配置信息
- 配置时钟、GPIO和电源管理
- 验证传感器ID
- 初始化V4L2子设备和控制框架
-
电源管理:
- 完整的上电/下电序列
- 运行时电源管理(Runtime PM)
- 休眠状态管理
-
视频流控制:
- 启动/停止视频流
- 模式切换(线性/HDR)
- 快速启停流功能
-
参数控制:
- 曝光时间设置
- 模拟/数字增益控制
- 测试图案生成
- 镜像/翻转控制
- HDR曝光参数设置
-
格式配置:
- 分辨率设置
- 数据格式配置
- 帧率控制
-
高级功能:
- HDR模式支持
- 转换增益控制(HCG/LCG)
- 组寄存器更新机制
- Pre-ISP HDR AE集成
-
调试接口:
- Sysfs调试接口(可选)
- 日志输出
-
兼容性支持:
- 32位应用兼容
- 设备树兼容
- 媒体控制器集成
该驱动程序遵循标准的V4L2子设备框架设计,与Linux内核的视频子系统深度集成,支持通过Media Controller进行管道配置,并通过V4L2控制接口提供丰富的参数调整功能。
大体学习流程
好的,作为一个 Linux Camera 驱动初学者,学习路径确实需要系统规划。Linux Camera 子系统相对复杂,涉及硬件、内核框架、用户空间等多个层面。下面我将为你梳理学习流程、核心概念、调试方法以及针对 RGB Sensor 的注意事项。
学习路径:从基础到实践
-
打好基础:
- Linux 内核基础: 理解 Linux 内核模块、设备模型(Device Tree, Platform Device/Driver, I2C/SPI 等总线)、中断处理、内存管理(DMA)、并发控制(mutex, spinlock)等。这是理解驱动如何工作的基石。
- C 语言: 熟练掌握 C 语言,特别是结构体、指针、内存操作。
- 硬件基础: 了解基本的电子知识,特别是你正在开发的 Sensor 的接口(I2C/SPI 控制, MIPI CSI-2/DVP 数据传输)、寄存器操作、时钟、供电等。仔细阅读 Sensor 的数据手册 (Datasheet) 和编程指南 (Programming Guide) 是必须的。
-
理解 Linux V4L2 子系统框架:
- 核心概念:
- Video for Linux Two (V4L2): Linux 视频采集的核心框架。定义了一套标准的 API 和数据结构。
- Media Controller (MC): 管理复杂的多媒体设备拓扑结构(如 Camera:Sensor -> CSI 接收器 -> ISP -> 内存)。它抽象了设备间的连接(pads, links)。
- v4l2_subdev: 代表子设备(如 Camera Sensor)。驱动的主要工作就是实现一个
v4l2_subdev驱动。 - v4l2_device: 代表一个 V4L2 设备实例,通常由更上层的驱动(如 CSI 接收器驱动或 ISP 驱动)创建。
v4l2_subdev需要注册到它下面。 - v4l2_ctrl: 控制框架,用于曝光、增益、白平衡、翻转等参数的标准化控制。
- 数据流链路 (Typical Pipeline for a Raw RGB Sensor):
- 应用层 (User Space App): 如
v4l2-ctl, GStreamer, OpenCV, 或自定义 App。使用open(),ioctl(),mmap()/read()等系统调用与 V4L2 设备节点 (/dev/videoX) 交互。 - V4L2 设备节点 (
/dev/videoX): 通常由 CSI 接收器驱动 或 ISP 驱动 创建。它暴露一个v4l2_device实例。 - CSI 接收器驱动 (e.g.,
rockchip_csi): 负责接收来自 Sensor 的 MIPI CSI-2 数据流,进行初步处理(如解包 RAW 数据),并通过 DMA 传输到内存缓冲区。它创建v4l2_device和/dev/videoX节点。 v4l2_subdev(Camera Sensor Driver - 你写的驱动):- 通过 I2C/SPI 配置 Sensor 寄存器 (分辨率、帧率、曝光、增益、测试图等)。
- 实现
v4l2_subdev_ops中的核心操作:.core_ops:s_power(电源管理),ioctl(自定义命令),subscribe_event(事件订阅)。.video_ops:s_stream(最关键!启动/停止数据流),g_frame_interval(获取帧率)。.pad_ops:enum_mbus_code(枚举支持的像素格式),enum_frame_size(枚举支持的分辨率),enum_frame_interval(枚举支持的帧率),get_fmt/set_fmt(获取/设置当前格式),get_selection(获取裁剪/合成区域),get_mbus_config(获取总线配置如 lanes, clock)。
- 实现
v4l2_ctrl_ops(通常是.s_ctrl函数) 来响应应用层对曝光、增益等参数的控制请求。 - 通过 Media Controller API (
media_entity_pads_init,media_create_pad_link) 将自己注册为一个media_entity,并与 CSI 接收器的media_entity建立link。
- 物理层 (Hardware):
- RGB Sensor: 通过 MIPI CSI-2 或 DVP 接口输出 RAW RGB 图像数据流。
- I2C/SPI: 用于配置 Sensor 寄存器。
- 时钟和电源: 为 Sensor 提供工作时钟和所需电压。
- 应用层 (User Space App): 如
- 学习资源:
- 官方文档: 这是最权威的!仔细阅读
Documentation/media/(或Documentation/driver-api/media/) 下的文档,特别是v4l2-framework.rst,media-controller.rst,v4l2-subdev.rst。 - 内核源码: 阅读内核中成熟的 Sensor 驱动源码是最好的学习方式。例如,在
drivers/media/i2c/目录下找类似ov5640.c,imx219.c,ov2685.c等驱动。你之前看的ov02k10.c也是一个例子。 - 书籍: 《Linux Device Drivers, 3rd Edition》(LDD3) 虽然有点老,但设备驱动基础部分仍有价值。关注 V4L2 的章节或网上资料。
- 在线资源: 博客、教程 (如 Bootlin, LWN.net 上的文章)、邮件列表 (linux-media@vger.kernel.org)。
- 官方文档: 这是最权威的!仔细阅读
- 核心概念:
-
开发调试步骤:
- 1. 硬件准备与验证:
- 确保 Sensor 模块硬件连接正确 (电源、时钟、I2C、MIPI/DVP)。
- 用示波器或逻辑分析仪检查 I2C 通信是否正常(上电后是否有读取 ID 的操作?),检查 MIPI 时钟和数据线是否有信号(启动流之后)。
- 确保设备树 (
.dts) 正确配置了 I2C 地址、引脚复用 (pinctrl)、时钟、电源、regulators 等。设备树配置错误是常见问题!
- 2. 驱动骨架搭建:
- 创建基本的 I2C/SPI 驱动框架。
- 定义
struct v4l2_subdev实例。 - 实现
probe()和remove()函数。 - 在
probe()中:- 初始化
v4l2_subdev(v4l2_i2c_subdev_init或v4l2_spi_subdev_init)。 - 初始化 Media Controller (
media_entity_pads_init- 通常一个 pad 就够了,MEDIA_PAD_FL_SOURCE)。 - 设置
v4l2_subdev的internal_ops(如果需要open()处理) 和ops(v4l2_subdev_ops)。 - 初始化控制处理程序 (
v4l2_ctrl_handler_init, 添加各种v4l2_ctrl,关联handler到subdev->ctrl_handler)。 - 配置电源、时钟、GPIO (reset, pwdn)。
- 尝试读取 Sensor ID 寄存器验证通信是否成功。
- 通过 Media Controller API (
media_create_pad_link) 将 Sensor 的 source pad 链接到 CSI 接收器的 sink pad。
- 初始化
- 在
remove()中进行资源释放和反初始化。
- 3. 实现核心操作 (
v4l2_subdev_ops):.s_power: 控制 Sensor 上电/下电序列。调用__ov02k10_power_on/off这样的内部函数。.s_stream: 最核心! 当应用调用VIDIOC_STREAMON时,此函数被调用。在这里:- 如果
on=1(启动流):- 可能需要上电 Sensor (如果
s_power没完全处理)。 - 写入一组初始化寄存器序列 (配置分辨率、输出格式、MIPI 参数等)。
- 启动 Sensor 的数据输出 (通常是一个特定的寄存器位)。
- 可能需要上电 Sensor (如果
- 如果
on=0(停止流):- 停止 Sensor 的数据输出。
- 可能需要下电 Sensor (如果
s_power没完全处理)。
- 如果
.pad_ops(尤其是.set_fmt): 处理应用设置分辨率/格式的请求。根据请求选择合适的模式(如你代码中的supported_modes),配置 Sensor 寄存器,并更新控制项的范围(如vblank)。.pad_ops->get_mbus_config: 告知上层本 Sensor 使用的总线配置(如 MIPI CSI-2 的 lane 数、时钟频率)。
- 4. 实现控制操作 (
v4l2_ctrl_ops):- 主要实现
.s_ctrl函数。当应用改变一个控制项(如曝光)的值时,此函数被调用。 - 根据
ctrl->id判断是哪个控制项。 - 将应用设置的值 (
ctrl->val) 转换为 Sensor 寄存器需要的值。 - 通过 I2C/SPI 写入相应的 Sensor 寄存器。
- 注意处理依赖关系(如改变
vblank会影响最大exposure)。
- 主要实现
- 5. 寄存器读写函数: 像你的代码中
ov02k10_write_reg和ov02k10_read_reg一样,实现可靠的寄存器读写函数。处理不同长度的读写(8/16/24/32位)。 - 6. 模式配置: 定义
struct ov02k10_mode数组 (supported_modes),包含每种分辨率/帧率/格式组合对应的寄存器配置列表、vts_def,hts_def,exp_def等关键参数。在s_stream和set_fmt中应用对应模式的配置。
- 1. 硬件准备与验证:
-
调试技巧与方法:
printk/dev_dbg/dev_info/dev_err: 最基本的调试手段,在关键路径(probe, stream on/off, 控制项设置)添加日志,打印寄存器值、状态、函数参数等。使用dev_xxx系列函数可以带上设备信息。dmesg: 查看内核打印信息。v4l2-ctl工具 (用户空间): 极其重要!v4l2-ctl --list-devices: 列出所有 V4L2 设备。v4l2-ctl -d /dev/videoX --all: 查看设备/dev/videoX的所有信息,包括支持的分辨率、格式、帧率、控制项及其当前值。这是检查驱动是否成功注册和配置的首要方法。v4l2-ctl -d /dev/videoX --set-fmt-video=width=W,height=H,pixelformat=PFMT: 设置格式。v4l2-ctl -d /dev/videoX --set-ctrl ctrl_id=value: 设置控制项值(如曝光、增益)。v4l2-ctl -d /dev/videoX --stream-mmap --stream-count=10 --stream-to=frame.raw: 捕获 RAW 帧数据到文件。这是调试 RGB Sensor 输出的关键!
- 分析 RAW 数据 (
frame.raw):- 你需要知道捕获的 RAW 格式(如
SBGGR10- Bayer RGGB 10bit)。 - 使用工具如
raw2rgbpnm(可能需要自己编译或找类似工具),或者自己写小程序,将 RAW 文件转换成可视化的 RGB 图像(如 PPM/PGM)。注意去马赛克(Demosaic)算法会影响效果,但用于检查图像是否存在、是否有严重问题(全黑/全白/花屏)足够了。 - 检查图像内容:
- 是否有图像?(不是全黑或全白)
- 图像内容是否正确?(用测试图模式验证最简单!)
- 颜色是否正确?(RGB 顺序是否匹配 Sensor 的 Bayer 模式?)
- 是否有坏点/坏线?
- 曝光/增益控制是否生效?
- 你需要知道捕获的 RAW 格式(如
- 逻辑分析仪 / 示波器: 硬件层面检查 I2C 通信波形、MIPI 时钟和数据线是否在
s_stream启动后有信号。检查 I2C 写入的寄存器地址和值是否正确。 - Sysfs 调试接口: 像你代码中那样,可以暴露一些调试接口(如强制切换 HCG/LCG)到 sysfs。
- KGDB / JTAG: 复杂问题可能需要源码级调试。
- 检查 Media Controller 拓扑:
media-ctl -d /dev/mediaX -p可以打印出 Media Controller 的拓扑结构,检查 Sensor 和 CSI 接收器之间的link是否建立正确。
-
调试 RGB Sensor 的特别注意事项:
- 测试图 (Test Pattern): 这是调试 Sensor 驱动本身 的利器!在驱动中实现
V4L2_CID_TEST_PATTERN控制。让 Sensor 输出固定的测试图案(如彩条、渐变灰阶)。这样:- 可以完全排除外部光线、镜头的影响。
- 可以验证数据链路是否通畅(能否收到数据?)。
- 可以验证 RAW 数据的格式、顺序是否正确(转换后的图像是否和预期的测试图一致?)。
- 可以验证分辨率设置是否正确。
- 寄存器配置: RGB Sensor 通常输出 Bayer RAW 数据。确保驱动中配置的:
- 输出格式 (
MEDIA_BUS_FMT_...) 与 Sensor 寄存器配置和实际输出完全一致。包括 Bayer 顺序 (RGGB, BGGR, GBRG, GRBG)、位宽 (8/10/12bit)、打包方式。 - 分辨率 寄存器配置正确。
- MIPI CSI-2 参数: Lane 数、数据速率、VC (Virtual Channel) 等是否与接收端(CSI RX 驱动)匹配。不匹配会导致数据无法接收或损坏。
- 时钟: 确保 Sensor 的输入时钟 (
xvclk) 频率正确且稳定。输出像素时钟 (link_freq/pixel_rate) 配置正确。
- 输出格式 (
- 控制项: 曝光、增益、白平衡(虽然 RAW 下 WB 主要在 ISP 做,但 Sensor 可能有相关寄存器影响模拟增益)、翻转/镜像等控制项需要正确映射到寄存器。使用
v4l2-ctl测试它们是否生效。 - 数据对齐: 确保 RAW 数据缓冲区的内存对齐符合 DMA 或 ISP 的要求(通常在 CSI RX 驱动或 ISP 驱动中处理,但 Sensor 驱动需要输出符合 MIPI 标准的数据流)。
- 初始化序列: Sensor 上电后通常需要一段初始化寄存器序列才能正常工作。仔细检查数据手册的推荐序列。有时需要等待 Sensor 稳定(
usleep_range)。 - 电源和复位序列: 严格按照数据手册的时序要求控制 reset 和 powerdown 引脚。不正确的时序可能导致 Sensor 无法启动或工作不稳定。
- DMA 和数据传输: Sensor 驱动 (
v4l2_subdev) 本身不直接处理 DMA 和数据缓冲区。它负责启动 Sensor 输出数据流。数据通过 MIPI CSI-2 传输到 CSI 接收器,由接收器驱动负责 DMA 到内存。问题通常出现在链路配置或接收器驱动。
- 测试图 (Test Pattern): 这是调试 Sensor 驱动本身 的利器!在驱动中实现
总结与建议:
- 由浅入深: 不要一开始就想完全理解整个复杂框架。先理解
v4l2_subdev,s_stream,set_fmt,s_ctrl这几个核心概念和操作。Media Controller 可以稍后深入。 - 实践驱动: 理论学习后,立刻动手实践。找一个相对简单的 Sensor 驱动源码(如
ov2685)作为参考,尝试在自己的板子上移植或修改。从让 Sensor 输出测试图开始。 - 善用工具:
v4l2-ctl和 RAW 图像分析是调试 Camera 驱动的两大法宝。务必熟练掌握。 - 重视文档: Sensor 数据手册、V4L2 内核文档是你的圣经。遇到问题先查文档。
- 利用社区: 遇到难题,搜索内核邮件列表 (
linux-media@vger.kernel.org存档)、相关芯片厂商的 SDK/文档、社区论坛 (如 Stack Overflow, Linaro 社区)。 - 耐心和毅力: Camera 驱动调试可能很繁琐,需要耐心地检查寄存器、波形、数据。做好心理准备。
学习 Linux Camera 驱动是一个循序渐进的过程。从理解框架开始,然后深入一个具体的驱动(如你看到的 ov02k10.c),结合实践和调试,你会逐渐掌握这项技能。祝你学习顺利!
更多推荐



所有评论(0)