生命之风的低语
Whispers in the Wind of Life.

Linux的电源管理-休眠与唤醒

2025-05-22 17:59:57

写在前面

为了理清新平台系统休眠和唤醒的流程,通过学习其他平台的电源管理方法,曲径通幽, 达到目的.

刚接手新平台,且相应的资料不多,很容易让人力不从心;我在网上寻找了学习资源,发现韦东山对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