3. RK平台Sensor驱动源码结构:probe、remove、power_on/off、s_stream回调函数
RK平台Sensor驱动的核心骨架由四个关键回调函数构成:probe(探测与初始化)、remove(卸载与资源释放)、power_on/off(电源与上下电时序管理)以及 s_stream(数据流启停控制)。理解这四个回调函数的职责、调用顺序与实现要点,是掌握Sensor驱动调试的基础。
核心认知:在RK平台V4L2子系统中,Sensor驱动本质上是
v4l2_subdev的实例化。probe完成设备注册与硬件初始化,remove做逆向清理,power_on/off管理物理电源与时钟,s_stream控制MIPI数据流与帧同步。这四个函数构成了驱动生命周期管理的完整闭环。
3.1 probe回调函数:驱动的入口与初始化
probe函数在设备与驱动匹配时被内核调用,是Sensor驱动的初始化入口。其主要职责包括:
- 设备树解析:读取
reg(I2C地址)、clocks(时钟源)、power-domains(电源域)、reset-gpios(复位引脚)等属性。 - I2C客户端初始化:通过
i2c_check_functionality验证适配器能力,设置客户端私有数据。 - V4L2子设备注册:调用
v4l2_i2c_subdev_init或v4l2_subdev_init初始化v4l2_subdev结构体,并绑定回调函数集。 - 媒体实体初始化:设置
media_entity的pad数量与类型(通常为1个源pad)。 - 电源与时钟获取:通过
devm_clk_get获取MCLK,通过devm_regulator_bulk_get获取多路电源。 - 芯片ID校验:读取Sensor的CHIP_ID寄存器,验证硬件是否匹配。
调试要点:probe失败最常见的原因是设备树节点与驱动不匹配(compatible字符串错误)或I2C通信异常。建议在probe入口处添加
dev_info打印,确认函数被调用;在读取CHIP_ID后立即打印寄存器值,快速定位硬件通信问题。
static int sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct sensor_priv *priv;
int ret;
dev_info(dev, "sensor probe start\n");
/* 1. 分配私有数据结构 */
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
i2c_set_clientdata(client, priv);
/* 2. 解析设备树 */
ret = sensor_parse_dt(dev, priv);
if (ret)
return ret;
/* 3. 获取电源与时钟 */
priv->xvclk = devm_clk_get(dev, "xvclk");
if (IS_ERR(priv->xvclk))
return PTR_ERR(priv->xvclk);
/* 4. 上电并读取芯片ID */
sensor_power_on(priv);
ret = sensor_check_chip_id(priv);
sensor_power_off(priv);
if (ret)
return ret;
/* 5. 初始化V4L2子设备 */
v4l2_i2c_subdev_init(&priv->subdev, client, &sensor_subdev_ops);
priv->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
/* 6. 注册媒体实体pad */
priv->pad.flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&priv->subdev.entity, 1, &priv->pad);
if (ret)
return ret;
dev_info(dev, "sensor probe success, chip_id=0x%04x\n", priv->chip_id);
return 0;
}
3.2 remove回调函数:资源的逆向释放
remove函数在驱动卸载或设备移除时被调用,负责释放probe阶段申请的所有资源。其典型实现包括:
- 调用
media_entity_cleanup清理媒体实体。 - 调用
v4l2_device_unregister_subdev注销子设备。 - 调用
sensor_power_off确保设备处于下电状态。 - 释放GPIO、中断、DMA缓冲区等硬件资源。
常见陷阱:remove函数中必须检查设备是否处于streaming状态。如果s_stream正在运行,应先调用
sensor_s_stream(0)停止数据流,再执行资源释放,否则可能导致内核崩溃或DMA访问非法内存。
static int sensor_remove(struct i2c_client *client)
{
struct sensor_priv *priv = i2c_get_clientdata(client);
/* 1. 确保数据流已停止 */
if (priv->streaming)
sensor_s_stream(&priv->subdev, 0);
/* 2. 下电 */
sensor_power_off(priv);
/* 3. 清理媒体实体 */
media_entity_cleanup(&priv->subdev.entity);
/* 4. 注销V4L2子设备 */
v4l2_device_unregister_subdev(&priv->subdev);
dev_info(&client->dev, "sensor removed\n");
return 0;
}
3.3 power_on/off回调函数:电源与时序管理
power_on和power_off函数通常不是V4L2子设备操作集的直接成员,而是由驱动内部封装,供probe、s_stream以及系统休眠唤醒时调用。在RK平台上,Sensor的上下电时序需要严格遵循硬件手册,否则可能导致初始化失败或图像异常。
| 电源操作 | 典型执行顺序 | 关键注意事项 |
|---|---|---|
| power_on | 1. 使能AVDD(模拟电源)→ 2. 使能DOVDD(数字IO电源)→ 3. 使能DVDD(数字核心电源)→ 4. 使能MCLK(主时钟)→ 5. 释放复位(RST低→高)→ 6. 等待Tready(稳定时间) | 各电源之间通常需要1~5ms延迟;MCLK频率必须与Sensor规格一致(常见24MHz/27MHz);复位释放后需等待至少10ms才能进行I2C通信。 |
| power_off | 1. 拉低复位(RST高→低)→ 2. 关闭MCLK → 3. 关闭DVDD → 4. 关闭DOVDD → 5. 关闭AVDD | 下电顺序与上电相反;关闭电源前必须确保I2C总线空闲;部分Sensor需要先写入shutdown寄存器再断电。 |
static int sensor_power_on(struct sensor_priv *priv)
{
struct device *dev = &priv->subdev.dev;
int ret;
/* 1. 使能模拟电源 */
ret = regulator_enable(priv->avdd_reg);
if (ret) return ret;
usleep_range(2000, 3000); /* 等待2ms */
/* 2. 使能IO电源 */
ret = regulator_enable(priv->iovdd_reg);
if (ret) return ret;
usleep_range(1000, 2000);
/* 3. 使能数字核心电源 */
ret = regulator_enable(priv->dvdd_reg);
if (ret) return ret;
usleep_range(1000, 2000);
/* 4. 使能MCLK */
ret = clk_prepare_enable(priv->xvclk);
if (ret) return ret;
usleep_range(1000, 2000);
/* 5. 释放复位 */
gpiod_set_value_cansleep(priv->reset_gpio, 1);
usleep_range(10000, 15000); /* 等待10ms以上 */
dev_dbg(dev, "power on sequence completed\n");
return 0;
}
static int sensor_power_off(struct sensor_priv *priv)
{
/* 1. 拉低复位 */
gpiod_set_value_cansleep(priv->reset_gpio, 0);
usleep_range(1000, 2000);
/* 2. 关闭MCLK */
clk_disable_unprepare(priv->xvclk);
/* 3. 按逆序关闭电源 */
regulator_disable(priv->dvdd_reg);
regulator_disable(priv->iovdd_reg);
regulator_disable(priv->avdd_reg);
dev_dbg(&priv->subdev.dev, "power off sequence completed\n");
return 0;
}
3.4 s_stream回调函数:数据流的启停控制
s_stream是V4L2子设备操作集v4l2_subdev_video_ops中的核心回调,用于控制Sensor的数据流输出。当应用层调用VIDIOC_STREAMON时,V4L2框架会逐级调用子设备的s_stream(1);调用VIDIOC_STREAMOFF时调用s_stream(0)。
s_stream(1)的典型实现步骤:
- 调用
sensor_power_on确保设备处于上电状态。 - 通过I2C写入Sensor寄存器组,配置分辨率、帧率、曝光、增益等参数。
- 写入
0x0100(Stream_On)寄存器为0x01,启动MIPI数据输出。 - 设置
priv->streaming = true标志位。
s_stream(0)的典型实现步骤:
- 写入
0x0100寄存器为0x00,停止MIPI数据输出。 - 调用
sensor_power_off下电以降低功耗。 - 设置
priv->streaming = false标志位。
性能优化提示:在s_stream(1)中,寄存器配置应尽量使用批量写入(I2C多字节写或寄存器数组),减少I2C通信次数。对于帧率切换频繁的场景,可将常用配置预计算并缓存到
priv结构体中,避免每次stream on都重新计算。
static int sensor_s_stream(struct v4l2_subdev *sd, int enable)
{
struct sensor_priv *priv = v4l2_get_subdevdata(sd);
int ret = 0;
if (enable) {
/* 1. 上电 */
ret = sensor_power_on(priv);
if (ret)
return ret;
/* 2. 写入初始化寄存器组 */
ret = sensor_write_array(priv, sensor_init_regs,
ARRAY_SIZE(sensor_init_regs));
if (ret)
goto err_power_off;
/* 3. 写入分辨率配置 */
ret = sensor_write_array(priv, sensor_resolution_regs,
ARRAY_SIZE(sensor_resolution_regs));
if (ret)
goto err_power_off;
/* 4. 启动MIPI流 */
ret = sensor_write_reg(priv, 0x0100, 0x01);
if (ret)
goto err_power_off;
priv->streaming = true;
dev_dbg(sd->dev, "stream started\n");
} else {
/* 停止MIPI流 */
ret = sensor_write_reg(priv, 0x0100, 0x00);
if (ret)
return ret;
/* 下电 */
sensor_power_off(priv);
priv->streaming = false;
dev_dbg(sd->dev, "stream stopped\n");
}
return 0;
err_power_off:
sensor_power_off(priv);
return ret;
}
3.5 四个回调函数的协作关系
在RK平台Sensor驱动的完整生命周期中,probe、remove、power_on/off、s_stream四个回调函数按照以下时序协作:
| 阶段 | 调用函数 | 关键动作 | 典型耗时 |
|---|---|---|---|
| 驱动加载 | probe | 设备树解析、资源获取、CHIP_ID校验、V4L2注册 | 50~200ms |
| 应用打开设备 | (无回调,V4L2框架自动处理) | 创建文件句柄、分配缓冲区 | 1~5ms |
| 开始采集 | s_stream(1) → power_on | 上电、寄存器配置、启动MIPI流 | 30~100ms |
| 持续采集 | (中断/轮询处理帧数据) | DMA传输、帧同步、缓冲区入队出队 | 每帧10~40ms |
| 停止采集 | s_stream(0) → power_off | 停止MIPI流、下电 | 10~30 |