13-设备驱动中的电源管理
12.4 设备驱动中的电源管理
一个真实生活中的设备驱动除了要处理设备的基本功能以外,还需要处理电源管理,主要提供挂起和恢复用的suspend()、resume()两个函数,对于platform_driver而言,该结构体已经包含了这两个成员函数,包含suspend()、resume()入口的platform_driver一般形如代码清单12.19。
代码清单12.19 包含suspend/resume的platform.driver
1 #ifdef CONFIG_PM
2 static int xxx_suspend(struct platform_device *pdev, pm_message_t state)
3 {
4 ...
5 return 0;
6 }
7
8 static int xxx_resume(struct platform_device *pdev)
9 {
10 ...
11 return 0;
12 }
13 #else
14 #define xxx_suspend NULL
15 #define xxx_resume NULL
16 #endif
17
18 static struct platform_driver xxx_driver = {
19 .probe = xxx_probe,
20 .remove = xxx_remove,
21 .suspend = xxx_suspend,
22 .resume = xxx_resume,
23 .driver = {
24 .name = "xxx",
25 .owner = THIS_MODULE,
26 },
27 };
上述代码清单中,对于suspend()、resume()进行了CONFIG_PM宏的检查,也就是说内核配置了电源管理的情况下,才定义suspend()、resume()的实体,否则将它们定义为NULL。
通常而言,在suspend()函数里面会停止设备,并关闭给它提供的时钟,所以在suspend()函数里面经常看见这样的语句:
clk_disable(xxx->clk);
而在resume()函数中,进行相反的操作:
clk_enable(xxx->clk);
clk_disable()、clk_enable()的具体实现直接依赖于SoC的类型,实际上,在BSP内为SoC内的各个PLL、分频器和时钟gate建立了一颗树,并提供了一组操作时钟的通用API。因此,在具体的设备驱动中,最好不要直接去修改寄存器来操作时钟,而应该用如下API:
/ 获得、释放时钟 /
struct clk clk_get(struct device dev, const char *id);
void clk_put(struct clk *clk);
/ 使能、禁止时钟 /
int clk_enable(struct clk *clk);
void clk_disable(struct clk *clk);
/ 获得、试探和设置频率 /
unsigned long clk_get_rate(struct clk *clk);
long clk_round_rate(struct clk *clk, unsigned long rate);
int clk_set_rate(struct clk *clk, unsigned long rate);
/ 设置、获得父时钟 /
int clk_set_parent(struct clk clk, struct clk parent);
struct clk clk_get_parent(struct clk clk);
从代码清单12.2中platform_driver结构体的定义可知,它除了包含suspend()和resume()入口以外,还包含如下两个入口:
int (suspend_late)(struct platform_device , pm_message_t state);
int (resume_early)(struct platform_device );
suspend_late()与suspend()的区别在于,suspend_late()工作于中断都被禁止的情况下,而且仅有一个CPU是活跃的。相似的,resume_early()也工作于中断都被禁止的情况下。绝大多数情况下,设备驱动不提供suspend_late()和resume_early()入口。