9. linux电源管理¶
在linux内核中电源管理(Power Management)是个庞大的系统,管理系统的电源,一般是在不用时关闭电源或将系统切换到低功耗状态。实际可以分为电源状态管理和省电管理, 涉及管理电源供电、电源状态管理、运行时的电源管理、电源省电管理、低功耗等等。
电源的状态管理,就是我们平常使用开机、关机和重启等。具体来讲有:睡眠(Sleep)也叫做Suspend to RAM(STR),把系统的状态信息保存到内存, 内存供电,其他断电,在内核中睡眠也称作Suspend;休眠(Hibernate)也叫做Suspend to Disk(STD),把系统的状态信息保存到磁盘,系统都断电
重启(Restart)和关机(Shutdown),比较简单,就是不再使用系统,或者是重新启动系统,在系统中使用命令reboot、halt、poweroff等就行重启或者关机, 这些命令都会发起reboot系统调用,然后实现操作,有兴趣可以自行去了解下Reboot系统调用。
除了上面的一些,电源还有省电管理,有CPU动态调频(CPUFreq)、设备动态调频(DevFreq)、CPUIdle、CPU Hotplug、Runtime PM、PM QoS等。 CPU动态调频和设备动态调频,通过降低频率进行省电,同时兼顾性能和负载进行调节。CPUIdle是当某个CPU上没有进程可调度的时候可以暂时局部关掉这个CPU的电源,有进程时再启用。
CPU Hotplug指的是我们可以把某个CPU热移除,然后系统就不会再往这个CPU上派任务了, 这个CPU就可以放心地完全关闭电源了,当把这个CPU再热插入之后,就对这个CPU恢复供电,这个CPU就可以正常执行任务了。 Runtime PM指的是设备的动态电源管理,系统中存在很多设备,但是并不是每种设备都在一直使用,可以在不用的时候关闭设备的电源,减少功耗。
本章主要简单介绍下系统的Suspend和Regulator Framework。
9.1. Suspend¶
从用户角度看,系统可以休眠sleep或者休眠(Hibernate),实际都是保存系统上下文,然后挂起(suspend)系统,之后那肯定就有唤醒(wakeup)。 suspend的详细过程较复杂涉及许多模块,我们就简单介绍下suspend相关的用户接口。
Linux Kernel支持的休眠方式,可以使用想下面命令查看:
1 | cat /sys/power/state
|
/sys/power/state文件用于将系统置于指定的电源状态(freeze,standby, mem, disk),有些系统是不会全部有的,一般会有其中一种或者几种。 用户空间往该文件写入特定的电源状态字符串,将会把系统置为该模式,这几种状态的解释如下:
freeze
冻结I/O设备,将它们置于低功耗状态,使处理器进入空闲状态,处于S2Idle状态下时,设备中断就可以将其唤醒。
Standby
除了冻结I/O设备外,还会暂停系统。由于系统核心逻辑单元保持上电状态,操作的状态不会丢失,也会很容易恢复到之前的状态。 处于Standby状态时,可能需要依赖平台来设置唤醒源。
mem
运行状态数据存到内存,并关闭外设,进入等待模式,除了Memory需要进行自刷新来保持数据外,其他的所有设备都需要进入到低功耗状态,就是STR(Suspend to RAM)。除了实现Standby中的操作外,还有一些平台相关的操作要进行。 由于存在掉电行为,因此Resume的时候需要重新进行配置,唤醒过程较慢,处于STR状态时,需要依赖平台设置唤醒源。
disk
这个操作会将运行时的context保存在Disk这种非易失的存储器中,然后进行掉电操作,就是STD(Suspend-to-Disk)。比如当按下电源键进行唤醒时,然后恢复,唤醒过程最慢。
上面四种状态,功耗节省效果依次增强,同时唤醒回来的时间开销也相应加大。
9.2. Regulator Framework¶
Regulator翻译为”调节器”,分为电压调节器(voltage regulator)和电流调节器(current regulator),是电源管理的底层基础设施之一,在内核中regulator实际是个抽象出来的概念。
在linux中regulator Framework框架设计,控制系统中某些设备的电压/电流供应,并且在系统运行的过程中,动态改变regulators的输出,以达到省电的目的。 该Regulator框架为各种使用电源的设备(consumer)提供统一接口,允许获取电压,限制电压,使能和关闭电源等操作,也提供了Regulator驱动接口, 允许注册电源提供者(provider)并向内核提供操作函数等。
Linux Regulator Framework整体分为四个部分,分别是machine(regulator硬件制约,映射关系等),regulator(理解为regulator driver), consumer(regulator的使用者,服务对象),sys-class-regulator(用户空间接口)。
9.2.1. Regulator驱动¶
Regulator驱动主要是电源提供者(provider)注册和通过相关操作函数,电源提供者(provider)是PMIC等。下面介绍下注册接口函数和数据结构:
struct regulator_desc结构体用来描述一个独立的PMIC提供的每个调节器,一个静态描述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | struct regulator_desc {
const char *name;
const char *supply_name;
const char *of_match;
const char *regulators_node;
/*..................*/
int id;
unsigned n_voltages;
const struct regulator_ops *ops;
int irq;
enum regulator_type type;
struct module *owner;
unsigned int min_uV;
unsigned int uV_step;
unsigned int ramp_delay;
/*..................*/
};
|
上面结构体有省略,详细请看内核源码/include/linux/regulator/driver.h,有详细的注释。
name: Regulator的名字
supply_name :该regulator parent的name,在级联时使用
of_match:匹配设备树中的regulator名字
regulators_node:自动从DTS中解析init_data
id:regulator的标识
n_voltages :regulator可用的选择器输出数量,固定输出电压,应将n_voltage设置为1
ops:一组操作函数,回调函数,用来操作电源管理,注册regulator资源,会指定
type: 表示regulator是电压调节器还是电流调节器。
min_uV: 表示regulator输出的最小电压
ramp_delay : 电压改变后稳定下来所需时间
1 2 3 4 | struct regulator_dev *regulator_register(struct regulator_desc *regulator_desc,
const struct regulator_config *config);
void regulator_unregister(struct regulator_dev *rdev);
|
regulator_register函数是注册regulator的接口,传入regulator_desc和regulator_config两个结构体参数,regulator_desc描述regulator以及相关操作函数, regulator_config主要包含一些调节器描述的可变元素和一些约束,一种安全限制等。返回一个regulator_dev结构体,该结构体是一个抽象的描述对regulator,如下(有省略):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | struct regulator_dev {
const struct regulator_desc *desc;
int exclusive;
u32 use_count;
u32 open_count;
u32 bypass_count;
/* lists we belong to */
struct list_head list; /* list of all regulators */
/* lists we own */
struct list_head consumer_list; /* consumers we supply */
struct coupling_desc coupling_desc;
struct blocking_notifier_head notifier;
struct mutex mutex; /* consumer lock */
struct task_struct *mutex_owner;
int ref_cnt;
struct module *owner;
struct device dev;
struct regulation_constraints *constraints;
struct regulator *supply; /* for tree */
const char *supply_name;
struct regmap *regmap;
struct delayed_work disable_work;
int deferred_disables;
void *reg_data; /* regulator_dev data */
/*..................*/
};
|
list:regulator链表。
consumer_list:该regulator下所有的consumer。
notifier: regulator的通知链,用于给consumer通知event。
constraints:结构体regulation_constraints结构对regulator施加的一些的限制,是一种安全限制。结构体如下(有省略):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | struct regulation_constraints {
const char *name;
/*电压输出范围 */
int min_uV;
int max_uV;
int uV_offset;
/* 电流输出范围 */
int min_uA;
int max_uA;
int ilim_uA;
int system_load;
/* used for coupled regulators */
int max_spread;
/* 标志对这个regulator有效的操作模式*/
unsigned int valid_modes_mask;
/* regulator有效的操作 */
unsigned int valid_ops_mask;
/* regulator input voltage - only if supply is another regulator */
int input_uV;
/*.......................*/
/* 约束标志位 */
unsigned always_on:1; /* 当系统开启时,regulator不会关闭 */
unsigned boot_on:1; /* bootloader/firmware enabled regulator */
unsigned apply_uV:1; /* 当电压最大值等于最小值,使能约束,是固定的电压*/
unsigned ramp_disable:1; /* disable ramp delay */
unsigned soft_start:1; /* ramp voltage slowly */
unsigned pull_down:1; /* pull down resistor when regulator off */
unsigned over_current_protection:1; /* auto disable on over current */
}
|
该结构体是一个安全限制,比如是电压调节器,限制了电压的输出范围等,这些一般在设备树描述,比如min_uA对应设备树的“regulator-min-microvolt”属性。
9.2.2. consumer接口函数¶
consumer是regulator提供服务的对象,使用者。每个consumer都有个结构体:
1 2 3 4 5 6 7 8 9 10 11 12 | struct regulator {
struct device *dev;
struct list_head list;
unsigned int always_on:1;
unsigned int bypass:1;
int uA_load;
struct regulator_voltage voltage[REGULATOR_STATES_NUM];
const char *supply_name;
struct device_attribute dev_attr;
struct regulator_dev *rdev; //关联的regulator
struct dentry *debugfs;
};
|
常见的consumer接口函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /*获取和释放*/
struct regulator *regulator_get(struct device*dev, const char *id);
void regulator_put(struct regulator *regulator);
/*使能和关闭*/
int regulator_enable(regulator);
int regulator_disable(regulator);
/*设置regulator的电压,获得regulator的电压状态*/
int regulator_set_voltage(regulator, min_uVmax_uV)
int regulator_get_voltage(regulator);
int regulator_set_current_limit(regulator, min_uA, max_uA);
/*操作模式控制和状态*/
int regulator_set_optimum_mode(struct regulator*regulator,int load_uA);
int regulator_set_mode(struct regulator*regulator, unsigned int mode);
unsigned int regulator_get_mode(struct regulator*regulator);
|
9.2.3. 用户空间sysfs接口¶
登录lubancat2系统,切换到/sys/class/regulator目录下,使用命令:
可以看到该目录有我们注册的regulator,并且都是链接文件,指向平台设备下的具体文件。 我们切换到一个regulator.14(tcs4525)目录下,可以看到一系列文件:
state 表示该regulator,可以是’enabled’、’disabled’、’unknown’几种状态。
type 表示regulator的类型,是电压、电流等,即:’voltage’、’current’、’unknown’
microvolts 表示输出的电压,min_microvolts和max_microvolts就表示可以输出的电压范围
num_users 表示使用改regulator的consumer数量
opmode 操作模式,一般是’normal’
9.3. 源码简单分析¶
将以lubancat2为例,简单讲解下rk3568的供电。 参考lubancat2原理图,rk3568电源供电PMIC是rk809和tcs4525,通过i2c控制,设备树描述如下(有省略):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | &i2c0 {
status = "okay";
vdd_cpu: tcs4525@1c {
compatible = "tcs,tcs452x";
reg = <0x1c>; //I2c地址
vin-supply = <&vcc5v0_sys>; //其父regulator节点,可以理解成tcs452x需要的外部电源
regulator-compatible = "fan53555-reg"; //弃用的属性
regulator-name = "vdd_cpu"; //regulator的名字
regulator-min-microvolt = <712500>; //输出最小的电压
regulator-max-microvolt = <1390000>; //输出最大的电压
regulator-ramp-delay = <2300>;//电压改变需要一定的生效时间,描述生效时间和电压的变化值成比例单位为uV/us
fcs,suspend-voltage-selector = <1>; //声明电压选择寄存器,用于suspend
regulator-boot-on; //这是一个bootloader/firmware 阶段使能的调节器
regulator-always-on; //表示不应该禁用调节器
regulator-state-mem {
regulator-off-in-suspend; //系统suspend下,该regulator关闭
};
};
/*.............*/
}
|
在I2C0节点下,描述了tcs4525设备,被描述成一个regulator,其中regulator-”为前缀的字段,是regulator特有的字段,一些解释看下注释。 “compatible”属性会匹配到i2c驱动module_i2c_driver(fan53555_regulator_driver),然后调用fan53555_regulator_probe()进行初始化, 最后调用devm_regulator_register()函数注册该regulator。(详细参考内核源码drivers/regulator/fan53555.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | &i2c0 {
/*.............*/
rk809: pmic@20 {
compatible = "rockchip,rk809";
reg = <0x20>; //i2c地址
interrupt-parent = <&gpio0>; //中断父节点
interrupts = <3 IRQ_TYPE_LEVEL_LOW>; //中断号中断类型
pinctrl-names = "default", "pmic-sleep",
"pmic-power-off", "pmic-reset";
pinctrl-0 = <&pmic_int>;
pinctrl-1 = <&soc_slppin_slp>, <&rk817_slppin_slp>;
pinctrl-2 = <&soc_slppin_gpio>, <&rk817_slppin_pwrdn>;
pinctrl-3 = <&soc_slppin_gpio>, <&rk817_slppin_rst>;
/*.............*/
vcc1-supply = <&vcc3v3_sys>;
vcc2-supply = <&vcc3v3_sys>;
vcc3-supply = <&vcc3v3_sys>;
vcc4-supply = <&vcc3v3_sys>;
vcc5-supply = <&vcc3v3_sys>;
vcc6-supply = <&vcc3v3_sys>;
vcc7-supply = <&vcc3v3_sys>;
vcc8-supply = <&vcc3v3_sys>;
vcc9-supply = <&vcc3v3_sys>;
pwrkey { //
status = "okay";
};
/*.............*/
regulators { //5路大电流BUCK,9个LDO、2个SWITCH
vdd_logic: DCDC_REG1 {
regulator-always-on;
regulator-boot-on;
regulator-min-microvolt = <500000>;
regulator-max-microvolt = <1350000>;
regulator-init-microvolt = <900000>;
regulator-ramp-delay = <6001>;
regulator-initial-mode = <0x2>;
regulator-name = "vdd_logic";
regulator-state-mem {
regulator-off-in-suspend;
};
};
/*...........*/
}
rk809_codec: codec { //音频编译码器
#sound-dai-cells = <0>;
compatible = "rockchip,rk809-codec", "rockchip,rk817-codec";
clocks = <&cru I2S1_MCLKOUT_TX>;
clock-names = "mclk";
assigned-clocks = <&cru I2S1_MCLKOUT_TX>, <&cru I2S1_MCLK_TX_IOE>;
assigned-clock-rates = <12288000>;
assigned-clock-parents = <&cru CLK_I2S1_8CH_TX>, <&cru I2S1_MCLKOUT_TX>;
pinctrl-names = "default";
pinctrl-0 = <&i2s1m0_mclk>;
hp-volume = <20>;
spk-volume = <3>;
//mic-in-differential;
status = "okay";
};
};
};
|
vcc1-supply:vcc1-supply这一系列节点,是该pmic的输入电源,都使用“vcc3v3_sys”,前缀vcc1是DCDC_REG1 regulator的supply_name(即指定该regulator父节点),“vcc3v3_sys”节点如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | dc_5v: dc-5v {
/*regulator-fixed是电压固定的regulator,或者是gpio开关控制,这里可以简单理解成板卡供电电源*/
compatible = "regulator-fixed";
regulator-name = "dc_5v";
regulator-always-on;
regulator-boot-on;
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
};
vcc5v0_sys: vcc5v0-sys {
compatible = "regulator-fixed";
regulator-name = "vcc5v0_sys";
regulator-always-on;
regulator-boot-on;
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
vin-supply = <&dc_5v>;
};
vcc3v3_sys: vcc3v3-sys {
compatible = "regulator-fixed";
regulator-name = "vcc3v3_sys";
regulator-always-on;
regulator-boot-on;
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
vin-supply = <&vcc5v0_sys>;
};
|
RK809是一款高性能PMIC,有regulator,rtc,pwrkey,codec等功能,因此该节点下还有多个节点,有“pwrkey”节点、“regulators”节点、“rk809_codec”节点。 注意到regulators节点下没有compatible,只有一系列节点,这些regulator是怎么注册的?
compatible = “rockchip,rk809”匹配到i2c驱动module_i2c_driver(rk808_i2c_driver),会调用rk808_probe,该函数会继续i2c设备初始化等操作, 其中会调用devm_mfd_add_devices函数,添加mfd(multi-function device,多功能设备)设备,会添加rk808-regulator平台设备,该设备会和平台驱动rk808_regulator_driver匹配, 最后在rk808_regulator_probe函数中注册regulators,(详细参考内核源码drivers/mfd/rk808.c和drivers/regulator/rk808-regulator.c)。
前面注册的regulator,那么就有使用者,在rk3568中有一个电源管理单元(PMU),用来控制rk3568中的电源资源,可以进入低功耗模式、支持多个电压源和电源域等。 其中的IO 电源域,用来管理IO输出的电平,IO电源域一般都会接到PMU电源芯片不同组的LDO。这个IO电源域和PMIC硬件连接看下原理图,设备树的描述如下:
1 2 3 4 5 6 7 8 9 10 11 | &pmu_io_domains {
status = "okay";
pmuio1-supply = <&vcc3v3_pmu>;
pmuio2-supply = <&vcc3v3_pmu>;
vccio1-supply = <&vccio_acodec>;
vccio3-supply = <&vccio_sd>;
vccio4-supply = <&vcc_1v8>;
vccio5-supply = <&vcc_3v3>;
vccio6-supply = <&vcc_1v8>;
vccio7-supply = <&vcc_3v3>;
};
|
pmuio1-supply 是指到vcc3v3_pmu,即rk809的 LDO_REG6 regulator,而且是一个固定的3.3V电压,pmuio2-supply同理。
vccio1-supply vccio1电源域的供电,连接到RK809的LDO_REG4,是3.3V。
vccio3-supply 是vccio_sd,接到rk809的LDO_REG5上,其他的类似。
系统中注册的regulator和consumer之间关系或者regulator之间的关系,可以查看下/sys/kernel/debug/regulator/regulator_summary文件, 下面图片显示了IO 电源域和regulator之间的关系:
9.4. 实验¶
下面我们简单编写一个驱动,向内核注册一个regulators,电压范围为500000µV到1350000µV。
配套源码和设备树插件位于linux_driver/power_management。
9.4.1. 驱动代码¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | static int regulator_driver_probe(struct platform_device *pdev)
{
struct regulator_config config = { };
int ret;
config.dev = &pdev->dev;
config.init_data = &my_regulator_initdata;
my_regulator_test_rdev = regulator_register(&my_regulator_desc, &config);
if (IS_ERR(my_regulator_test_rdev)) {
ret = PTR_ERR(my_regulator_test_rdev);
pr_err("Failed to register regulator: %d\n", ret);
return ret;
}
return 0;
}
static struct platform_driver my_regulator_driver = {
.probe = regulator_driver_probe,
.driver = {
.name = "my_regulator",
.owner = THIS_MODULE,
},
};
static struct platform_device *regulator_pdev;
static int my_regulator_test_init(void)
{
int ret;
regulator_pdev = platform_device_alloc("my_regulator", -1);
if (!regulator_pdev) {
pr_err("Failed to allocate dummy regulator device\n");
return -1;
}
ret = platform_device_add(regulator_pdev);
if (ret != 0) {
pr_err("Failed to register dummy regulator device: %d\n", ret);
platform_device_put(regulator_pdev);
return -1;
}
ret = platform_driver_register(&my_regulator_driver);
if (ret != 0) {
pr_err("Failed to register dummy regulator driver: %d\n", ret);
platform_device_unregister(regulator_pdev);
return -1;
}
return 0;
}
static void my_regulator_test_exit(void)
{
regulator_unregister(my_regulator_test_rdev);
platform_device_unregister(regulator_pdev);
platform_driver_unregister(&my_regulator_driver);
}
module_init(my_regulator_test_init);
module_exit(my_regulator_test_exit);
MODULE_LICENSE("GPL");
|
第9行 注册regulator,
第19行 定义平台驱动my_regulator_driver
第29-54行 注册平台驱动和增加平台设备
第56-60行 注销平台设备和平台驱动和regulator
9.4.2. 测试结果¶
设备树插件和内核模块的编译,参考下前面的环境搭建章节,编译后得到regulator_test.ko。
设备树插就加载后,执行命令加载驱动,sudo insmod regulator_test.ko。加载之后, /sys/class/regulator下会生成一个regulator.31的目录(这个后缀数字根据实际的板卡)。
在/sys/kernel/debug/regulator/regulator_summary文件中记录系统regulator和consumer之间的关系,
9.5. 参考¶
内核文档:
Documentation/devicetree/bindings/regulator/regulator.txt
Documentation/ABI/testing/sysfs-class-regulator