4. 点亮一颗Sensor:从零开始编写sensor驱动probe函数
终于到了最激动人心的环节——让Sensor真正跑起来。前面几章我们把驱动框架、I2C通信、电源时序都铺垫好了,现在要做的就是把它们串起来,写一个能用的probe函数。
说实话,我第一次写Sensor驱动时,以为probe就是简单注册一下设备。结果踩了不少坑。后来才明白,probe函数是Sensor的“出生证明”,它决定了硬件能不能被内核正确识别和管理。
4.1 probe函数的核心职责
probe函数说白了就是做三件事:检测硬件是否存在、初始化通信接口、注册V4L2子设备。我习惯把它分成四个阶段:
| 阶段 | 主要工作 | 常见问题 |
|---|---|---|
| 设备树解析 | 读取reg、clocks、power-gpios等属性 | 属性名拼写错误、缺少必要属性 |
| 硬件检测 | 读取CHIP_ID寄存器,验证Sensor型号 | I2C地址错误、电源未稳定 |
| 资源初始化 | 申请GPIO、时钟、电源、中断 | 资源冲突、顺序依赖 |
| V4L2注册 | 初始化子设备、注册到媒体框架 | ops未正确赋值、缺少必要回调 |
4.2 设备树配置——硬件信息的“身份证”
设备树是驱动和硬件之间的桥梁。我见过不少新手直接在代码里硬编码GPIO号,结果换一个平台就得改代码。正确的做法是把所有硬件配置都放到设备树里。
下面是一个典型的Sensor设备树节点:
// imx258_sensor.dtsi
&i2c_2 {
status = "okay";
imx258: camera_sensor@1a {
compatible = "sony,imx258";
reg = <0x1a>;
clocks = <&camcc CAM_CC_MCLK0_CLK>;
clock-names = "xvclk";
clock-frequency = <24000000>;
avdd-supply = <&pm8150_l17>;
dvdd-supply = <&pm8150_l16>;
iovdd-supply = <&pm8150_l6>;
reset-gpios = <&tlmm 94 GPIO_ACTIVE_LOW>;
powerdown-gpios = <&tlmm 105 GPIO_ACTIVE_HIGH>;
port {
imx258_ep: endpoint {
remote-endpoint = <&csiphy0_ep>;
data-lanes = <1 2 3 4>;
link-frequencies = /bits/ 64 <480000000>;
};
};
};
};
4.3 驱动probe函数的完整实现
好了,设备树配好了,接下来写probe函数。我会一步步拆解,每个环节都配上实际代码。
4.3.1 解析设备树属性
第一步是从设备树节点里把配置读出来。这里我习惯用of_property_read_*系列函数:
static int imx258_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct imx258 *sensor;
int ret;
sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
// 解析时钟
sensor->xvclk = devm_clk_get(dev, "xvclk");
if (IS_ERR(sensor->xvclk)) {
dev_err(dev, "failed to get xvclk\n");
return PTR_ERR(sensor->xvclk);
}
// 解析电源
sensor->avdd = devm_regulator_get(dev, "avdd");
sensor->dvdd = devm_regulator_get(dev, "dvdd");
sensor->iovdd = devm_regulator_get(dev, "iovdd");
// 解析GPIO
sensor->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
sensor->powerdown_gpio = devm_gpiod_get(dev, "powerdown", GPIOD_OUT_LOW);
// 解析CSI配置
ret = of_property_read_u32(dev->of_node, "clock-frequency",
&sensor->mclk_freq);
if (ret)
sensor->mclk_freq = 24000000; // 默认24MHz
}
GPIOD_OUT_LOW表示初始状态为低电平。对于复位引脚,通常低电平有效,所以初始状态要保持低电平。我曾经搞反了极性,结果Sensor一直处于复位状态,读ID总是失败。
4.3.2 硬件检测——读CHIP_ID
这是最关键的一步。读不到正确的CHIP_ID,后面所有工作都是白费。我一般会写一个独立的检测函数:
static int imx258_detect(struct imx258 *sensor)
{
struct i2c_client *client = sensor->i2c_client;
u16 chip_id;
int ret;
// 先上电
ret = imx258_power_on(sensor);
if (ret) {
dev_err(&client->dev, "power on failed\n");
return ret;
}
// 等待电源稳定,至少10ms
msleep(20);
// 读取CHIP_ID寄存器(0x000A)
ret = imx258_read_reg(sensor, 0x000A, &chip_id);
if (ret) {
dev_err(&client->dev, "failed to read chip id\n");
goto power_off;
}
dev_info(&client->dev, "chip id: 0x%04x\n", chip_id);
if (chip_id != IMX258_CHIP_ID) {
dev_err(&client->dev, "chip id mismatch: 0x%04x != 0x%04x\n",
chip_id, IMX258_CHIP_ID);
ret = -ENODEV;
goto power_off;
}
return 0;
power_off:
imx258_power_off(sensor);
return ret;
}
你可能会问:为什么要先上电再读ID?嗯,这个问题我当年也犯过。Sensor不上电,I2C总线上的设备根本不会响应,读回来的数据全是0xFF。所以电源时序一定要对。
4.3.3 注册V4L2子设备
硬件检测通过后,就可以把Sensor注册到V4L2框架了。这里我习惯用v4l2_i2c_subdev_init:
// 初始化V4L2子设备
v4l2_i2c_subdev_init(&sensor->sd, client, &imx258_subdev_ops);
sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
// 设置必要的回调
sensor->sd.internal_ops = &imx258_internal_ops;
sensor->sd.dev->platform_data = sensor;
// 注册异步子设备
ret = v4l2_async_register_subdev(&sensor->sd);
if (ret) {
dev_err(dev, "failed to register subdev\n");
goto power_off;
}
关键点:注册异步子设备后,驱动会等待CSI PHY和CSID等硬件就绪,然后才完成整个pipeline的绑定。这个机制保证了各个模块的初始化顺序。
4.4 完整的probe流程
把上面几个步骤串起来,就是完整的probe函数。我画了一张流程图,方便你理解整体逻辑:
4.5 避坑指南——我踩过的那些坑
写了这么多年驱动,probe函数里的坑我基本都踩过一遍。分享几个典型的:
- 电源时序问题:我曾经在某个项目里,Sensor的AVDD和DVDD需要先上电再拉高IOVDD。结果我同时使能了三个电源,导致Sensor内部LDO锁死,读ID一直失败。后来加了20ms延时才解决。
- GPIO极性搞反:复位引脚低电平有效,但设备树里忘了加
GPIO_ACTIVE_LOW标志。驱动拉高复位引脚,Sensor一直处于复位状态。这个问题排查了我整整一天。 - I2C地址错误:Sensor数据手册里写的是8位地址,但Linux内核用的是7位地址。如果不做转换,驱动会去错误的地址找设备。
- 时钟频率不匹配:Sensor的MCLK要求24MHz,但平台默认输出19.2MHz。没有正确配置时钟树,Sensor内部PLL无法锁定,输出图像全是花屏。
/sys/kernel/debug/gpio看GPIO状态对不对。然后用i2cget手动读一下Sensor的CHIP_ID寄存器,确认I2C通信是否正常。这两个方法能快速定位80%的问题。
4.6 小结
probe函数是Sensor驱动的入口,也是整个驱动稳定性的基石。写的时候记住三个原则:先检测再注册、先上电再通信、先解析再使用。把设备树配置好,把电源时序理清楚,把CHIP_ID读对,剩下的工作就水到渠成了。