1、简介
regulator 框架是 Linux 内核中用于管理电压和电流调节器(如 LDO、DCDC 转换器等)的一个子系统。它提供了一个抽象层,使得驱动程序和内核的其他部分可以以一致的方式与调节器进行交互,而无需了解底层硬件的细节。
主要功能包括:
- 控制电压的启停
- 调整输出电压
- 查询当前电压状态
- 保护系统不受过流或过压的影响
2、设备树中的 regulator
设备树中常见的有两种 regulator,一种是可变的 regulator,一种是固定的 regulator。
2.1 固定 regulator
固定 regulator 在 Linux 设备树中的 compatible 为 “regulator-fixed”。固定,顾名思义,该 regulator 不支持电压调节,只支持 enable/disable。
regulator-fixed 通常是由一个 GPIO 控制了一路 regulator,如下图,GPIO3_C3 控制了 TP6327GS6 电源芯片,输出 MINIPCIE_3V3 电压给到 MINIPCIE 部件。
对应的设备树节点如下:
vcc3v3_m2_pcie: vcc3v3-m2-pcie-regulator {
compatible = "regulator-fixed";
regulator-name = "m2_pcie_3v3";
enable-active-high;
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
gpios = <&gpio0 RK_PD4 GPIO_ACTIVE_HIGH>;
pinctrl-0 = <&vcc3v3_m2_pcie_en>;
pinctrl-names = "default";
startup-delay-us = <200000>;
vin-supply = <&vcc5v0_sys>;
};
解析如下:
- regulator-min-microvolt:固定电压(对于 regulator-fixed 来说,min 和 max 一定相同)
- gpios :控制 regulator 使能的 gpio 引脚(当调用 enable regulator 接口时就是使能该 gpio 引脚)
- startup-delay-us:启动时间(微秒)
- enable-active-high:GPIO 极性为高电平有效。如果缺少此属性,则默认为低电平有效。
- regulator-boot-on:指示该 regulator 在 bootloader/firmware 阶段已经被 enable 了(注意,只能确定 bootloader 阶段,无法确定系统启动后的一个状态)
- regulator-always-on:该 regulator 永远不应该被禁用,系统一上电就应该使能该 regulator
- gpio-open-drain:GPIO 为开漏类型。如果缺少此属性,则默认假设为 false
- vin-supply:该 regulator 输入源
关于 regulator-fixed 详细节点属性,见
Linux/Documentation/devicetree/bindings/regulator/fixed-regulator.yaml
regulator-fixed
对应的驱动文件:drivers/regulator/fixed.c
static const struct regulator_ops fixed_voltage_ops = {
};
可以看到,fixed_voltage_ops
结构为空,不支持调节电压。
2.2 可变 regulator
可变 regulator,常见的,又可分为 regulator-gpio 和 普通 regualtor。
2.2.1 regulator-gpio
通常是由 GPIO 控制的 regulator。可以通过一个或多个 GPIO 控制输出多个不同的电压或电流,因此除了开(enable) \ 关(disabled)两种操作外,往往还支持电压或电流的控制。
具体是电压还是电流,是由设备树节点中的 regulator-type 属性决定的。如果没有该属性,默认是电压调节
对应的 Linux 设备树节点如下:
gpio-regulator {
compatible = "regulator-gpio";
regulator-name = "mmci-gpio-supply";
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <2600000>;
regulator-boot-on;
enable-gpios = <&gpio0 23 0x4>;
gpios = <&gpio0 24 0x4
&gpio0 25 0x4>;
states = <1800000 0x3>,
<2200000 0x2>,
<2600000 0x1>,
<2900000 0x0>;
startup-delay-us = <100000>;
enable-active-high;
};
解析如下:
- regulator-min-microvolt:描述该电源输出电压的允许范围最小值 1.8v
- regulator-max-microvolt:描述该电源输出电压的允许范围最大值 2.6v
- regulator-boot-on:指示该 regulator 在 bootloader/firmware 阶段已经被 enable 了(注意,只能确定 bootloader 阶段,无法确定系统启动后的一个状态)
- enable-gpios:用来控制该 regulator 开关的 gpio 引脚
- gpios:一个或多个 GPIO 引脚数组,用于选择 “states” 中列出的调节器电压/电流
- states:此 regulator 提供的可用电压/电流,并匹配相应的 GPIO 配置以实现这些电压/电流
- gpios-states:在不支持在输出模式下读取 GPIO 值的操作系统上(尤其是 Linux),此数组提供从 GPIO 控制器请求 GPIO 引脚时设置的 GPIO 引脚状态(可以理解为设置 gpios 的初始状态)。gpios-states 属性中的元素和 gpios 属性中的元素一一对应
- startup-delay-us:启动时间(微秒)
- enable-active-high:GPIO 极性为高电平有效。如果缺少此属性,则默认为低电平有效。
关于 regulator-gpio 详细节点属性,见
Linux/Documentation/devicetree/bindings/regulator/gpio-regulator.yaml
关于 states
和 gpios
属性这里详细解释一下,这两个属性是成对出现。regulator-gpio
本质上,是通过一组 GPIO(也就是 gpios
属性中的),来控制不同的电压输出。可提供的电压输出选项(也就是 states
属性中的)。
gpio0_24 电平 | gpio0_25 电平 | 输出电压 |
---|---|---|
0 | 0 | 2.9v |
0 | 1 | 2.2v |
1 | 0 | 2.6v |
1 | 1 | 1.8v |
2.2.2 普通 regulator
vdd_cpu: regulator@1c {
compatible = "tcs,tcs4525";
reg = <0x1c>;
fcs,suspend-voltage-selector = <1>;
regulator-name = "vdd_cpu";
regulator-always-on;
regulator-boot-on;
regulator-min-microvolt = <800000>;
regulator-max-microvolt = <1150000>;
regulator-ramp-delay = <2300>;
vin-supply = <&vcc5v0_sys>;
regulator-state-mem {
regulator-off-in-suspend;
};
};
解析如下:
- regulator-min-microvolt:描述该电源输出电压的允许范围最小值 0.8v
- regulator-max-microvolt:描述该电源输出电压的允许范围最大值 1.15v
- regulator-ramp-delay:改变电压到电源稳定后需要的时间
- regulator-boot-on:指示该 regulator 在 bootloader/firmware 阶段已经被 enable 了(注意,只能确定 bootloader 阶段,无法确定系统启动后的一个状态)
- regulator-always-on:该 regulator 永远不应该被禁用,系统一上电就应该使能该 regulator
- vin-supply:该 regulator 输入源
3、Linux 源码中的 regulator 驱动
3.1 框架
3.2 machine
machin 使用 struct regulator_init_data
,静态的描述 regulator 在板级的硬件连接情况。这些限制通过驱动或 dts 配置,涉及到系统供电安全,因此必须小心,这些配置主要包括:
- 用于描述 regulator 在板级的级联关系:前级 regulator(即该 regulator 的输出是另一个 regulator 的输入,简称 supply regulator)和后级 regulator(即该 regulator 的输入是其它 regulator 的输出,简称 consumer regulator)
- 利用
struct regulation_constraints
描述 regulator 的物理限制,比如:- 输出电压的最大值和最小值(voltage regulator)
- 输出电流的最大值和最小值(current regulator)
- 允许的操作(修改电压值、修改电流限制、enable、disable等)
- 输入电压是多少(当输入是另一个regulator时)
- 是否不允许关闭(always_on)
- 是否启动时就要打开(always_on)
涉及到的 Linux 源码文件:Linux/include/linux/regulator/machine.h
/**
* struct regulator_init_data - regulator platform initialisation data.
*
* Initialisation constraints, our supply and consumers supplies.
*
* @supply_regulator: Parent regulator. Specified using the regulator name
* as it appears in the name field in sysfs, which can
* be explicitly set using the constraints field 'name'.
*
* @constraints: Constraints. These must be specified for the regulator to
* be usable.
* @num_consumer_supplies: Number of consumer device supplies.
* @consumer_supplies: Consumer device supply configuration.
*
* @regulator_init: Callback invoked when the regulator has been registered.
* @driver_data: Data passed to regulator_init.
*/
struct regulator_init_data {
const char *supply_regulator; /* or NULL for system supply */
struct regulation_constraints constraints;
int num_consumer_supplies;
struct regulator_consumer_supply *consumer_supplies;
/* optional regulator machine specific init */
int (*regulator_init)(void *driver_data);
void *driver_data; /* core does not touch this */
};
/**
* struct regulation_constraints - regulator operating constraints.
*
* This struct describes regulator and board/machine specific constraints.
*
* @name: Descriptive name for the constraints, used for display purposes.
*
* @min_uV: Smallest voltage consumers may set.
* @max_uV: Largest voltage consumers may set.
* @uV_offset: Offset applied to voltages from consumer to compensate for
* voltage drops.
*
* @min_uA: Smallest current consumers may set.
* @max_uA: Largest current consumers may set.
* @ilim_uA: Maximum input current.
* @system_load: Load that isn't captured by any consumer requests.
*
* @over_curr_limits: Limits for acting on over current.
* @over_voltage_limits: Limits for acting on over voltage.
* @under_voltage_limits: Limits for acting on under voltage.
* @temp_limits: Limits for acting on over temperature.
*
* @max_spread: Max possible spread between coupled regulators
* @max_uV_step: Max possible step change in voltage
* @valid_modes_mask: Mask of modes which may be configured by consumers.
* @valid_ops_mask: Operations which may be performed by consumers.
*
* @always_on: Set if the regulator should never be disabled.
* @boot_on: Set if the regulator is enabled when the system is initially
* started. If the regulator is not enabled by the hardware or
* bootloader then it will be enabled when the constraints are
* applied.
* .....
* .....
*/
struct regulation_constraints {
const char *name;
/* voltage output range (inclusive) - for voltage control */
int min_uV;
int max_uV;
int uV_offset;
/* current output range (inclusive) - for current control */
int min_uA;
int max_uA;
int ilim_uA;
int system_load;
/* used for coupled regulators */
u32 *max_spread;
/* used for changing voltage in steps */
int max_uV_step;
/* valid regulator operating modes for this machine */
unsigned int valid_modes_mask;
/* valid operations for regulator on this machine */
unsigned int valid_ops_mask;
/* regulator input voltage - only if supply is another regulator */
int input_uV;
......
}
3.3 regulator driver
regulator driver 指的是具体的 regulator 设备的驱动,例如:
- regulator-gpio 这一类的 regulator 驱动,对应的驱动文件 Linux/drivers/regulator/gpio-regulator.c
- regulator-fixed 这一类的 regulator 驱动,对应的驱动文件 Linux/drivers/regulator/fixed.c
- tcs,tcs4525 这种具体的 regulator 驱动,对应的驱动文件 Linux/drivers/regulator/fan53555.c
regulator drive 要做的事,是操作底层具体的 regulator 硬件寄存器,控制 regulator 输出的电流、电压。同时将控制电压、电流的接口封装,注册、提交给 regulator framework core 层。
在内核 Linux/kernel/drivers/regulator/dummy.c 文件中构造了一个虚拟的 regulator,给各种 regulator 驱动开发提供的参考。
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* dummy.c
*
* Copyright 2010 Wolfson Microelectronics PLC.
*
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
*
* This is useful for systems with mixed controllable and
* non-controllable regulators, as well as for allowing testing on
* systems with no controllable regulators.
*/
#include <linux/err.h>
#include <linux/export.h>
#include <linux/platform_device.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include "dummy.h"
struct regulator_dev *dummy_regulator_rdev;
/*
* 提供 machine 层的 regulator_init_data 资源
* 通常在 regulator 驱动中,regulator_init_data 资源是通过设备树解析出来的。
* 调用 of_get_regulator_init_data 设备树接口,而不是像下面这样直接在驱动中固定
* 解析出的 machine 信息保存在 struct regulator_config 结构中
* 通过调用 devm_regulator_register 系统接口,将接口注册进 regulator framework core 中
*/
static const struct regulator_init_data dummy_initdata = {
.constraints = {
.always_on = 1,
},
};
/*
* 封装具体的控制 regulator 电压、电流的函数接口(通过操作一些 regulaor 硬件寄存器)
* 存放在 struct regulator_desc 结构中
* 通过调用 devm_regulator_register 系统接口,将接口注册进 regulator framework core 中
*/
static const struct regulator_ops dummy_ops;
static const struct regulator_desc dummy_desc = {
.name = "regulator-dummy",
.id = -1,
.type = REGULATOR_VOLTAGE,
.owner = THIS_MODULE,
.ops = &dummy_ops,
};
static int dummy_regulator_probe(struct platform_device *pdev)
{
struct regulator_config config = { };
int ret;
config.dev = &pdev->dev;
config.init_data = &dummy_initdata;
/*
* 向 regulator framework core 注册接口
* 函数入参 struct regulator_desc、struct regulator_config
*/
dummy_regulator_rdev = devm_regulator_register(&pdev->dev, &dummy_desc,
&config);
if (IS_ERR(dummy_regulator_rdev)) {
ret = PTR_ERR(dummy_regulator_rdev);
pr_err("Failed to register regulator: %d\n", ret);
return ret;
}
return 0;
}
static struct platform_driver dummy_regulator_driver = {
.probe = dummy_regulator_probe,
.driver = {
.name = "reg-dummy",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
static struct platform_device *dummy_pdev;
void __init regulator_dummy_init(void)
{
int ret;
dummy_pdev = platform_device_alloc("reg-dummy", -1);
if (!dummy_pdev) {
pr_err("Failed to allocate dummy regulator device\n");
return;
}
ret = platform_device_add(dummy_pdev);
if (ret != 0) {
pr_err("Failed to register dummy regulator device: %d\n", ret);
platform_device_put(dummy_pdev);
return;
}
ret = platform_driver_register(&dummy_regulator_driver);
if (ret != 0) {
pr_err("Failed to register dummy regulator driver: %d\n", ret);
platform_device_unregister(dummy_pdev);
}
}
3.4 regulator framework core
regulator framework core 屏蔽了底层各种 regulaor 硬件的差异,对上面的 regulator consumer 提供统一的控制类接口。并提供接口给内核中其他的 consumer(使用当前 regulator 设备的驱动),并以 sysfs 的形式,向用户空间提供接口。
regulator framework core 主要涉及的文件是:Linux/drivers/regulator/core.c
该文件涉及了 regulator framework core 层接口的具体实现。例如,实现 regulator driver 层注册 regulator 的接口: devm_regulator_register
等。
3.5 regulator consumer
regulator consumer 抽象出 regulator 设备(struct regulator),并提供 regulator 操作相关的接口。包括:
- regulator_get/regulator_put
- regulator_enable/regulator_disable
- regulator_set_voltage/regulator_get_voltage 等
以调节 CPU 电压为例:
vdd_cpu: regulator@1c {
compatible = "tcs,tcs4525";
reg = <0x1c>;
fcs,suspend-voltage-selector = <1>;
regulator-name = "vdd_cpu";
regulator-always-on;
regulator-boot-on;
regulator-min-microvolt = <800000>;
regulator-max-microvolt = <1150000>;
regulator-ramp-delay = <2300>;
vin-supply = <&vcc5v0_sys>;
regulator-state-mem {
regulator-off-in-suspend;
};
};
&cpu0 {
cpu-supply = <&vdd_cpu>;
};
&cpu1 {
cpu-supply = <&vdd_cpu>;
};
&cpu2 {
cpu-supply = <&vdd_cpu>;
};
&cpu3 {
cpu-supply = <&vdd_cpu>;
};
consumer 代码调用流程:
dt_cpufreq_early_init
--> dev_pm_opp_set_regulators
--> regulator_get_optional
通过解析 CPU 设备树节点 cpu-supply
属性,调用 regulator_get_optional
找到 CPU 对应的 regulator。
struct regulator * regulator_get_optional(struct device * dev, const char * id);
Parameters
- struct device * dev
- device for regulator “consumer”
- const char * id
- Supply name or regulator ID
获取到对应的 regulator 结构后,会调用 regulator_set_voltage
设置具体的 CPU 电压
static int _set_opp_voltage(struct device *dev, struct regulator *reg,
struct dev_pm_opp_supply *supply)
{
int ret;
/* Regulator not available for device */
if (IS_ERR(reg)) {
dev_dbg(dev, "%s: regulator not available: %ld\n", __func__,
PTR_ERR(reg));
return 0;
}
dev_dbg(dev, "%s: voltages (mV): %lu %lu %lu\n", __func__,
supply->u_volt_min, supply->u_volt, supply->u_volt_max);
ret = regulator_set_voltage_triplet(reg, supply->u_volt_min,
supply->u_volt, supply->u_volt_max);
if (ret)
dev_err(dev, "%s: failed to set voltage (%lu %lu %lu mV): %d\n",
__func__, supply->u_volt_min, supply->u_volt,
supply->u_volt_max, ret);
return ret;
}
4、关于电压操作的两种方式
Linux kernel 抽象了两种电压操作的方法:
- 直接操作电压
- 以 selector 的形式
4.1 直接操作电压
对应 struct regulator_ops
中的如下回调函数:
/* enumerate supported voltages */
int (*list_voltage) (struct regulator_dev *, unsigned selector);
/* get/set regulator voltage */
int (*set_voltage) (struct regulator_dev *, int min_uV, int max_uV,
unsigned *selector);
int (*get_voltage) (struct regulator_dev *);
- set_voltage, 用于将电压设置为 min_uV 和 max_uV 范围内、和 min_uV 最接近的电压。该接口可以返回一个 selector 参数,用于告知调用者,实际的电压值对应的索引
- get_voltage,用于返回当前的真实电压值
- list_voltage,以 selector 为参数,获取 selector 对应的真实电压值
这里的 selector 可以理解为,电压值对应的索引。这个值的类型、计算方法,每个 regulator 驱动都可能不同(也可以里理解为,只有 regulator 驱动本人才知道 selector 是啥)。以 regulator-gpio
驱动为例:
static int gpio_regulator_set_voltage(struct regulator_dev *dev,
int min_uV, int max_uV,
unsigned *selector)
{
struct gpio_regulator_data *data = rdev_get_drvdata(dev);
int ptr, target = 0, state, best_val = INT_MAX;
for (ptr = 0; ptr < data->nr_states; ptr++)
if (data->states[ptr].value < best_val &&
data->states[ptr].value >= min_uV &&
data->states[ptr].value <= max_uV) {
target = data->states[ptr].gpios;
best_val = data->states[ptr].value;
/* 从这里也可以看到,返回的 selector 就是将要设置的电压对应的设备树节点 states 中的电压编号 */
if (selector)
*selector = ptr;
}
if (best_val == INT_MAX)
return -EINVAL;
for (ptr = 0; ptr < data->nr_gpios; ptr++) {
state = (target & (1 << ptr)) >> ptr;
gpiod_set_value_cansleep(data->gpiods[ptr], state);
}
data->state = target;
return 0;
}
4.2 以 selector 的形式
regulator driver 以 selector 的形式,反映电压值。selector 是一个从 0 开始的整数,driver 提供如下的接口:
int (*map_voltage)(struct regulator_dev *, int min_uV, int max_uV);
int (*set_voltage_sel) (struct regulator_dev *, unsigned selector);
int (*get_voltage_sel) (struct regulator_dev *);
- map_voltage,是和 list_voltage 相对的接口,用于将电压范围 map 成一个 selector(这个电压范围对应的一个索引)
- set_voltage_sel,以 selector 的形式,设置电压,函数入参为 selector
- get_voltage_sel,以 selector 的形式,读取电压,返回值为 selector
以瑞芯微 rk809 regulator 驱动为例:
/**
* regulator_map_voltage_linear_range - map_voltage() for multiple linear ranges
*
* @rdev: Regulator to operate on
* @min_uV: Lower bound for voltage
* @max_uV: Upper bound for voltage
*
* Drivers providing linear_ranges in their descriptor can use this as
* their map_voltage() callback.
*/
int regulator_map_voltage_linear_range(struct regulator_dev *rdev,
int min_uV, int max_uV)
{
const struct linear_range *range;
int ret = -EINVAL;
unsigned int sel;
bool found;
int voltage, i;
if (!rdev->desc->n_linear_ranges) {
BUG_ON(!rdev->desc->n_linear_ranges);
return -EINVAL;
}
for (i = 0; i < rdev->desc->n_linear_ranges; i++) {
range = &rdev->desc->linear_ranges[i];
ret = linear_range_get_selector_high(range, min_uV, &sel,
&found);
if (ret)
continue;
ret = sel;
/*
* Map back into a voltage to verify we're still in bounds.
* If we are not, then continue checking rest of the ranges.
*/
voltage = rdev->desc->ops->list_voltage(rdev, sel);
if (voltage >= min_uV && voltage <= max_uV)
break;
}
if (i == rdev->desc->n_linear_ranges)
return -EINVAL;
return ret;
}
regulator 的输出电压通过内部的寄存器值(selector 或 index)控制的,寄存器值与输出电压之间有一个固定的映射关系。这个映射关系可以用以下公式表示:
V o u t = V m i n + s e l e c t o r × u V s t e p V_{out}=V_{min}+selector×uV_{step} Vout=Vmin+selector×uVstep
其中:
- V_ o u t {out} out 是当前输出电压
- V_ m i n {min} min 是调节器支持的最低输出电压
- uV_ s t e p {step} step 是调节电的压步长
- selector 是一个整数(从 0 开始),对应电压级别。
硬件只支持离散的 selector 值,因此电压只能按照 uV_step 的倍数变化。
例如:
/**
* struct linear_range - table of selector - value pairs
*
* Define a lookup-table for range of values. Intended to help when looking
* for a register value matching certaing physical measure (like voltage).
* Usable when increment of one in register always results a constant increment
* of the physical measure (like voltage).
*
* @min: Lowest value in range
* @min_sel: Lowest selector for range
* @max_sel: Highest selector for range
* @step: Value step size
*/
struct linear_range {
unsigned int min;
unsigned int min_sel;
unsigned int max_sel;
unsigned int step;
};
static const struct regulator_linear_range my_ranges[] = {
{
.min_uV = 800000,
.min_sel = 0,
.max_sel = 15,
.uV_step = 25000,
},
};
在这种定义下:
- selector = 0 → 800,000 μV
- selector = 1 → 825,000 μV
- …
- selector = 15 → 1,175,000 μV
4.3 总结
以上两种方式,regulator driver 可以根据实际情况,选择一种实现方式。