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提供的每个调节器,一个静态描述。

struct regulator_desc(内核源码/include/linux/regulator/driver.h)
 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,如下(有省略):

regulator_dev(内核源码/include/linux/regulator/driver.h)
 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施加的一些的限制,是一种安全限制。结构体如下(有省略):

regulation_constraints(内核源码/include/linux/regulator/machine.h)
 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都有个结构体:

regulator_dev(内核源码/drivers/regulator/internal.h)
 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目录下,使用命令:

broken

可以看到该目录有我们注册的regulator,并且都是链接文件,指向平台设备下的具体文件。 我们切换到一个regulator.14(tcs4525)目录下,可以看到一系列文件:

broken
  • 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控制,设备树描述如下(有省略):

tcs4525设备树描述(内核源码arch/arm64/boot/dts/rockchip/rk3568-lubancat2.dts)
 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)

rk809设备树描述(内核源码arch/arm64/boot/dts/rockchip/rk3568-lubancat2.dts)
 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”节点如下:

regulator-fixed节点(内核源码arch/arm64/boot/dts/rockchip/rk3568-lubancat2.dts)
 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硬件连接看下原理图,设备树的描述如下:

(内核源码arch/arm64/boot/dts/rockchip/rk3568-lubancat2.dts)
 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之间的关系:

broken

9.4. 实验

下面我们简单编写一个驱动,向内核注册一个regulators,电压范围为500000µV到1350000µV。

配套源码和设备树插件位于linux_driver/power_management

9.4.1. 驱动代码

regulator_test.c(linux_driver/power_management/下)
 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的目录(这个后缀数字根据实际的板卡)。

broken

在/sys/kernel/debug/regulator/regulator_summary文件中记录系统regulator和consumer之间的关系,

broken

9.5. 参考

内核文档:

Documentation/devicetree/bindings/regulator/regulator.txt

Documentation/ABI/testing/sysfs-class-regulator

https://docs.kernel.org/power/regulator/overview.html