[Linux]学习笔记系列 -- [drivers]regulator
Linux Regulator框架:统一电源管理解决方案 Regulator框架是Linux内核中用于统一管理电源供应的核心子系统,主要解决嵌入式系统中电源控制的复杂性问题。该框架通过生产者/消费者模型实现硬件解耦,设备驱动通过标准API请求电源而无需了解底层硬件细节。 关键特性: 基于设备树的电源拓扑描述 支持电压/电流的动态调节 提供安全约束机制 实现全局电源优化 典型应用场景包括: 外设电源
title: regulator
categories:
- linux
- drivers
tags: - linux
- drivers
abbrlink: ‘656712e5’
date: 2025-10-03 09:01:49
https://github.com/wdfk-prog/linux-study

文章目录
- drivers/regulator Regulator框架(Regulator Framework) 内核统一的电源供应管理框架
- drivers/regulator/devres.c
- drivers/regulator/dummy.c
- drivers/regulator/core.c
- drivers/regulator/of_regulator.c
- drivers/regulator/fixed.c
drivers/regulator Regulator框架(Regulator Framework) 内核统一的电源供应管理框架
历史与背景
这项技术是为了解决什么特定问题而诞生的?
Regulator(电源稳压器)框架的诞生是为了解决在现代嵌入式系统(尤其是SoC)中一个核心且日益复杂的问题:电源管理。
在此框架出现之前,对电源的控制是混乱、不可移植且与硬件高度耦合的:
- 代码的混乱与不可移植:设备驱动程序(例如一个Wi-Fi芯片驱动)需要电源才能工作。在没有统一框架的情况下,这个驱动必须包含特定于某个主板的代码来打开它的电源,例如通过I2C与PMIC(电源管理集成电路)通信,或者直接翻转一个GPIO引脚。这意味着同一个Wi-Fi驱动,如果要用在另一块使用不同PMIC的主板上,就需要被修改和重新编译。
- 缺乏电源拓扑视图:内核无法了解系统中复杂的电源供应关系(即“电源树”),例如哪个LDO(低压差稳压器)是由哪个Buck(降压)转换器供电的。这使得实现复杂的、系统级的电源优化变得几乎不可能。
- 无法实现动态电压与频率调整(DVFS):DVFS是现代CPU节能的关键技术,它要求在调整CPU频率之前,必须先精确地将其核心电压调整到一个安全水平。这种精细的、有依赖关系的操作需要一个健壮的框架来支持。
Regulator框架的核心目标就是创建一个统一的、抽象的接口,将**需要电源的设备(消费者)与提供电源的硬件(生产者)**彻底解耦,并通过设备树来描述它们之间的连接关系。
它的发展经历了哪些重要的里程碑或版本迭代?
Regulator框架的发展与设备树(Device Tree)在Linux内核中的普及紧密相连。
- 框架的建立:框架被引入,定义了核心的生产者/消费者模型,以及
regulator_get,regulator_enable等核心API。 - 与设备树的深度融合:这是最重要的里程碑。所有的电源供应关系都通过一套标准的设备树绑定来描述。消费者通过一个
-supply属性(如vmmc-supply)来声明它需要哪个电源;生产者(PMIC驱动)则在自己的设备树节点下定义它能提供的所有电源。这使得整个电源拓扑图可以完全在设备树中定义,无需修改任何驱动代码。 - 约束(Constraints)模型的完善:框架引入了对电压、电流和工作模式的约束管理。这允许内核根据所有消费者的需求,自动计算出某个电源轨道的安全工作范围,是实现DVFS等高级功能的基础。
目前该技术的社区活跃度和主流应用情况如何?
Regulator框架是所有现代嵌入式Linux系统中一个极其核心、稳定且强制使用的基础设施。
- 主流应用:
- 所有SoC平台:ARM, RISC-V, PowerPC等架构的Linux系统都严重依赖此框架来管理PMIC和内部的LDO。
- 动态电压与频率调整(DVFS):
CPUFreq子系统是Regulator框架的一个主要消费者,用于动态调整CPU核心电压。 - 所有外设驱动:SD卡、Wi-Fi、传感器、显示屏等几乎所有需要可控电源的外设,其驱动都是此框架的消费者。
核心原理与设计
它的核心工作原理是什么?
Regulator框架是一个典型的生产者/消费者模型,由Regulator核心层进行协调,并由设备树进行描述。
- 生产者(Provider / Regulator驱动):
- 这是控制物理电源硬件(如PMIC芯片、LDO)的驱动程序。
- 它知道如何通过I2C/SPI等总线与硬件通信,来执行使能/禁止、设置电压/电流等操作。
- 它会实现一组
struct regulator_ops回调函数(如.enable,.disable,.set_voltage),并为它能控制的每一个电源输出(rail)注册一个struct regulator_desc。
- 消费者(Consumer / 客户端驱动):
- 这是需要使用电源的设备驱动,例如一个SDIO Wi-Fi驱动。
- 它完全不知道电源来自哪个PMIC,也不知道如何控制它。它只知道自己需要一个名为“vmmc”的3.3V电源。
- 在驱动的
.probe()函数中,它通过devm_regulator_get(dev, "vmmc")来请求这个电源。一旦获取成功,它就可以通过regulator_enable(),regulator_disable(),regulator_set_voltage()等API来控制这个电源。
- 设备树(The Map / The Description):
- 设备树是连接生产者和消费者的蓝图。
- 生产者(如一个PMIC)的设备树节点会包含一个子节点,专门描述它提供的所有regulator。每个regulator都有自己的属性,如电压范围、名称等。
pmic@4b { regulators { sd_supply: ldo5 { regulator-name = "vcc_sd"; regulator-min-microvolt = <3300000>; ... }; }; }; - 消费者(如SD卡控制器)的设备树节点会使用一个
-supply属性来声明它的电源依赖。sdhci@deadbeef { vmmc-supply = <&sd_supply>; // phandle指向生产者 ... };
- Regulator核心层(The Glue):
- 当SD卡驱动调用
regulator_get(dev, "vmmc")时,Regulator核心会:- 查看SD卡设备的设备树节点,找到
vmmc-supply属性。 - 通过phandle
&sd_supply找到对应的PMIC LDO5节点。 - 找到管理这个PMIC的驱动(生产者)。
- 建立消费者和生产者之间的链接,并返回一个句柄给SD卡驱动。
- 查看SD卡设备的设备树节点,找到
- 当SD卡驱动调用
regulator_enable()时,核心层最终会调用PMIC驱动注册的.enable回调函数,完成对硬件的操作。
- 当SD卡驱动调用
它的主要优势体现在哪些方面?
- 完全解耦与可移植性:设备驱动无需修改即可在不同主板上运行,只要设备树提供了正确的电源描述。
- 集中式电源管理:内核可以完整地了解整个系统的电源拓扑,从而可以进行智能的、全局性的电源优化。
- 安全性:约束模型可以防止驱动程序将电压设置到一个危险的水平。框架还会处理多个消费者共享同一个regulator的情况。
- 节能:可以安全地关闭不使用的设备电源,并在需要时重新打开。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 复杂性:正确地编写Regulator驱动和设备树描述,特别是对于具有复杂依赖关系和约束的PMIC,是一项复杂的工作。
- 开销:对于最简单的、仅需开关的电源,使用此框架比直接操作GPIO要引入更多的代码和配置,但换来的是巨大的可维护性优势。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
Regulator框架是任何需要可软件控制的电源供应的场景下的标准解决方案。
- 外设电源控制:一个SD卡控制器驱动,在检测到卡插入时,通过
regulator_enable()打开卡槽的vmmc电源;在卡拔出时,通过regulator_disable()关闭电源以节省功耗。 - CPU动态电压调整(DVFS):CPUFreq驱动在准备提升CPU频率时,会先通过
regulator_set_voltage()将CPU核心电压(如vdd_cpu)提升到一个更高的水平;在降低频率后,再将电压降下来以节省功耗。 - 模拟电路供电:一个音频编解码器(Codec)驱动,在开始播放或录音前,使能其模拟电路部分的电源供应,以保证信号质量;在空闲时则关闭它。
是否有不推荐使用该技术的场景?为什么?
- 不可控的电源:如果一个设备的电源是硬连线的,总是打开且不能被软件控制,那么就不需要在其驱动中使用Regulator框架来消费这个电源。但在设备树中,这个电源本身仍然应该被描述为一个
regulator-always-on的生产者,以供内核了解完整的电源拓扑。 - 逻辑使能信号:如果一个引脚只是一个逻辑上的“使能”信号(Enable pin),而不是承载设备主要工作电流的电源输入,那么使用GPIO子系统来控制通常更为恰当和简单。
对比分析
请将其 与 其他相似技术 进行详细对比。
在Linux内核中,与Regulator子系统最常进行比较的是GPIO子系统和Clock子系统。它们共同构成了SoC资源管理的三大支柱。
| 特性 | Regulator Subsystem | GPIO Subsystem | Clock Subsystem |
|---|---|---|---|
| 核心功能 | 管理电源供应。控制电压和电流。 | 控制通用数字信号。控制逻辑高/低电平。 | 管理时钟信号。控制频率。 |
| 物理量 | 伏特 (V), 安培 (A) | 逻辑 1 / 0 (二进制) | 赫兹 (Hz) |
| 抽象概念 | 电源供应 (Supply) | 信号线 (Line) | 时钟源 (Source) |
| 典型API | regulator_enable(), regulator_set_voltage() |
gpiod_set_value(), gpiod_get_value() |
clk_enable(), clk_set_rate() |
| 交互关系 | 为设备提供工作的能量。 | 为设备提供控制或状态信号。 | 为设备提供工作的节拍。 |
| 协作场景 (DVFS) | CPUFreq驱动先调用Regulator提高电压,再调用Clock提高频率。 | (不直接参与DVFS核心) | CPUFreq驱动在Regulator提压后,调用Clock提高频率。 |
| 总结 | 负责“吃饭” | 负责“举旗” | 负责“心跳” |
drivers/regulator/devres.c
devm_regulator_register: 注册一个受设备管理的稳压器 (Regulator)
此函数是 regulator_register 的一个 “设备资源管理” (device-managed 或 devres) 封装版本。它的核心作用是注册一个稳压器, 同时利用内核的 devres 机制, 将这个新注册的稳压器的生命周期与提供它的设备 (dev) 的生命周期绑定。这样做最大的好处是自动化资源管理: 当设备被移除或其驱动被卸载时, 内核会自动调用相应的释放函数来注销这个稳压器, 从而极大地简化了驱动程序的代码, 并从根本上杜绝了因忘记释放资源而导致的内存泄漏。
/**
* devm_regulator_register - 资源管理的 regulator_register()
* @dev: 提供电源的设备 (即稳压器所在的设备).
* @regulator_desc: 要注册的稳压器的静态描述.
* @config: 稳压器的运行时配置.
*
* 由稳压器驱动程序调用以注册一个稳压器. 成功时返回一个有效的
* struct regulator_dev 指针, 失败时返回一个 ERR_PTR().
* 当设备被解除绑定时, 该稳压器将被自动释放.
*/
struct regulator_dev *devm_regulator_register(struct device *dev,
const struct regulator_desc *regulator_desc,
const struct regulator_config *config)
{
/*
* ptr: 一个指向 "指向 struct regulator_dev 的指针" 的指针 (即二级指针).
* 它将用于存储由 devres 框架管理的内存地址.
* rdev: 一个指向 struct regulator_dev 的指针, 用于接收 regulator_register 的返回值.
*/
struct regulator_dev **ptr, *rdev;
/*
* 调用 devres_alloc, 向设备资源管理框架申请一小块内存.
* @ devm_rdev_release: 这是一个函数指针, 指向当设备释放时需要被调用的清理函数.
* @ sizeof(*ptr): 要分配的内存大小. 这里的大小就是一个指针的大小.
* @ GFP_KERNEL: 内存分配标志.
* devres_alloc 返回一个指向所分配内存的指针, 我们将其存入 ptr.
*/
ptr = devres_alloc(devm_rdev_release, sizeof(*ptr),
GFP_KERNEL);
/*
* 如果内存分配失败, devres_alloc 返回 NULL.
*/
if (!ptr)
/*
* 返回一个内嵌了 -ENOMEM (内存不足) 错误码的指针.
*/
return ERR_PTR(-ENOMEM);
/*
* 调用非 "devm" 版本的核心注册函数 regulator_register, 执行实际的注册工作.
* 将其返回值存入 rdev.
*/
rdev = regulator_register(dev, regulator_desc, config);
/*
* 检查 regulator_register 是否成功. IS_ERR 用于判断返回值是否为错误指针.
* 如果不为错误 (即注册成功).
*/
if (!IS_ERR(rdev)) {
/*
* 将成功注册后返回的 rdev 指针存入由 devres 管理的那块内存中 (*ptr).
* 这样, 清理函数 devm_rdev_release 将来就可以通过这个指针找到要释放的资源.
*/
*ptr = rdev;
/*
* 调用 devres_add, 将我们刚刚分配和填充的受管资源(ptr)正式与设备(dev)关联起来.
* 从这一刻起, 内核就承诺在 dev 被释放时, 会自动调用 devm_rdev_release(*ptr).
*/
devres_add(dev, ptr);
} else {
/*
* 如果 regulator_register 失败了.
* 调用 devres_free, 释放我们之前通过 devres_alloc 申请的内存.
* 因为注册失败了, 所以不再需要管理这个资源了.
*/
devres_free(ptr);
}
/*
* 返回 regulator_register 的原始返回值.
* 无论成功 (返回 rdev 指针) 还是失败 (返回错误指针), 都将其传递给调用者.
*/
return rdev;
}
/*
* 使用 EXPORT_SYMBOL_GPL 将 devm_regulator_register 函数导出.
* 这使得其他遵循GPL许可证的内核驱动模块可以调用这个函数.
*/
EXPORT_SYMBOL_GPL(devm_regulator_register);
drivers/regulator/dummy.c
dummy_regulator_probe: 注册虚拟稳压器 (Dummy Regulator)
此函数是 reg-dummy 这个虚拟设备所对应的 “驱动程序” 的 probe (探测) 函数。当 faux_device_create 创建了 “reg-dummy” 设备后, 内核的驱动核心会立即调用此函数。它的唯一职责就是向内核的稳压器框架注册一个虚拟的、纯软件的、永远在线的稳压器实例。
/*
* 定义一个全局的、指向 struct regulator_dev 的指针.
* 'static' 关键字在此处是不正确的, 因为它在头文件中被 'extern' 声明了,
* 因此它是一个全局可见的变量. 它将用于保存成功注册后的虚拟稳压器设备的实例指针.
*/
struct regulator_dev *dummy_regulator_rdev;
/*
* 定义一个静态的、常量 regulator_init_data 结构体实例.
* regulator_init_data 用于向稳压器框架提供设备的初始约束和配置.
*/
static const struct regulator_init_data dummy_initdata = {
/*
* .constraints: 定义了稳压器的硬件约束.
*/
.constraints = {
/*
* .always_on = 1: 这是一个关键设置. 它告诉稳压器框架, 这个电源是永远开启的,
* 不能被软件关闭. 这对于模拟那些直接连接到主电源、不可控的电源轨至关重要.
*/
.always_on = 1,
},
};
/*
* 定义一个静态的、常量 regulator_ops 结构体实例.
* regulator_ops 包含了一系列函数指针, 用于实现对稳压器硬件的实际操作 (如开关、设置电压等).
* 在这里, 我们定义了一个完全空的结构体, 意味着这个虚拟稳压器不支持任何实际的控制操作.
* 任何对它进行控制的尝试都会被框架忽略或返回成功, 因为它不需要被控制.
*/
static const struct regulator_ops dummy_ops;
/*
* 定义一个静态的、常量 regulator_desc 结构体实例.
* regulator_desc 用于向稳压器框架描述一个稳压器的静态特性.
*/
static const struct regulator_desc dummy_desc = {
/*
* .name: 稳压器的名称. 当此虚拟稳压器满足一个未指定的电源请求时, 将使用此名称.
*/
.name = "regulator-dummy",
/*
* .id: 稳压器的ID号. -1 表示一个通用的、不区分ID的实例.
*/
.id = -1,
/*
* .type: 稳压器的类型. REGULATOR_VOLTAGE 表示这是一个电压稳压器.
*/
.type = REGULATOR_VOLTAGE,
/*
* .owner: 指向拥有此稳压器的模块. THIS_MODULE 是一个宏, 指向当前编译的内核模块.
*/
.owner = THIS_MODULE,
/*
* .ops: 指向我们上面定义的空的 regulator_ops 实例.
*/
.ops = &dummy_ops,
};
/*
* dummy_regulator_probe: 虚拟设备 "reg-dummy" 的 probe (探测) 函数.
*
* @fdev: 指向被探测的 faux_device 实例的指针.
* @return: 成功时返回0, 失败时返回一个负值的错误码.
*/
static int dummy_regulator_probe(struct faux_device *fdev)
{
/*
* 在栈上定义一个 regulator_config 结构体变量 config, 并将其内容初始化为零.
* regulator_config 用于在注册时向稳压器框架传递动态配置信息.
*/
struct regulator_config config = { };
/*
* 定义一个整型变量 ret, 用于存储函数调用的返回值.
*/
int ret;
/*
* 设置配置中的 .dev 成员, 将其指向虚拟设备的标准 device 结构体.
* 这样就将正在创建的稳压器与它的父设备("reg-dummy")关联了起来.
*/
config.dev = &fdev->dev;
/*
* 设置配置中的 .init_data 成员, 将其指向我们定义的 dummy_initdata.
* 这就将 "always_on" 这个约束传递给了注册函数.
*/
config.init_data = &dummy_initdata;
/*
* 调用 devm_regulator_register 函数, 执行注册操作.
* "devm_" 前缀表示这是一个 "设备管理" 的函数. 它会自动处理资源的释放.
* 当 "reg-dummy" 设备被移除时, 内核会自动调用 devm_regulator_unregister 来注销这个稳压器, 无需手动管理.
* @ &fdev->dev: 关联的设备.
* @ &dummy_desc: 稳压器的静态描述.
* @ &config: 稳压器的动态配置.
* 函数返回一个指向新创建的 regulator_dev 实例的指针, 或一个错误指针.
*/
dummy_regulator_rdev = devm_regulator_register(&fdev->dev, &dummy_desc,
&config);
/*
* 使用 IS_ERR 宏检查返回值是否为一个错误指针.
*/
if (IS_ERR(dummy_regulator_rdev)) {
/*
* 如果是错误指针, 使用 PTR_ERR 宏从中提取出负值的错误码.
*/
ret = PTR_ERR(dummy_regulator_rdev);
/*
* 使用 pr_err 打印一条内核错误级别的日志.
*/
pr_err("Failed to register regulator: %d\n", ret);
/*
* 返回错误码, 表示 probe 失败.
*/
return ret;
}
/*
* 如果注册成功, 返回0.
*/
return 0;
}
regulator_dummy_init: 初始化虚拟稳压器 (Dummy Regulator)
此函数的作用是在内核启动时, 创建并注册一个虚拟的(或称为"哑"、“假的”)稳压器设备。这个虚拟稳压器的核心原理是充当一个"万能的后备电源供应者"。在稳压器框架中, 当一个设备驱动程序请求一个电源供应(例如, “VDD_3V3”), 但系统中没有任何一个真实的、由硬件驱动的稳压器注册来提供这个电源时, 框架就会求助于这个虚拟稳压器。虚拟稳压器会无条件地"满足"这个请求, 使得设备驱动程序能够成功地完成初始化, 而不因缺少电源依赖而失败。
/*
* 定义一个名为 dummy_regulator_driver 的静态 faux_device_ops 结构体实例.
* faux_device_ops 是用于 "虚拟/伪造"(faux) 设备的驱动操作集.
* 在这里, 我们只定义了 .probe 操作.
* .probe: 当这个虚拟设备被系统 "发现" 并进行探测时, 内核将调用 dummy_regulator_probe 这个函数.
*/
struct faux_device_ops dummy_regulator_driver = {
.probe = dummy_regulator_probe,
};
/*
* 定义一个静态的、指向 faux_device 结构体的指针 dummy_fdev.
* 'static' 关键字意味着这个指针变量只在当前文件内可见.
* 它将用于保存由 faux_device_create 函数创建的虚拟设备实例的地址.
*/
static struct faux_device *dummy_fdev;
/*
* regulator_dummy_init: 虚拟稳压器的初始化函数.
* 'void' 表明该函数不接受任何参数.
* '__init' 是一个属性宏, 它告诉编译器和链接器这个函数只在内核初始化阶段被调用.
* 在初始化完成之后, 这个函数所占用的内存可以被内核回收, 以节省宝贵的RAM资源.
*/
void __init regulator_dummy_init(void)
{
/*
* 调用 faux_device_create 函数来创建一个新的虚拟设备实例.
* @ "reg-dummy": 新创建的虚拟设备的名称.
* @ NULL: 父设备的指针. NULL表示这个设备没有父设备, 它是一个顶层设备.
* @ &dummy_regulator_driver: 指向我们上面定义的驱动操作集的指针.
* 这个函数调用的效果是创建一个名为 "reg-dummy" 的设备, 并将其与 dummy_regulator_driver 关联起来.
* 创建过程会触发对 .probe 函数 (即 dummy_regulator_probe) 的调用, 从而完成虚拟稳压器的注册.
* 函数的返回值(新创建的设备实例的指针)被赋给 dummy_fdev.
*/
dummy_fdev = faux_device_create("reg-dummy", NULL, &dummy_regulator_driver);
/*
* 检查 faux_device_create 的返回值. 如果创建失败(例如, 因为内存不足), 它会返回 NULL.
*/
if (!dummy_fdev) {
/*
* 如果创建失败, 使用 pr_err 打印一条内核错误级别的日志消息.
*/
pr_err("Failed to allocate dummy regulator device\n");
/*
* 提前返回, 结束函数的执行.
*/
return;
}
}
drivers/regulator/core.c
generic_coupler_attach: 通用耦合器的连接回调函数
此函数是 generic_regulator_coupler (通用稳压器耦合器) 的 attach_regulator 回调实现。当一个稳压器设备 (rdev) 被发现存在级联关系 (即它的电源输入来自另一个稳压器) 时, 稳压器框架会调用此函数, 让通用耦合器来验证这种连接是否为其所支持。此函数的核心作用是执行一系列检查, 确保只有最简单、最安全的级联场景才被允许通过这个通用逻辑。
/*
* generic_coupler_attach: 通用耦合器的连接回调函数.
* 当一个稳压器设备需要被"附加"到耦合器上以处理其级联关系时, 此函数被调用.
*
* @coupler: 指向被调用的耦合器实例的指针 (在这里就是 generic_regulator_coupler).
* @rdev: 指向需要被附加和检查的稳压器设备的指针.
* @return: 如果连接有效则返回0, 如果配置不被支持则返回一个负值的错误码.
*/
static int generic_coupler_attach(struct regulator_coupler *coupler,
struct regulator_dev *rdev)
{
/*
* 检查被附加的稳压器 rdev 的耦合描述符中, 声明的被耦合稳压器的数量是否大于2.
* .coupling_desc.n_coupled > 2 表示这是一个涉及三个或更多稳压器的复杂耦合 (例如电压均衡).
*/
if (rdev->coupling_desc.n_coupled > 2) {
/*
* 如果是, 使用 rdev_err 打印一条与该设备关联的错误日志.
* 日志内容明确指出, 针对多个稳压器的电压均衡功能尚未实现.
*/
rdev_err(rdev,
"Voltage balancing for multiple regulator couples is unimplemented\n");
/*
* 返回 -EPERM (Operation not permitted), 表示不允许这种操作.
*/
return -EPERM;
}
/*
* 检查被附加的稳压器 rdev 的约束条件, 看它是否不是 "always_on" (永远在线) 的.
* rdev->constraints->always_on 为 false 意味着这个稳压器是可以被软件关闭的.
*/
if (!rdev->constraints->always_on) {
/*
* 如果该稳压器不是永远在线的, 打印一条错误日志.
* 日志内容明确指出, 对一个非永远在线的稳压器进行耦合的功能尚未实现.
* 这是因为管理可开关的级联稳压器的电源时序非常复杂.
*/
rdev_err(rdev,
"Coupling of a non always-on regulator is unimplemented\n");
/*
* 返回 -ENOTSUPP (Operation not supported), 表示不支持这种配置.
*/
return -ENOTSUPP;
}
/*
* 如果以上所有检查都通过了, 说明这是一个该通用耦合器可以接受的、简单的、
* 永远在线的稳压器之间的级联关系.
* 返回 0, 表示连接成功.
*/
return 0;
}
regulator_coupler_register: 注册一个稳压器耦合器
此代码片段的作用是向Linux稳压器框架注册一个"耦合器" (coupler)。在稳压器框架中, 耦合器是一个关键的组件, 用于管理不同稳压器之间的物理连接和依赖关系, 最常见的情况是一个稳压器的输出 (VOUT) 作为另一个稳压器的输入 (VIN)。regulator_coupler_register 函数将一个新的耦合器实例添加到一个全局链表中, 以便在需要解析和建立这种依赖关系时被框架调用。
/*
* 定义一个静态的 regulator_coupler 结构体实例, 名为 generic_regulator_coupler.
* 'static' 关键字表示这个变量只在当前文件内可见.
* 这是一个通用的耦合器, 用于处理标准的稳压器级联情况.
*/
static struct regulator_coupler generic_regulator_coupler = {
/*
* .attach_regulator: 这是耦合器的核心回调函数之一.
* 当框架需要建立两个稳压器之间的连接时, 就会调用这个函数.
* 在这里, 它被设置为 generic_coupler_attach 函数的地址.
*/
.attach_regulator = generic_coupler_attach,
};
/*
* regulator_coupler_register: 将一个稳压器耦合器注册到框架中.
*
* @coupler: 指向要注册的 regulator_coupler 实例的指针.
* @return: 总是返回 0, 表示成功.
*/
int regulator_coupler_register(struct regulator_coupler *coupler)
{
/*
* 对全局的 regulator_list_mutex 互斥锁进行加锁.
* 这个锁保护着下面的 regulator_coupler_list 全局链表, 防止并发访问.
* 在单核抢占式内核中, 这可以防止一个任务在修改链表时被另一个任务抢占, 从而避免数据竞争.
*/
mutex_lock(®ulator_list_mutex);
/*
* 调用 list_add_tail, 将传入的 coupler 实例中的 .list 成员(一个 list_head 结构体)
* 添加到全局的 regulator_coupler_list 链表的尾部.
* 这样, 新的耦合器就成为了系统已知耦合器的一部分.
*/
list_add_tail(&coupler->list, ®ulator_coupler_list);
/*
* 解锁互斥锁, 允许其他任务访问该链表.
*/
mutex_unlock(®ulator_list_mutex);
/*
* 返回 0, 表示注册操作成功.
*/
return 0;
}
regulator_init: 初始化内核稳压器(Regulator)子系统
此函数在内核启动过程中被调用, 其核心作用是完成稳压器(Regulator)框架的初始化。这包括在sysfs中注册一个设备类(class), 创建用于调试的debugfs接口, 并注册一些框架级的组件, 如虚拟稳压器(dummy regulator)和通用耦合器(coupler)。稳压器框架是Linux内核中用于管理系统中各种电源(如LDO, Buck/Boost转换器)的标准接口。
- 在单核无MMU的STM32H750平台上的原理与作用
在STM32H750这样的嵌入式系统中, 精确的电源管理至关重要。片上可能集成了多个LDO或有外部的PMIC(电源管理芯片), 用于为CPU核心、外设、内存等提供不同等级的电压。regulator框架为控制这些硬件提供了统一的软件模型。
regulator_init 函数本身是硬件无关的, 它建立的是软件框架。它的执行流程在STM32上和在其他平台上是相同的。
class_register: 这会在/sys/class/目录下创建一个regulator目录。当具体的稳压器驱动(例如, 控制STM32片上LDO的驱动)被加载并注册一个稳压器设备时, 相应的设备符号链接会出现在这个目录下, 为用户空间提供了查询和控制电源状态的标准接口。- Debugfs: 如果内核配置了
CONFIG_DEBUG_FS, 此函数会创建一个/sys/kernel/debug/regulator目录, 并填充一些用于调试的文件。这对于在STM32上开发和调试电源相关的驱动程序非常有用, 开发者可以通过查看这些文件来快速了解系统中所有稳压器的状态、约束关系和供电拓扑。 - Dummy Regulator & Coupler: 这些是框架的组成部分, 即使没有物理稳压器驱动, 它们也能确保框架可以正常工作, 并处理不同电源域之间的耦合关系。
总而言之, regulator_init为STM32平台上的电源管理打下了基础软件框架, 使得上层的设备驱动可以简单地通过API来请求或释放所需的电压, 而不必关心底层具体的PMIC或LDO是如何被控制的。
/*
* regulator_init: 稳压器(regulator)框架的初始化函数.
* 这是一个静态函数, 标记为 __init, 表示它仅在内核初始化期间执行,
* 其占用的内存之后可能会被回收.
* @return: 返回 class_register 的执行结果, 0表示成功, 负值表示错误.
*/
static int __init regulator_init(void)
{
/*
* 定义一个整型变量 ret, 用于存储函数调用的返回值(错误码).
*/
int ret;
/*
* 调用 class_register, 向内核注册一个新的设备类.
* @ ®ulator_class: 指向一个全局的 struct class 实例, 该实例描述了"regulator"这个设备类.
* 这个函数调用成功后, 会在 sysfs 文件系统中创建 /sys/class/regulator/ 目录.
* 所有后续注册的稳压器设备都会出现在这个目录下.
* 将函数的返回值存入 ret.
*/
ret = class_register(®ulator_class);
/*
* 调用 debugfs_create_dir, 在debugfs文件系统的根目录下创建一个名为 "regulator" 的新目录.
* @ "regulator": 目录的名称.
* @ NULL: 父目录的指针, NULL表示在debugfs的根目录下创建.
* 返回的目录描述符指针被存入全局变量 debugfs_root.
* debugfs 是一个用于内核调试的虚拟文件系统, 通常挂载在 /sys/kernel/debug.
*/
debugfs_root = debugfs_create_dir("regulator", NULL);
/*
* 检查 debugfs_create_dir 的返回值. 如果创建失败, 它会返回一个错误指针.
* IS_ERR 宏用于判断一个指针是否为错误指针.
*/
if (IS_ERR(debugfs_root))
/*
* 如果创建失败, 打印一条调试级别的消息. 在默认的内核日志级别下, 这条消息可能不会显示.
*/
pr_debug("regulator: Failed to create debugfs directory\n");
/*
* #ifdef CONFIG_DEBUG_FS 是一个预处理指令.
* 下面的代码只有在内核编译时启用了 CONFIG_DEBUG_FS 选项时才会被包含进来.
* 如果没有启用debugfs, 这部分代码会被完全忽略.
*/
#ifdef CONFIG_DEBUG_FS
/*
* 在 "regulator" debugfs 目录下创建一个名为 "supply_map" 的文件.
* @ "supply_map": 文件名.
* @ 0444: 文件的权限, 表示所有用户都可读, 但无人可写.
* @ debugfs_root: 父目录的描述符.
* @ NULL: 文件私有数据, 这里为NULL.
* @ &supply_map_fops: 一个 file_operations 结构体, 定义了对此文件进行读/写等操作时应调用的函数.
*/
debugfs_create_file("supply_map", 0444, debugfs_root, NULL,
&supply_map_fops);
/*
* 同样, 在 "regulator" debugfs 目录下创建一个名为 "regulator_summary" 的文件.
* 这个文件用于提供系统中所有稳压器的摘要信息.
*/
debugfs_create_file("regulator_summary", 0444, debugfs_root,
NULL, ®ulator_summary_fops);
#endif
/*
* 调用 regulator_dummy_init, 初始化虚拟稳压器(dummy regulator)功能.
* 虚拟稳压器允许那些在设备树中声明了电源需求、但实际系统中没有物理稳压器驱动的设备能够正常工作.
*/
regulator_dummy_init();
/*
* 调用 regulator_coupler_register, 注册一个通用的稳压器耦合器.
* 耦合器用于管理多个稳压器之间的电压依赖关系 (例如, 一个稳压器的输出是另一个稳y压器的输入).
*/
regulator_coupler_register(&generic_regulator_coupler);
/*
* 返回 class_register 函数的执行结果.
* 如果 class_register 失败, 整个子系统的初始化被认为是失败的.
*/
return ret;
}
_notifier_call_chain: 调节器事件的内部广播引擎
这是一个静态内部函数, 是regulator_notifier_call_chain的核心实现。它的作用是将一个调节器事件分发给两个不同的、并行的通知渠道: (1) 内核内部的订阅者, 以及 (2) 用户空间的监听者。它充当了将硬件事件转化为多路软件通知的中心枢纽。
该函数的原理可以清晰地分为两个独立的步骤:
-
内核内部通知 (In-Kernel Notification): 这是最主要、最直接的通知路径。
- 它调用
blocking_notifier_call_chain(&rdev->notifier, event, data). 这是对Linux内核通用通知链(Notifier Chain)框架的直接调用。 - 工作机制:
rdev->notifier是一个链表头, 链接了所有通过regulator_register_notifier()订阅了此特定调节器(rdev)事件的notifier_block结构。blocking_notifier_call_chain会同步地(即在当前函数的上下文中, “阻塞式地”)遍历这个链表, 并依次执行每个notifier_block中注册的回调函数。 - 目的: 这允许内核中的其他驱动程序(消费者)对电源事件做出直接、实时的反应。例如, 一个SD卡驱动在收到其电源调节器即将关闭的事件(
REGULATOR_EVENT_PRE_DISABLE)时, 可以在其回调函数中立即停止所有I/O操作, 确保数据完整性。调用者必须持有互斥锁, 就是为了保护这个链表在遍历期间不被其他线程修改, 从而保证线程安全。
- 它调用
-
用户空间通知 (Userspace Notification via Netlink): 这是一个可选的、用于与用户空间通信的路径。
- 它首先通过
IS_REACHABLE(CONFIG_REGULATOR_NETLINK_EVENTS)检查内核是否在编译时开启了此功能。如果未开启, 这部分代码将被完全优化掉。 - 唯一名称生成: 为了让用户空间的程序能够明确识别是哪个调节器产生了事件, 它精心构造了一个唯一的名字。如果调节器的名字(如"LDO1")与其父设备(如一个名为"pmic-max77802"的PMIC芯片)的名字不同, 它会创建一个组合名, 如
pmic-max77802-LDO1。这解决了系统中可能存在多个同名(但属于不同PMIC)调节器的歧义问题。 - 发送Netlink事件:
reg_generate_netlink_event()函数会创建一个Netlink消息, 其中包含这个唯一的调节器名称和事件代码, 然后将此消息广播出去。 - 目的: 这允许用户空间的守护进程(daemon)或监控工具(如
udev或自定义的电源管理程序)接收并响应电源事件。例如, 一个程序可以在收到REGULATOR_EVENT_OVER_CURRENT事件时, 记录一条系统日志, 或者在图形界面上弹出一个警告。
- 它首先通过
/* 通知调节器消费者和下游调节器消费者.
* 注意: 调用者必须持有互斥锁.
*/
static int _notifier_call_chain(struct regulator_dev *rdev,
unsigned long event, void *data)
{
/*
* --- 1. 内核内部通知 ---
* 首先调用 rdev (本调节器) 的通知链.
* blocking_notifier_call_chain 是一个内核标准函数, 它会同步地
* 遍历 rdev->notifier 链表中的所有订阅者, 并调用它们的回调函数.
* 任何通过 regulator_register_notifier() 注册的驱动都会在这里收到通知.
* 其返回值(成功/失败)被保存在 ret 中.
*/
int ret = blocking_notifier_call_chain(&rdev->notifier, event, data);
/*
* --- 2. 用户空间通知 (可选) ---
* IS_REACHABLE 是一个编译时宏, 用于检查 CONFIG_REGULATOR_NETLINK_EVENTS
* 选项是否被启用(无论是编译进内核还是作为模块).
* 如果未启用, 这整个 if 块的代码将不存在于最终的二进制文件中.
*/
if (IS_REACHABLE(CONFIG_REGULATOR_NETLINK_EVENTS)) {
/*
* 获取父设备指针.
*/
struct device *parent = rdev->dev.parent;
/*
* 获取调节器的名称.
*/
const char *rname = rdev_get_name(rdev);
/*
* 定义一个字符数组, 用于构造唯一的名称.
*/
char name[32];
/*
* 构造唯一名称的逻辑:
* 避免重复的 debugfs 目录名. 同样地, 这也避免了用户空间命名的歧义.
* 如果调节器有父设备, 并且它当前的名字就是其描述符中的通用名
* (例如, 很多PMIC都有一个叫 "LDO1" 的调节器),
* 那么我们就创建一个更具体的名字.
*/
if (parent && rname == rdev->desc->name) {
/*
* 使用 snprintf 安全地将父设备名和调节器名组合起来,
* 例如: "pmic-max77802" + "LDO1" -> "pmic-max77802-LDO1".
*/
snprintf(name, sizeof(name), "%s-%s", dev_name(parent),
rname);
/*
* 将 rname 指向这个新构造的、更唯一的名称.
*/
rname = name;
}
/*
* 调用 reg_generate_netlink_event,
* 将唯一的名称和事件代码打包成一个Netlink消息, 并广播给用户空间.
*/
reg_generate_netlink_event(rname, event);
}
/*
* 返回内核内部通知链的执行结果.
* 用户空间通知的成功与否不影响此返回值.
*/
return ret;
}
regulator_notifier_call_chain 和 regulator_handle_critical: 两级调节器事件处理框架
这两个函数共同构成了一个强大的、两级(two-tiered)的事件处理框架, 用于响应来自电压调节器(regulator)驱动程序的事件。其核心原理是将事件处理分为两个阶段: (1) 对可能危及整个系统稳定的"关键事件"进行立即的、预定义的、强制性的处理; (2) 将所有事件(无论是否关键)广播给所有对此感兴趣的"订阅者"(其他内核驱动程序), 以便它们进行各自的、灵活的响应。
regulator_handle_critical: 第一级 - 系统关键事件的"安全网"
这是一个静态内部函数, 它的唯一职责是充当一个系统级的安全网。它只关心那些被明确标记为"系统关键"的调节器, 并且只对最严重的故障事件(欠压、过流、通用失败)做出反应。
/**
* regulator_handle_critical - 处理系统关键调节器的事件.
* @rdev: 调节器设备.
* @event: 正在处理的事件.
*
* 此函数为被认为是系统关键的调节器处理关键事件, 例如欠压、
* 过流和未知错误. 当检测到此类事件时, 它会触发一个具有
* 定义超时的硬件保护性关机.
*/
static void regulator_handle_critical(struct regulator_dev *rdev,
unsigned long event)
{
/*
* 定义一个指向常量字符串的指针 reason, 用于存储故障原因.
*/
const char *reason = NULL;
/*
* 这是本函数的"看门人".
* 它检查此调节器的约束(constraints)中, system_critical 标志是否被设置.
* 这个标志通常在设备树中为调节器节点设置, 用以标识那些为CPU核心、
* 内存等关键部件供电的调节器.
* 如果此调节器不是系统关键的, 函数立即返回, 不做任何事.
*/
if (!rdev->constraints->system_critical)
return;
/*
* 使用 switch 语句来筛选事件. 只对最严重的故障事件做出反应.
*/
switch (event) {
case REGULATOR_EVENT_UNDER_VOLTAGE:
/*
* 如果是欠压事件, 设置一个描述性的原因字符串.
*/
reason = "System critical regulator: voltage drop detected";
break;
case REGULATOR_EVENT_OVER_CURRENT:
reason = "System critical regulator: over-current detected";
break;
case REGULATOR_EVENT_FAIL:
reason = "System critical regulator: unknown error";
}
/*
* 如果事件不是上述关键事件之一, reason 将保持为NULL.
*/
if (!reason)
return;
/*
* 这是最关键的操作: 触发硬件保护机制.
* hw_protection_trigger 是一个平台相关的函数, 它会启动一个
* 无法被软件轻易阻止的硬件关机或复位序列.
* @reason: 传递原因字符串, 以便在系统重启后的日志中进行调试.
* @rdev->constraints->uv_less_critical_window_ms: 传递一个超时值,
* 这通常是一个非常短的时间窗口, 给系统一个最后的机会来尝试同步,
* 但最终会强制关机.
* 这是一个"大红按钮", 是防止硬件损坏或严重数据损坏的最后一道防线.
*/
hw_protection_trigger(reason,
rdev->constraints->uv_less_critical_window_ms);
}
regulator_notifier_call_chain: 第二级 - 通用事件广播中心
这是暴露给所有调节器驱动程序使用的公共API。它负责编排整个两级事件处理流程。
/**
* regulator_notifier_call_chain - 调用调节器事件通知链
* @rdev: 事件来源的调节器
* @event: 事件类型
* @data: 传递给回调的特定数据.
*
* 由调节器驱动程序调用, 以通知客户端(其他驱动)一个调节器事件已经发生.
*
* 返回: %NOTIFY_DONE.
*/
int regulator_notifier_call_chain(struct regulator_dev *rdev,
unsigned long event, void *data)
{
/*
* 第一步: 首先调用 regulator_handle_critical.
* 这确保了无论后续通知链的处理结果如何, 如果这是一个关键事件,
* 系统的"安全网"总会被最先触发.
*/
regulator_handle_critical(rdev, event);
/*
* 第二步: 调用 _notifier_call_chain (这是内核通知链框架的底层实现).
* 这个函数会:
* 1. 遍历一个与此调节器设备(rdev)关联的"通知链"(一个回调函数列表).
* 2. 任何通过 regulator_register_notifier() 订阅了此调节器事件的驱动程序,
* 它们的回调函数都会被依次调用.
* 3. 事件类型(event)和附加数据(data)会被传递给每一个回调函数.
* 这允许消费者驱动(例如一个显示屏驱动)在收到其电源调节器的欠压事件时,
* 能够采取自己的措施, 比如尝试优雅地关闭显示面板.
*/
_notifier_call_chain(rdev, event, data);
/*
* 返回 NOTIFY_DONE, 这是通知链框架的标准返回值, 表示通知已完成.
*/
return NOTIFY_DONE;
}
/*
* 将此函数导出, 使其对所有遵循GPL许可证的内核模块(即所有调节器驱动)可用.
*/
EXPORT_SYMBOL_GPL(regulator_notifier_call_chain);
drivers/regulator/of_regulator.c
of_get_regulator_init_data & of_get_regulation_constraints
这两个函数是Linux内核稳压器(Regulator)框架中设备树(Device Tree)解析的核心。它们协同工作, 读取设备树节点中定义的所有标准电源约束属性, 并将它们翻译成内核可以理解和使用的C语言数据结构。这套机制是现代Linux驱动实现"数据驱动"设计的典范, 即用设备树中的数据来描述硬件配置, 而不是在驱动代码中硬编码。
of_get_regulator_init_data: 顶层封装函数
此函数是暴露给外部驱动程序(如 reg_fixed_voltage_probe)调用的顶层API。它的作用非常简单直接: 它负责分配内存并调用核心工作函数of_get_regulation_constraints。
原理:
它扮演一个安全的封装者(wrapper)的角色。
- 它首先使用
devm_kzalloc为regulator_init_data结构体分配资源管理的内存。这确保了即使后续的解析失败或驱动probe过程在其他地方失败, 这块内存也会被自动释放, 避免了内存泄漏。 - 然后, 它将主要的解析任务委托给
of_get_regulation_constraints函数。 - 这种封装将内存分配和核心解析逻辑分离开来, 使得代码结构更清晰。
of_get_regulation_constraints: 核心解析引擎
这是真正执行所有设备树属性解析工作的核心引擎。它的职责是系统性地、逐一地读取设备树节点中的每一个与稳压器相关的标准属性, 并将这些信息填充到regulation_constraints结构体中。
原理:
此函数是一个全面的、基于设备树标准绑定的解析器, 其工作流程如下:
- 解析基本约束: 它读取最基本的属性, 如
regulator-name(名称),regulator-min/max-microvolt(电压范围), 以及regulator-min/max-microamp(电流范围)。 - 推断有效操作: 在解析的同时, 它会进行逻辑推断。例如, 如果
min_uV和max_uV不相等, 它就知道这个调节器的电压是可变的, 于是就在valid_ops_mask中设置REGULATOR_CHANGE_VOLTAGE标志位。这个掩码最终会告诉稳压器核心框架, 哪些操作(如改变电压、改变模式)对于这个特定的调节器是合法的。 - 解析行为标志: 它读取一系列布尔型属性, 如
regulator-always-on(是否永不关闭)、regulator-boot-on(是否在启动时默认开启), 这些标志直接决定了调节器的默认行为和电源管理策略。 - 解析时序参数: 它读取与电源时序相关的属性, 如
regulator-ramp-delay(电压爬升速率)和各种settling-time(稳定时间), 这些参数对于保证硬件上电顺序和稳定性至关重要。 - 处理操作模式: 对于支持多种工作模式(如低功耗模式、性能模式)的调节器, 它会解析
regulator-initial-mode和regulator-allowed-modes。这里有一个关键的抽象: 它使用驱动提供的desc->of_map_mode回调函数, 将设备树中定义的数值(特定于硬件)映射成内核内部统一的模式宏(如REGULATOR_MODE_NORMAL), 从而实现了通用解析逻辑与特定驱动实现的解耦。 - 解析挂起状态: 这是最复杂也是功能最强大的部分。它会通过
of_get_child_by_name查找名为state_mem、state_disk等的子节点。如果找到, 它会递归地解析这些子节点内部的属性(如regulator-mode,regulator-on-in-suspend)。这允许设备树以极其精细的方式定义调节器在各种系统睡眠状态(如待机、休眠)下的行为, 是实现高级电源管理的基础。
在STM32H750这样的嵌入式系统上, 这套代码是连接设备树(描述了板级电源拓扑)和内核电源管理框架的桥梁。无论是片上LDO, 还是由外部PMIC提供的电源, 它们的约束和行为都是通过这套函数解析后才为内核所知的。
/*
* of_get_regulation_constraints - 解析设备树中定义的电源调节约束.
* 这是核心的工作函数.
*/
static int of_get_regulation_constraints(struct device *dev,
struct device_node *np,
struct regulator_init_data **init_data,
const struct regulator_desc *desc)
{
struct regulation_constraints *constraints = &(*init_data)->constraints;
// ... (变量定义)
// --- 解析基本属性: 名称、电压、电流 ---
constraints->name = of_get_property(np, "regulator-name", NULL);
if (!of_property_read_u32(np, "regulator-min-microvolt", &pval))
constraints->min_uV = pval;
if (!of_property_read_u32(np, "regulator-max-microvolt", &pval))
constraints->max_uV = pval;
// --- 逻辑推断: 根据电压范围判断是否可变, 并设置有效操作掩码 ---
if (constraints->min_uV != constraints->max_uV)
constraints->valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE;
if (constraints->min_uV && constraints->max_uV)
constraints->apply_uV = true; // 标志在启动时需要应用电压
if (!of_property_read_u32(np, "regulator-min-microamp", &pval))
constraints->min_uA = pval;
if (!of_property_read_u32(np, "regulator-max-microamp", &pval))
constraints->max_uA = pval;
if (constraints->min_uA != constraints->max_uA)
constraints->valid_ops_mask |= REGULATOR_CHANGE_CURRENT;
// --- 解析行为标志 ---
constraints->boot_on = of_property_read_bool(np, "regulator-boot-on");
constraints->always_on = of_property_read_bool(np, "regulator-always-on");
if (!constraints->always_on)
constraints->valid_ops_mask |= REGULATOR_CHANGE_STATUS; // 如果不是永远开启, 状态就应该是可变的
// --- 解析时序参数 ---
ret = of_property_read_u32(np, "regulator-ramp-delay", &pval);
if (!ret) { /* ... */ } // 读取电压爬坡延迟
ret = of_property_read_u32(np, "regulator-settling-time-us", &pval);
if (!ret)
constraints->settling_time = pval; // 读取稳定时间
// ... (处理 up/down 稳定时间的逻辑, 并对模糊配置告警)
// --- 解析操作模式 ---
if (!of_property_read_u32(np, "regulator-initial-mode", &pval)) {
if (desc && desc->of_map_mode) { // 检查驱动是否提供了模式映射函数
mode = desc->of_map_mode(pval); // 调用驱动的回调来翻译模式值
if (mode != REGULATOR_MODE_INVALID)
constraints->initial_mode = mode;
} // ... (错误/警告处理)
}
len = of_property_count_elems_of_size(np, "regulator-allowed-modes", sizeof(u32));
if (len > 0) {
if (desc && desc->of_map_mode) {
for (i = 0; i < len; i++) {
// ... (读取允许的模式列表, 并通过 of_map_mode 翻译)
}
if (constraints->valid_modes_mask)
constraints->valid_ops_mask |= REGULATOR_CHANGE_MODE;
} // ... (警告处理)
}
// --- 解析挂起状态 (Suspend States) ---
for (i = 0; i < ARRAY_SIZE(regulator_states); i++) {
// ... (根据循环变量 i, 确定当前要解析的挂起状态, 如 PM_SUSPEND_MEM)
// 查找名为 "state-mem", "state-disk" 等的子节点
suspend_np = of_get_child_by_name(np, regulator_states[i]);
if (!suspend_np)
continue; // 如果没有该子节点, 继续下一个状态
// ... (在 suspend_np 子节点中, 递归地解析 "regulator-mode", "regulator-on-in-suspend" 等属性)
// 这允许为每个睡眠状态定义不同的电压和模式.
of_node_put(suspend_np); // 释放对子节点的引用
}
return 0;
}
/**
* of_get_regulator_init_data - 提取 regulator_init_data 结构体信息
* (顶层API函数)
*/
struct regulator_init_data *of_get_regulator_init_data(struct device *dev,
struct device_node *node,
const struct regulator_desc *desc)
{
struct regulator_init_data *init_data;
if (!node)
return NULL;
// 1. 分配资源管理的内存
init_data = devm_kzalloc(dev, sizeof(*init_data), GFP_KERNEL);
if (!init_data)
return NULL;
// 2. 调用核心工作函数执行解析
if (of_get_regulation_constraints(dev, node, &init_data, desc))
return NULL; // 如果解析失败, devm_* 机制会处理内存, 这里直接返回NULL即可
// 3. 返回填充好的数据结构
return init_data;
}
EXPORT_SYMBOL_GPL(of_get_regulator_init_data);
drivers/regulator/fixed.c
of_get_fixed_voltage_config: 从设备树解析固定电压调节器的配置
此函数的核心作用是充当一个专门的设备树(Device Tree)解析器, 为"固定电压调节器"驱动的probe函数服务。它负责读取设备树节点中与电源调节器相关的标准和非标准属性, 并将这些信息汇总、填充到一个struct fixed_voltage_config结构体中。这个结构体随后会被probe函数用来初始化硬件和注册调节器。
该函数的原理是分层解析和数据转换:
- 调用通用解析器: 它不直接解析所有的设备树属性, 而是首先调用一个更通用的内核辅助函数
of_get_regulator_init_data。这个通用函数会处理所有Linux稳压器框架定义的标准绑定属性, 例如regulator-name,regulator-min-microvolt,regulator-max-microvolt,regulator-boot-on等, 并将它们填充到一个标准的regulator_init_data结构体中。 - 验证与转换: 获取到标准的
regulator_init_data后, 此函数会进行针对"固定电压"调节器的特定验证。最重要的一步是检查min_uV和max_uV是否相等。对于一个"固定"电压源, 这是逻辑上的必然要求。如果它们不相等, 说明设备树配置有误, 函数会报错返回。验证通过后, 它会将这些标准属性的值"翻译"并复制到fixed_voltage_config结构体中对应的字段。 - 解析特定属性: 最后, 它会继续解析一些此"固定电压"驱动所特有的、非标准的设备树属性, 如
startup-delay-us(启动延迟)和off-on-delay-us(关闭再开启延迟), 并将它们也填充到config结构体中。
通过这种方式, 该函数将设备树中分散的、基于字符串的属性描述, 转换成了一个驱动程序可以直接使用的、结构化的C语言数据结构, 极大地简化了probe函数本身的逻辑。它也体现了良好的分层设计思想, 即先用通用框架处理通用问题, 再在特定驱动中处理特定逻辑。
/**
* of_get_fixed_voltage_config - 提取 fixed_voltage_config 结构体信息
* @dev: 请求 fixed_voltage_config 的设备
* @desc: 调节器描述符
*
* 通过从设备树节点中提取数据来填充 fixed_voltage_config 结构体.
*
* Return: 返回一个指向已填充的 &struct fixed_voltage_config 的指针,
* 如果内存分配失败则返回错误指针.
*/
static struct fixed_voltage_config *
of_get_fixed_voltage_config(struct device *dev,
const struct regulator_desc *desc)
{
struct fixed_voltage_config *config;
struct device_node *np = dev->of_node;
struct regulator_init_data *init_data;
/*
* 使用 devm_kzalloc 分配并清零 config 结构体的内存.
* 'devm_' 前缀确保了这块内存在驱动卸载时会被自动释放.
*/
config = devm_kzalloc(dev, sizeof(struct fixed_voltage_config),
GFP_KERNEL);
if (!config)
return ERR_PTR(-ENOMEM); // 如果内存分配失败, 返回错误指针.
/*
* 调用内核稳压器框架提供的标准辅助函数 of_get_regulator_init_data.
* 这个函数会解析设备树中所有标准的 regulator 绑定属性,
* 并将它们填充到一个 regulator_init_data 结构体中.
*/
config->init_data = of_get_regulator_init_data(dev, dev->of_node, desc);
if (!config->init_data)
/*
* 如果解析失败 (例如, 缺少必要的标准属性), 返回错误.
* of_get_regulator_init_data 内部会处理内存分配, 失败时返回NULL或错误指针.
*/
return ERR_PTR(-EINVAL);
/*
* 为了方便使用, 创建一个指向 init_data 的局部指针.
*/
init_data = config->init_data;
/*
* 设置 apply_uV 标志为0. 对于固定电压调节器, 电压是不可变的,
* 因此不应在启动时尝试去"应用"或改变电压值.
*/
init_data->constraints.apply_uV = 0;
/*
* 将从设备树中解析出的调节器名称 (如 "vdda-supply") 赋值给 config->supply_name.
*/
config->supply_name = init_data->constraints.name;
/*
* --- 核心验证逻辑 ---
* 检查从设备树解析出的最小电压和最大电压是否相等.
*/
if (init_data->constraints.min_uV == init_data->constraints.max_uV) {
/*
* 如果相等, 那么这个值就是此固定调节器的输出电压.
*/
config->microvolts = init_data->constraints.min_uV;
} else {
/*
* 如果不相等, 这是一个配置错误. 固定电压调节器不能有可变的电压范围.
*/
dev_err(dev,
"Fixed regulator specified with variable voltages\n");
return ERR_PTR(-EINVAL);
}
/*
* 如果设备树中存在 "regulator-boot-on" 属性, init_data->constraints.boot_on 会为真.
*/
if (init_data->constraints.boot_on)
config->enabled_at_boot = true; // 设置 config 中的相应标志.
/*
* 解析本驱动特有的、非标准的设备树属性.
* of_property_read_u32 会尝试读取名为 "startup-delay-us" 的u32属性,
* 并将其值存入 config->startup_delay. 如果属性不存在, 该变量的值不会被改变.
*/
of_property_read_u32(np, "startup-delay-us", &config->startup_delay);
of_property_read_u32(np, "off-on-delay-us", &config->off_on_delay);
/*
* 检查是否存在 "vin-supply" 属性. 这是另一种指定上游电源的方式.
* 如果存在, 硬编码上游电源的逻辑名称为 "vin".
*/
if (of_property_present(np, "vin-supply"))
config->input_supply = "vin";
/*
* 返回填充好的 config 结构体指针.
*/
return config;
}
reg_fixed_get_irqs: 获取并注册固定电压调节器的可选中断
/**
* reg_fixed_get_irqs - 为固定电压调节器获取并注册可选的IRQ.
* @dev: 指向设备结构体的指针.
* @priv: 指向包含私有数据的 fixed_voltage_data 结构体的指针.
*
* 此函数尝试从设备的固件节点(如设备树)中获取IRQ.
* 如果它是一个可选的IRQ并且没有找到, 函数返回0.
* 否则, 它会尝试请求一个线程化的IRQ.
*
* 返回: 成功时返回0, 失败时返回一个负的错误码.
*/
static int reg_fixed_get_irqs(struct device *dev,
struct fixed_voltage_data *priv)
{
int ret;
/*
* 调用 fwnode_irq_get, 从与此设备关联的固件节点(通常是设备树节点)
* 中获取索引为0(第一个)的中断号.
*/
ret = fwnode_irq_get(dev_fwnode(dev), 0);
/*
* 这是一个关键检查, 用于处理"可选"中断.
* 按照约定, 如果在设备树中找不到中断属性, fwnode_irq_get 会返回 -EINVAL.
* 在这种情况下, 我们不认为这是一个错误, 而是正常情况, 直接返回0表示成功.
* 这使得驱动可以在没有中断支持的情况下继续工作.
*/
if (ret == -EINVAL)
return 0;
/*
* 如果 ret 是其他负值, 说明发生了真实的错误 (例如-EPROBE_DEFER, 表示中断控制器未就绪).
*/
if (ret < 0)
/*
* dev_err_probe 是一个辅助函数, 它会打印错误信息并返回原始的错误码.
* 这样做可以正确地将 -EPROBE_DEFER 传递给驱动核心, 以便稍后重试.
*/
return dev_err_probe(dev, ret, "Failed to get IRQ\n");
/*
* 如果成功获取了中断号(现在存储在ret中), 就调用 devm_request_threaded_irq 来请求它.
* devm_: 这是一个资源管理的版本, 内核会在驱动卸载时自动释放这个中断, 使代码更健壮.
* threaded_irq: 请求一个线程化的中断. 处理程序将在一个专门的内核线程中运行, 而不是在硬中断上下文中.
* dev: 中断所属的设备.
* ret: 要请求的中断号.
* NULL: 主中断处理程序(硬中断上下文), 设置为NULL表示我们不需要它.
* reg_fixed_under_voltage_irq_handler: 线程化的中断处理程序.
* IRQF_ONESHOT: 一个标志, 表示在处理程序返回之前, 中断线将保持禁用状态. 这有助于防止中断风暴.
* "under-voltage": 中断的描述性名称, 会出现在 /proc/interrupts 中.
* priv: 传递给中断处理程序的私有数据指针.
*/
ret = devm_request_threaded_irq(dev, ret, NULL,
reg_fixed_under_voltage_irq_handler,
IRQF_ONESHOT, "under-voltage", priv);
if (ret)
/*
* 如果请求中断失败, 打印错误并返回错误码.
*/
return dev_err_probe(dev, ret, "Failed to request IRQ\n");
/*
* 所有步骤都成功.
*/
return 0;
}
reg_fixed_under_voltage_irq_handler: 欠压中断处理程序
/*
* 静态函数: reg_fixed_under_voltage_irq_handler
* 这是一个中断服务例程 (ISR).
* @irq: 触发此处理程序的IRQ编号.
* @data: 在请求中断时传递的私有数据指针 (即 'priv').
* @return: irqreturn_t 类型, 通常是 IRQ_HANDLED 或 IRQ_NONE.
*/
static irqreturn_t reg_fixed_under_voltage_irq_handler(int irq, void *data)
{
/*
* 将 void* 类型的 data 指针强制转换回其原始类型 fixed_voltage_data.
* 这是获取中断处理上下文的标准做法.
*/
struct fixed_voltage_data *priv = data;
/*
* 从私有数据中获取指向 regulator_dev 的指针.
* regulator_dev 是内核中代表一个调节器设备的核心结构.
*/
struct regulator_dev *rdev = priv->dev;
/*
* 这是此处理程序的唯一核心操作.
* 调用 regulator_notifier_call_chain, 将一个欠压事件广播出去.
* @rdev: 事件的来源.
* @REGULATOR_EVENT_UNDER_VOLTAGE: 要广播的事件类型. 这是一个在调节器框架中定义的标准事件.
* @NULL: 传递给订阅者的可选数据, 这里没有.
* 其他订阅了此调节器事件的内核代码将会被唤醒并执行它们的回调函数.
*/
regulator_notifier_call_chain(rdev, REGULATOR_EVENT_UNDER_VOLTAGE,
NULL);
/*
* 返回 IRQ_HANDLED, 告诉内核中断已经被成功处理.
*/
return IRQ_HANDLED;
}
固定电压稳压器驱动的操作逻辑与数据结构
此代码片段定义了一组函数和数据结构, 它们共同实现了一个抽象的"固定电压稳压器"(Fixed Voltage Regulator)驱动程序的核心操作逻辑。这个驱动程序的独特之处在于, 它并不直接通过I2C、SPI或GPIO来控制一个物理的稳压器芯片, 而是通过将"使能/禁用稳压器"这一抽象请求, 转化为对Linux内核中其他子系统资源(具体是时钟或通用功耗域)的控制来实现其功能。
其核心原理是利用引用计数机制(enable_counter), 将一个虚拟的稳压器设备作为其所依赖的物理资源(时钟或功耗域)的代理。
宏定义与核心数据结构
/*
* 宏定义: FV_DEF_EMERG_SHUTDWN_TMO
* 定义了在紧急关机情况下的默认等待超时时间, 单位为毫秒 (10ms).
* 这可能用于在系统崩溃或断电前, 等待稳压器完成关断操作的时间.
*/
#define FV_DEF_EMERG_SHUTDWN_TMO 10
/*
* 结构体定义: fixed_voltage_data
* 这是该驱动的私有数据结构, 用于存储一个固定电压稳压器实例的所有状态信息.
*/
struct fixed_voltage_data {
/*
* .desc: 一个内嵌的 regulator_desc 结构体.
* 这是向内核稳压器核心框架注册时所用的描述符, 它包含了稳压器的名称、
* 支持的操作(如此片段中定义的函数指针)等所有静态信息.
*/
struct regulator_desc desc;
/*
* .dev: 一个指向 regulator_dev 的指针.
* 这是在稳压器成功注册后, 内核返回的设备句柄, 用于后续操作.
*/
struct regulator_dev *dev;
/*
* .enable_clock: 一个指向 clk 结构体的指针.
* 如果该稳压器的"使能"依赖于一个时钟, 此指针会指向该时钟.
*/
struct clk *enable_clock;
/*
* .enable_counter: 一个无符号整型计数器.
* 这是实现引用计数的关键. 当计数器大于0时, 表示稳压器(及其依赖的资源)
* 处于使能状态. 每次有消费者请求使能, 计数器加1; 每次有消费者禁用, 计数器减1.
*/
unsigned int enable_counter;
/*
* .performance_state: 一个整型.
* 如果该稳压器的"使能"依赖于一个通用功耗域(Generic Power Domain, genpd)
* 的性能状态, 此变量存储需要设置的目标性能状态值.
*/
int performance_state;
};
/*
* 结构体定义: fixed_dev_type
* 这是一个配置结构体, 用于告知驱动一个具体的稳压器实例依赖于哪种资源.
*/
struct fixed_dev_type {
/*
* .has_enable_clock: 布尔值. 如果为真, 表示该稳压器依赖于时钟.
*/
bool has_enable_clock;
/*
* .has_performance_state: 布尔值. 如果为真, 表示该稳-压器依赖于功耗域性能状态.
*/
bool has_performance_state;
};
操作函数
这些函数将被赋值给regulator_desc中的regulator_ops成员, 作为内核稳压器框架的回调。
基于时钟的控制
/*
* reg_clock_enable: 使能依赖于时钟的稳压器.
*/
static int reg_clock_enable(struct regulator_dev *rdev)
{
/* 获取驱动的私有数据. */
struct fixed_voltage_data *priv = rdev_get_drvdata(rdev);
int ret = 0;
/*
* 调用时钟框架API, "准备"并"使能"该稳压器所依赖的时钟.
* 这是将"使能稳压器"请求翻译为"使能时钟"的核心步骤.
*/
ret = clk_prepare_enable(priv->enable_clock);
if (ret)
return ret;
/* 如果时钟使能成功, 引用计数加1. */
priv->enable_counter++;
return ret;
}
/*
* reg_clock_disable: 禁用依赖于时钟的稳压器.
*/
static int reg_clock_disable(struct regulator_dev *rdev)
{
struct fixed_voltage_data *priv = rdev_get_drvdata(rdev);
/*
* 调用时钟框架API, "禁用"并"解除准备"时钟.
* 只有当所有消费者都释放了对此时钟的请求后, 时钟才会被真正关闭.
*/
clk_disable_unprepare(priv->enable_clock);
/* 引用计数减1. */
priv->enable_counter--;
return 0;
}
基于通用功耗域(genpd)的控制
/*
* reg_domain_enable: 使能依赖于功耗域性能状态的稳压器.
*/
static int reg_domain_enable(struct regulator_dev *rdev)
{
struct fixed_voltage_data *priv = rdev_get_drvdata(rdev);
struct device *dev = rdev->dev.parent;
int ret;
/*
* 调用通用功耗域框架API, 将该稳压器父设备所在的功耗域的性能状态
* 设置为预先指定的 performance_state.
* 这是将"使能稳压器"请求翻译为"提升功耗域性能"的核心步骤.
*/
ret = dev_pm_genpd_set_performance_state(dev, priv->performance_state);
if (ret)
return ret;
/* 如果成功, 引用计数加1. */
priv->enable_counter++;
return ret;
}
/*
* reg_domain_disable: 禁用依赖于功耗域性能状态的稳压器.
*/
static int reg_domain_disable(struct regulator_dev *rdev)
{
struct fixed_voltage_data *priv = rdev_get_drvdata(rdev);
struct device *dev = rdev->dev.parent;
int ret;
/*
* 调用通用功耗域框架API, 将功耗域的性能状态设置为0.
* 状态0通常意味着最低性能或空闲状态.
*/
ret = dev_pm_genpd_set_performance_state(dev, 0);
if (ret)
return ret;
/* 引用计数减1. */
priv->enable_counter--;
return 0;
}
状态检查
/*
* reg_is_enabled: 检查稳压器当前是否处于使能状态.
*/
static int reg_is_enabled(struct regulator_dev *rdev)
{
struct fixed_voltage_data *priv = rdev_get_drvdata(rdev);
/*
* 稳压器的"使能"状态直接由引用计数器决定.
* 只要 enable_counter 大于0, 就意味着至少有一个消费者正在使用它,
* 因此它被认为是"使能"的.
*/
return priv->enable_counter > 0;
}
reg_fixed_voltage_probe: 初始化并注册一个固定电压调节器
此函数是"固定电压调节器"平台驱动程序的核心。当内核的设备模型根据设备树中的compatible字符串, 将此驱动程序与一个具体的设备节点"绑定"(bind)时, 此函数就会被调用。它的核心原理是充当一个适配器和翻译器: 它从特定于硬件的配置源(主要是设备树)中读取信息, 将这些信息"翻译"成Linux内核稳压器(Regulator)框架所能理解的标准格式, 然后调用稳压器框架的API来完成最终的注册工作。
整个探测过程可以分解为以下几个关键步骤:
- 数据驱动的硬件识别: 在函数开始, 它通过
of_device_get_match_data(dev)获取与设备树条目关联的私有数据(drvtype)。这是该驱动灵活性的关键: 无需在代码中硬编码if ("regulator-fixed-clock")之类的判断, 而是通过匹配表中的数据来获知当前硬件的变体(例如, “这个调节器需要一个时钟来使能”)。 - 配置加载: 函数会尝试从设备树(
of_get_fixed_voltage_config)或旧式的平台数据(dev_get_platdata)中解析出详细的配置参数, 例如供电名称、输出电压值、启动延迟等。这使得同一个驱动二进制文件可以支持设备树中描述的无数种不同的固定电压源。 - 描述符填充: 它将上一步加载的配置信息, 逐一填充到
struct regulator_desc结构体中。这个结构体是向内核稳压器框架描述一个调节器所有静态特性的"身份证"。 - 操作函数集选择: 根据第一步识别出的硬件变体(
drvtype), 它为描述符的.ops字段选择一组正确的操作函数指针。如果是一个简单的固定调节器, 就使用fixed_voltage_ops; 如果是需要时钟的变体, 就使用fixed_voltage_clkenabled_ops, 并额外获取该时钟的句柄。这实现了行为的多态性。 - GPIO使能引脚处理: 函数会获取用于使能/禁能调节器的GPIO引脚。这里有一个重要的细节: 它不使用
devm_版本的GPIO获取函数, 因为这个GPIO引脚的生命周期将由此函数移交给稳压器核心框架来管理, 而不是由设备模型在驱动卸载时自动释放。GPIOD_FLAGS_BIT_NONEXCLUSIVE标志的使用表明它能优雅地处理多个调节器共享同一个GPIO使能引脚的硬件场景。 - 最终注册: 所有信息都收集并打包到
struct regulator_config结构体后, 函数调用devm_regulator_register。这是一个devm_管理的封装函数, 它会调用稳压器核心框架的注册API, 将这个新的调节器设备注册到系统中。从此, 系统中的其他设备驱动(消费者)就可以通过标准的regulator_get()API来请求和使用这个电源了。
在STM32H750这样的嵌入式系统中, 这个驱动非常有用, 它可以用来为设备树中定义的、由LDO(低压差线性稳压器)或其他固定电源供电的各种外设(如传感器、外部Flash等)创建标准的电源模型。dev_err_probe的使用确保了如果任何依赖(如GPIO控制器)尚未就绪, 探测过程会安全地推迟(-EPROBE_DEFER), 从而自动解决驱动加载顺序问题。
static const struct regulator_ops fixed_voltage_ops = {
};
static const struct regulator_ops fixed_voltage_clkenabled_ops = {
.enable = reg_clock_enable,
.disable = reg_clock_disable,
.is_enabled = reg_is_enabled,
};
static const struct regulator_ops fixed_voltage_domain_ops = {
.enable = reg_domain_enable,
.disable = reg_domain_disable,
.is_enabled = reg_is_enabled,
};
static int reg_fixed_voltage_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct fixed_voltage_config *config;
struct fixed_voltage_data *drvdata;
const struct fixed_dev_type *drvtype = of_device_get_match_data(dev);
struct regulator_config cfg = { };
enum gpiod_flags gflags;
int ret;
/*
* 为驱动的私有数据结构(drvdata)分配内存.
* devm_kzalloc 确保这块内存在驱动卸载时会被自动释放, 简化了错误处理.
*/
drvdata = devm_kzalloc(&pdev->dev, sizeof(struct fixed_voltage_data),
GFP_KERNEL);
if (!drvdata)
return -ENOMEM;
if (pdev->dev.of_node) {
/* 如果设备是通过设备树实例化的, 调用 of_get_fixed_voltage_config 解析设备树属性. */
config = of_get_fixed_voltage_config(&pdev->dev,
&drvdata->desc);
if (IS_ERR(config))
return PTR_ERR(config);
} else {
/* 否则, 尝试获取旧式的平台数据. */
config = dev_get_platdata(&pdev->dev);
}
if (!config)
return -ENOMEM;
/*
* --- 开始填充 regulator_desc 描述符 ---
* 这是向内核 regulator 核心描述此调节器特性的主要结构.
*/
drvdata->desc.name = devm_kstrdup(&pdev->dev,
config->supply_name,
GFP_KERNEL);
if (drvdata->desc.name == NULL) {
dev_err(&pdev->dev, "Failed to allocate supply name\n");
return -ENOMEM;
}
drvdata->desc.type = REGULATOR_VOLTAGE; // 声明这是一个电压调节器.
drvdata->desc.owner = THIS_MODULE; // 声明所有权.
/*
* --- 根据硬件变体选择操作函数集并获取额外资源 ---
*/
if (drvtype && drvtype->has_enable_clock) {
/* 如果匹配的数据表明这是一个需要时钟的调节器 */
drvdata->desc.ops = &fixed_voltage_clkenabled_ops;
drvdata->enable_clock = devm_clk_get(dev, NULL); // 获取时钟句柄
if (IS_ERR(drvdata->enable_clock)) {
dev_err(dev, "Can't get enable-clock from devicetree\n");
return PTR_ERR(drvdata->enable_clock);
}
} else if (drvtype && drvtype->has_performance_state) {
/* 如果匹配的数据表明这是一个与性能域相关的调节器 */
drvdata->desc.ops = &fixed_voltage_domain_ops;
/* 获取所需的性能状态 */
drvdata->performance_state = devm_regulator_register(dev->of_node, 0);
if (drvdata->performance_state < 0) {
dev_err(dev, "Can't get performance state from devicetree\n");
return drvdata->performance_state;
}
} else {
/* 对于最简单的固定电压源 */
drvdata->desc.ops = &fixed_voltage_ops;
}
drvdata->desc.enable_time = config->startup_delay; // 启动延迟(us)
drvdata->desc.off_on_delay = config->off_on_delay; // 关闭再开启的延迟
if (config->input_supply) {
/* 如果定义了上游电源, 复制其名称 */
drvdata->desc.supply_name = devm_kstrdup(&pdev->dev,
config->input_supply,
GFP_KERNEL);
if (!drvdata->desc.supply_name)
return -ENOMEM;
}
if (config->microvolts)
drvdata->desc.n_voltages = 1; // 这是一个固定电压源, 所以只有1个电压等级.
drvdata->desc.fixed_uV = config->microvolts; // 设置固定的输出电压值.
/*
* --- GPIO 使能引脚处理 ---
*/
if (config->enabled_at_boot)
gflags = GPIOD_OUT_HIGH; // 如果设备在启动时就应使能, 设置GPIO初始状态为高.
else
gflags = GPIOD_OUT_LOW; // 否则为低.
/*
* 设置 GPIOD_FLAGS_BIT_NONEXCLUSIVE 标志, 允许多个消费者共享同一个GPIO描述符.
* 这对于两个调节器共用一个使能引脚的场景至关重要.
*/
gflags |= GPIOD_FLAGS_BIT_NONEXCLUSIVE;
/*
* 获取使能GPIO的描述符. 使用 gpiod_get_optional 表示这个GPIO是可选的.
* 注意: 这里没有使用 devm_* 版本, 因为这个gpiod的生命周期将由 regulator 核心管理.
*/
cfg.ena_gpiod = gpiod_get_optional(&pdev->dev, NULL, gflags);
if (IS_ERR(cfg.ena_gpiod))
/* dev_err_probe 会打印错误并智能处理 -EPROBE_DEFER, 简化了推迟探测的逻辑. */
return dev_err_probe(&pdev->dev, PTR_ERR(cfg.ena_gpiod),
"can't get GPIO\n");
/*
* --- 填充 regulator_config 结构体, 准备最终注册 ---
* 这个结构体包含了传递给 regulator 核心的所有运行时和初始化信息.
*/
cfg.dev = &pdev->dev; // 关联的设备.
cfg.init_data = config->init_data; // 初始约束数据.
cfg.driver_data = drvdata; // 驱动的私有数据.
cfg.of_node = pdev->dev.of_node; // 关联的设备树节点.
/*
* --- 调用 regulator 核心框架进行注册 ---
*/
drvdata->dev = devm_regulator_register(&pdev->dev, &drvdata->desc,
&cfg);
if (IS_ERR(drvdata->dev)) {
ret = dev_err_probe(&pdev->dev, PTR_ERR(drvdata->dev),
"Failed to register regulator: %ld\n",
PTR_ERR(drvdata->dev));
return ret;
}
/*
* 将驱动的私有数据保存到平台设备中, 方便后续(如remove函数)访问.
*/
platform_set_drvdata(pdev, drvdata);
dev_dbg(&pdev->dev, "%s supplying %duV\n", drvdata->desc.name,
drvdata->desc.fixed_uV);
/*
* 获取并设置可能存在的"错误"中断.
*/
ret = reg_fixed_get_irqs(dev, drvdata);
if (ret)
return ret;
return 0; // 探测成功.
}
固定电压调节器平台驱动程序
此代码片段的整体作用是定义并注册一个灵活的、由设备树驱动的平台驱动程序, 用于管理"固定电压调节器"(Fixed Voltage Regulator)。固定电压调节器是一种简单的电源组件, 其输出电压是固定的, 无法通过软件调节(但可能可以被使能/禁能)。该驱动程序的设计体现了现代Linux内核驱动开发中的一个核心原则: 用数据而非代码来描述硬件差异。
1. 硬件变体描述与设备树匹配
这部分代码通过设备树匹配表, 将不同的硬件变体与特定的配置数据关联起来。
#if defined(CONFIG_OF) // 仅在内核配置了设备树(Open Firmware)支持时, 才编译以下代码.
/*
* 定义三个静态的、常量类型的 fixed_dev_type 结构体实例.
* 这些结构体作为 "配置数据", 用于区分不同类型的固定调节器.
*/
static const struct fixed_dev_type fixed_voltage_data = {
.has_enable_clock = false, // 表示这是一个最简单的调节器, 没有独立的使能时钟.
};
static const struct fixed_dev_type fixed_clkenable_data = {
.has_enable_clock = true, // 表示这是一个需要时钟才能使其能/禁能控制生效的调节器.
};
static const struct fixed_dev_type fixed_domain_data = {
.has_performance_state = true, // 表示这是一个与性能域相关的调节器.
};
/*
* 定义设备树匹配表. 这是驱动与设备树节点之间的"联姻"桥梁.
*/
static const struct of_device_id fixed_of_match[] = {
{
/*
* 当设备树节点的 "compatible" 属性为 "regulator-fixed" 时,
* 内核将匹配此条目.
*/
.compatible = "regulator-fixed",
/*
* .data 字段指向与之关联的配置数据. 当 probe 函数被调用时,
* 它可以从中得知这是一个最简单的固定电压调节器.
*/
.data = &fixed_voltage_data,
},
{
.compatible = "regulator-fixed-clock",
.data = &fixed_clkenable_data, // 关联到 "有时钟" 的配置数据.
},
{
.compatible = "regulator-fixed-domain",
.data = &fixed_domain_data, // 关联到 "性能域" 的配置数据.
},
{
}, // 哨兵条目, 标记数组结束.
};
/*
* 将设备树匹配表导出到模块元数据中, 以便用户空间的 udev/mdev
* 等系统能够根据设备树信息自动加载本模块.
*/
MODULE_DEVICE_TABLE(of, fixed_of_match);
#endif
原理: 这种设计的精妙之处在于, 它避免了在驱动的探测函数(probe)中使用冗长的if/else或switch语句来判断设备类型。驱动的probe函数被设计得更通用, 它只需要通过device_get_match_data()函数获取到匹配条目中的.data指针, 就能知道当前硬件的具体特性(例如, 是否需要处理时钟), 从而执行相应的初始化逻辑。这使得驱动程序更容易维护和扩展。
2. 平台驱动程序核心结构体
这是驱动程序的"注册表", 包含了它的所有核心信息。
static struct platform_driver regulator_fixed_voltage_driver = {
/* .probe: 指向驱动的探测函数 reg_fixed_voltage_probe.
* 当内核将此驱动与一个匹配的设备绑定时, 此函数会被调用.
*/
.probe = reg_fixed_voltage_probe,
.driver = {
/* .name: 驱动的逻辑名称, 会出现在 /sys/bus/platform/drivers/ 目录下. */
.name = "reg-fixed-voltage",
/* .probe_type: 这是一个对内核调度器的提示.
* PROBE_PREFER_ASYNCHRONOUS 表示此驱动的 probe 函数可以与其他驱动的
* probe 函数并行执行, 这有助于加快系统的启动速度.
*/
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
/* .of_match_table: 将此驱动与上面定义的设备树匹配表关联起来.
* of_match_ptr 是一个宏, 用于安全地处理 CONFIG_OF 未定义的情况.
*/
.of_match_table = of_match_ptr(fixed_of_match),
},
};
3. 驱动初始化与注销
这部分代码负责将驱动程序注册到内核, 以及在模块卸载时将其注销。
/*
* 驱动的初始化函数. __init 宏表示此函数仅在初始化期间使用,
* 其占用的内存可以在初始化完成后被释放.
*/
static int __init regulator_fixed_voltage_init(void)
{
/* 将上面定义的 platform_driver 结构体注册到内核的平台总线核心中. */
return platform_driver_register(®ulator_fixed_voltage_driver);
}
/*
* subsys_initcall() 宏指定了此初始化函数的调用时机.
* "subsys" 级别相对较早, 因为电源调节器(Regulator)是底层基础设备,
* 许多其他设备(如SPI, I2C控制器)都依赖于它们, 所以必须尽早初始化.
*/
subsys_initcall(regulator_fixed_voltage_init);
/*
* 驱动的退出函数. __exit 宏表示此函数仅在模块卸载时使用.
* 对于静态编译进内核的驱动, 这部分代码会被丢弃.
*/
static void __exit regulator_fixed_voltage_exit(void)
{
/* 从内核中注销此平台驱动. */
platform_driver_unregister(®ulator_fixed_voltage_driver);
}
/* module_exit() 宏注册了模块的退出函数. */
module_exit(regulator_fixed_voltage_exit);
4. 模块元数据
这部分提供了关于模块的描述性信息, 可通过modinfo命令查看。
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_DESCRIPTION("Fixed voltage regulator"); // 模块功能描述.
MODULE_LICENSE("GPL"); // 模块的许可证.
/*
* MODULE_ALIAS: 为模块创建一个别名.
* "platform:reg-fixed-voltage" 意味着, 即使用户空间请求加载名为
* "reg-fixed-voltage" 的平台驱动, 内核也能知道应该加载这个模块.
* 这增强了模块自动加载的灵活性.
*/
MODULE_ALIAS("platform:reg-fixed-voltage");
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)