Linux的电源管理-休眠与唤醒
写在前面
为了理清新平台系统休眠和唤醒的流程,通过学习其他平台的电源管理方法,曲径通幽, 达到目的.
刚接手新平台,且相应的资料不多,很容易让人力不从心;我在网上寻找了学习资源,发现韦东山对S3C2440的驱动讲解有相关的内容,口碑也不错,可以作为一个切入点.
不同平台一定会有平台的差异,不同的Linux内核版本之间也会有相应的差异,但思路是一致的,可以总结出来,帮助理解电源管理的开发使用.
本文总结关于系统休眠和唤醒的流程以及开发方法.
Linux电源管理基本框架
下图所示为Linux电源管理基本框架. 详细请参考Linux电源管理(1)_整体架构 本文重点介绍关于Generic PM的使用.
Generic PM,就是Power Management,如Power Off、 Suspend to RAM、Suspend to Disk、Hibernate等.
关于Suspend, Linux内核提供了四种Suspend: Freeze、Standby、STR(Suspend to RAM)和STD(Suspend to Disk),如下表 . 在用户空间向”/sys/power/state”文件分别写入”freeze”、”standby”、”mem”和"disk",即可触发它们。
模式
描述
freeze
冻结I/O设备,将它们置于低功耗状态,使处理器进入空闲状态,唤醒最快,耗电比其它standby, mem, disk方式高
standby
除了冻结I/O设备外,还会暂停系统,唤醒较快,耗电比其它 mem, disk方式高
mem
将运行状态数据存到内存,并关闭外设,进入等待模式,唤醒较慢,耗电比disk方式高
disk
将运行状态数据存到硬盘,然后关机,唤醒最慢. 对于嵌入式系统,由于没有硬盘,所以一般不支持
Linux内核提供的电源管理框架引入了面向对象的思想,理解起来会困难.为此,很有必要在抛开复杂框架的基础上,通过实验验证suspend的结果,总结suspend的流程后,帮助理解linux的supend流程.
S3C2440 Sleep模式进入与唤醒
对于S3C2440 的power manager有三种模式,如下图所示,
我们选择在u-boot中实现Sleep mode作为suspend, 也就是对应Linux内核实现的mem模式的suspend. 实验的效果是:在u-boot命令行运行suspend指令后系统进入Sleep Mode; 当按下按键后,系统唤醒,继续接着休眠前的地方执行下去.
进入Sleep Mode的流程
实现代码如下:
/*
* weidongshan@qq.com, www.100ask.net
*
*/
#include
#include
#include
#include
#include
extern void s3c2440_cpu_suspend(void);
int do_suspend (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]);
U_BOOT_CMD(
suspend, 1, 0, do_suspend,
"suspend - suspend the board\n",
" - suspend the board"
);
int do_suspend (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
/* 休眠的实现: */
/* 对于NAND启动: 要设置EINT23,22,21为输入引脚 */
rGPGCON &= ~((3<<30) | (3<<28) | (3<<26));
/* 1. 配置GPIO模式,用于唤醒CPU的引脚要设为中断功能 */
/* JZ2440只有S2/S3/S4可用作唤醒源,设置它们对应的GPIO用于中断模式 */
/*这几个IO对于着按键,也就是实现按键唤醒*/
/* EINT0 EINT2*/
rGPFCON &= ~((3<<0) | (3<<4));
rGPFCON |= ((2<<0) | (2<<4));
/*EINT11*/
rGPGCON &= ~(3<<6);
rGPGCON |= (2<<6);
/* 2. 设置INTMSK屏蔽所有中断: 在sleep模式下,这些引脚只是用于唤醒系统,当CPU正常运行时可以重新设置INTMSK让这些引脚用于中断功能 */
rINTMSK = ~0;
/* 3. 配置唤醒源 , 为了能在休眠模式下被唤醒*/
rEXTINT0 |= (6<<0) | (6<<8); /* EINT0,2双边沿触发 */
rEXTINT1 |= (6<<12); /* EINT11双边沿触发 */
/* 4. 设置MISCCR[13:12]=11b, 使得USB模块进入休眠 */
rMISCCR |= (3<<12);
/* 5. 在GSTATUS[4:3]保存某值, 它们可以在系统被唤醒时使用 */
//rGSTATUS3 = ; /* 唤醒时首先执行的函数的地址 */
//rGSTATUS4 = ; /* */
/* 6. 设置 MISCCR[1:0] 使能数据总线的上拉电阻 */
rMISCCR &= ~(3);
/* 7. 清除 LCDCON1.ENVID 以停止LCD */
rLCDCON1 &= ~1;
/* 8~12使用汇编在s3c2440_cpu_suspend函数来实现,参考内核源码:
* arch\arm\mach-s3c2410\sleep.S
*/
/* 8. 读这2个寄存器: rREFRESH and rCLKCON, 以便填充TLB
* 如果不使用MMU的话,这个目的可以忽略
*/
/* 9. 设置 REFRESH[22]=1b,让SDRAM进入self-refresh mode */
/* 10. 等待SDRAM成功进入self-refresh mode */
/* 11.设置 MISCCR[19:17]=111b以保护SDRAM信号(SCLK0,SCLK1 and SCKE) */
/* 12. 设置CLKCON的SLEEP位让系统进入sleep mode */
printf("suspend ...");
delay(1000000); //保证printf打印完整
s3c2440_cpu_suspend(); /* 执行到这里就不会返回,直到CPU被唤醒 */
/* 恢复运行: 重新初始化硬件 */
serial_init();
printf("wake up\n");
return 0;
}
代码解释
对于进入Sleep Mode的第5步,主要目的是保存休眠前的PC指针到GSTATUS寄存器,以便唤醒后恢复现场. 这一步的实现,放到 s3c2440_cpu_suspend函数中.
其中s3c2440_cpu_suspend函数的功能是让系统真正进入休眠状态,实现流程如下:
/* suspend.S weidongshan@qq.com, www.100ask.net
* s3c2410_cpu_suspend
* put the cpu into sleep mode
*/
#define S3C2440_REFRESH_SELF (1<<22)
#define S3C2440_MISCCR_SDSLEEP (7<<17)
#define S3C2440_CLKCON_POWER (1<<3)
#define GSTATUS2 (0x56