power domain
最近工作上接触到了kernel power domain framework,kernel文档对这块的解释不多,网上的参考资料也很少,不过还是得感谢wowo大神几年前的文章,对自己还是有帮助的。废话不多说,梳理一下,也让自己加深一下印象。
什么是power domain?
电源域(power domain)可以理解为多个模块共用一个电源,比如usb、gpio在soc的内部实现上都属于同一个xxx子系统,那么xxx子系统要进入低功耗状态的前提是什么?就是usb和gpio都得进入低功耗;那反过来,如果要使用usb呢?usb要能上电的前提就是xxx子系统得先上电(即xxx子系统的控制寄存器的enable位需要置1)。
那么usb和gpio在软件实现上需要如何进入低功耗呢?相信大家也能猜到,那就是runtime_pm,所以power domain和runtime_pm是存在耦合关系的,本篇文章不会深入分析runtime_pm,后续有机会再另起一篇分析。
如何使用power domain?
power domian的使用方法非常的简单,如果不想去深入分析power domain如何工作的,只想使用kernel提供的方法,那么只需在dts中定义一个power domain节点,同时在驱动中注册该power domain即可,是不是很简单?
参考:Documentation\devicetree\bindings\power
//DTS中定义power domain节点,这里以内核文档提供的例程
parent: power-controller@12340000 {
compatible = "foo,power-controller";
reg = <0x12340000 0x1000>;
#power-domain-cells = <1>; //这里表明parent节点是一个power domain,也就是内核文档所形容的provider,parent管理它下面挂接着的其它模块,为它们提供电源
};
child: power-controller@12341000 {
compatible = "foo,power-controller";
reg = <0x12341000 0x1000>;
power-domains = <&parent 0>; //这里表明child节点是parent下面的一个模块,它使用parent提供的电源
#power-domain-cells = <1>;
};
从上面的dts语法中可以看出,一个系统的电源域从dts中可以很容易就看出来,#power-domain-cells声明该节点是一个power domain,power-domains = <&xxx x>表明该节点属于xxx电源域,那么系统中某个电源域下面有多少个模块就从dts中看有多少个节点引用了该电源域即可。
介绍完了dts,那么驱动中如何注册一个电源域呢?
/**
* pm_genpd_init - Initialize a generic I/O PM domain object.
* @genpd: PM domain object to initialize.
* @gov: PM domain governor to associate with the domain (may be NULL).
* @is_off: Initial value of the domain's power_is_off field.
*
* Returns 0 on successful initialization, else a negative error code.
*/
int pm_genpd_init(struct generic_pm_domain *genpd,
struct dev_power_governor *gov, bool is_off) //初始化一个 generic_pm_domain 实例
/**
* of_genpd_add_provider_onecell() - Register a onecell PM domain provider
* @np: Device node pointer associated with the PM domain provider.
* @data: Pointer to the data associated with the PM domain provider.
*/
//下面两个接口都可以用来向内核注册一个power domian
int of_genpd_add_provider_onecell(struct device_node *np,
struct genpd_onecell_data *data)
int of_genpd_add_provider_simple(struct device_node *np,
struct generic_pm_domain *genpd)
可以看到,驱动想要注册一个power domain只需要调用两个函数即可,甚至不需要实现任何具体的函数,so easy吧~
代码分析
接下来进入正题,开始分析内核是如何实现power domain的,为何consumer只需引用power domain节点就可以和provider有耦合。
provider分析
先来看一个非常重要的数据结构,这个数据结构是内核为device电源管理所抽象出来的回调函数,我们关心最后三个即可,即runtime_pm方法,后面的分析我们会发现设备的suspend和resume最终都调用该结构体下的某个函数。
struct dev_pm_ops {
int (*prepare)(struct device *dev);
void (*complete)(struct device *dev);
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
int (*freeze)(struct device *dev);
int (*thaw)(struct device *dev);
int (*poweroff)(struct device *dev);
int (*restore)(struct device *dev);
int (*suspend_late)(struct device *dev);
int (*resume_early)(struct device *dev);
int (*freeze_late)(struct device *dev);
int (*thaw_early)(struct device *dev);
int (*poweroff_late)(struct device *dev);
int (*restore_early)(struct device *dev);
int (*suspend_noirq)(struct device *dev);
int (*resume_noirq)(struct device *dev);
int (*freeze_noirq)(struct device *dev);
int (*thaw_noirq)(struct device *dev);
int (*poweroff_noirq)(struct device *dev);
int (*restore_noirq)(struct device *dev);
int (*runtime_suspend)(struct device *dev); //runtime suspend函数
int (*runtime_resume)(struct