🔍
B站相应的视屏教程:
📌 内核:博文+视频 - 总线驱动模型实战全解析 —— 以 PCA9450 PMIC 为例
敬请关注,记得标为原始粉丝。
本节深入剖析 NXP PCA9450 PMIC(电源管理芯片)驱动中 pca9450_i2c_probe()
函数的实现原理,讲解如何通过设备树、I2C 总线模型和 Linux regulator 子系统实现多个电源通道的注册与管理。我们将从总线匹配、资源获取、regulator 注册、GPIO 处理等方面全面讲解,并配合结构图、函数表格、设备树实例等丰富内容,帮助读者全面掌握典型 PMIC 驱动的写法。本文共计约 6000 字,适合作为内核驱动学习和讲解材料。
一、驱动的总线模型与匹配机制:I2C 驱动非 platform 驱动
虽然使用了 devm_*
、regmap_*
、regulator_register()
等函数与平台驱动类似,但 本驱动并非 platform_driver
,而是基于 I2C 的 i2c_driver
。
static struct i2c_driver pca9450_i2c_driver = {
.driver = {
.name = "nxp-pca9450",
.of_match_table = pca9450_of_match,
},
.probe = pca9450_i2c_probe,
};
设备树通过 compatible
与 of_device_id
匹配,I2C 核心框架通过 reg
获取地址并自动探测设备。
示例:设备树结构(完整展开)
&i2c1 {
pmic@25 {
compatible = "nxp,pca9450a";
reg = <0x25>; // I2C 地址
interrupt-parent = <&gpio1>;
interrupts = <3 IRQ_TYPE_LEVEL_LOW>; // 中断来源
sd-vsel-gpios = <&gpio3 5 GPIO_ACTIVE_HIGH>; // 控制 LDO5 电压表征
regulators {
buck1 {
regulator-name = "vdd_arm";
nxp,dvs-run-voltage = <900000>; // DVS 运行模式电压
nxp,dvs-standby-voltage = <800000>; // DVS 待机模式电压
};
ldo1 {
regulator-name = "vdd_3v3";
};
};
};
};
二、probe 函数核心功能与分阶段操作
pca9450_i2c_probe()
的设计目标是:初始化 I2C 寄存器访问(regmap)、配置中断、注册所有电源通道(BUCK/LDO)、读取 GPIO 配置、验证芯片 ID 和版本。流程结构如下:
📘 probe 流程图:
驱动加载
↓
i2c-core 匹配 compatible
↓
pca9450_i2c_probe()
├── devm_kzalloc() // 分配主数据结构
├── devm_regmap_init_i2c() // 建立 regmap
├── regmap_read() // 读取设备 ID
├── 选择 regulator 表格
├── 遍历 regulator:
│ ├── 传入 desc
│ └── devm_regulator_register()
├── devm_request_threaded_irq()
├── 配置寄存器(中断屏蔽等)
└── 获取 sd-vsel GPIO(可选)
三、关键结构体成员说明与作用
struct pca9450_regulator_desc
struct pca9450_regulator_desc {
struct regulator_desc desc; // regulator 子系统用的描述符
const struct pc9450_dvs_config dvs; // 对应的 DVS 控制寄存器掩码信息
};
用于将一个 regulator_desc
进行封装,并指定该通道的 run/standby 电压控制寄存器与掩码。
struct pca9450
struct pca9450 {
struct device *dev; // 设备对象(从 i2c_client 中获得)
struct regmap *regmap; // I2C 寄存器访问封装
struct gpio_desc *sd_vsel_gpio; // LDO5 电压控制引脚
enum pca9450_chip_type type; // 芯片型号
unsigned int rcnt; // regulator 通道数量
int irq; // 中断编号
};
四、资源获取函数与设备树属性的一一对应
API 函数 | 获取资源类型 | 对应设备树字段 | 返回值类型 / 说明 |
---|---|---|---|
devm_regmap_init_i2c() | I2C 寄存器映射 | reg = <0x25> | struct regmap * |
regmap_read() | 寄存器读取 | - | 芯片 ID 或状态位 |
devm_request_threaded_irq() | 中断资源 | interrupts = <...> | 请求中断 IRQ(由设备树中断号决定) |
gpiod_get_optional() | GPIO 控制线 | sd-vsel-gpios | 控制 LDO5 模式(电压选择) |
of_property_read_u32() | 获取电压值 | nxp,dvs-run-voltage 等 | 运行/待机电压配置 |
devm_regulator_register() | 注册 regulator | regulators 节点下子节点 | 生成 regulator 实例 |
五、以 buck1 为例完整说明设备树 → 驱动注册流程
buck1 {
regulator-name = "vdd_arm";
nxp,dvs-run-voltage = <900000>;
nxp,dvs-standby-voltage = <800000>;
};
驱动注册过程:
- 遍历 regulator 描述表(
pca9450a_regulators[]
) - 传入
regulator_desc
给devm_regulator_register()
- 如果
desc->of_parse_cb
非 NULL,执行pca9450_set_dvs_levels()
- 它进一步调用
buck_set_dvs()
,使用of_property_read_u32()
获取nxp,dvs-run-voltage
,并配置寄存器
六、设备树结构图与 API 映射示意
📊 结构与函数映射图:
&i2c1 → i2c_adapter + i2c_client
└── pmic@25 → i2c_client 绑定 probe()
├── reg = <0x25> → devm_regmap_init_i2c()
├── interrupts → devm_request_threaded_irq()
├── sd-vsel-gpios → gpiod_get_optional()
└── regulators → regulator_register()
├── buck1 → pca9450_set_dvs_levels()
└── ldo1 → 无需解析电压,直接注册
七、devm_ 系列函数的资源释放机制
函数 | 功能 | 优势 |
---|---|---|
devm_kzalloc() | 申请内存 | 在驱动 remove 时自动释放 |
devm_regmap_init_i2c() | 初始化 regmap | 自动释放 regmap 资源 |
devm_request_threaded_irq() | 中断申请 | 自动释放 IRQ 注册 |
devm_regulator_register() | 注册电源模块 | 由 regulator core 管理生命周期 |
gpiod_get_optional() | 获取 GPIO 控制线 | 自动释放 GPIO 控制器 |
📘 等价于现代 C++ 中的智能指针 RAII(资源自动释放)模型,避免内存泄漏和资源残留。
八、完整总结与拓展
该 PCA9450 驱动充分体现了嵌入式 Linux 中典型 I2C 器件的驱动结构:
- 使用设备树匹配 → i2c-core 框架驱动探测
regmap
简化寄存器访问(无需手动写 I2C)regulator
子系统标准化电源通道管理devm_
系列自动资源释放机制避免手动释放- 每个 regulator 支持 DVS(动态电压选择)控制
- 可选 GPIO 控制硬件输入选择(如
sd-vsel
)
✅ 若你掌握了 PCA9450 的驱动结构,将轻松应对同类芯片,如 Dialog、TI、ROHM、Maxim 等 PMIC 驱动设计。
📘 后续扩展建议
扩展主题 | 建议内容 |
---|---|
regulator 注册流程 | 内部如何挂载到 /sys/class/regulator |
regmap 操作原理 | 读写如何转换为 i2c_transfer 操作 |
BUCK 与 LDO 差异 | 稳压结构、用途、配置区别 |
中断事件分析 | IRQ_THERM_125、IRQ_VR_FLT1 等意义 |
多 PMIC 管理 | 多个 PMIC 时的调度策略与依赖配置 |