给uboot添加一个休眠的命令,uboot实际上就是一个裸板程序。先忽略linux复杂的软件框架,在裸板上实现休眠的过程。
1. 配置GPIO,比如想维持led亮或灭,用于唤醒CPU的引脚要设为中断功能
从原理图上可以看出,
JZ2440上面有4个按键,只有S2,S3,S4可以用作唤醒源,设置它们对应的GPIO用作中断模式。
在S3c24x0.h/include目录下,打开rGPFCON、rGPGCON所对应的宏
rGPFCON寄存器对应着EINT0,EINT2,需要将bit[5:4]=10,bit[1:0]=10.
rGPGCON寄存器对应着EINT11需要将bit[7:6]=10.
rGPFCON & =~((3<<0) |(3<<4)) ;
rGPFCON | = ((2<<0) | (2<<4));
rGPGCON & = ~(3<<6);
rGPGCON | =(2<<6);
2.设置INTMSK屏蔽所有中断:在sleep模式下,这些引脚只是用于唤醒系统,当CPU正常运行时可以重新设置INTMSK,让这些引脚用于中断功能。
rINTMASK = ~0;
3.配置唤醒源,设置中断的触发方式。在这里设为上升沿和下降沿触发。
rEXTINT0 | =(6<<0) | (6<<8); // EINT0、EINT2双边沿触发
rEXTINT1 | =(6 << 12); //EINT11双边沿触发
4.设置MISCCR[13:12]=11b,使得USB模块进入休眠
rMISCCR | =(3<<12);
5.在GSTATUS[4:3] 中保存某值,它们可以在系统被唤醒时使用
rGSTATUS3 = /*一般来说是在GSTATUS3这个寄存器中存放唤醒时首先执行函数的地址*/
rGSTATUS4 = /*想写入什么就写什么,*/
写入rGSTATUS3、rGSTATUS4这两个寄存器的值,在系统休眠的过程中这些值保持不变。因此可以在系统唤醒的过程中,根据里面的值做一些事情。
比如说可以把一些函数的地址放在里面,当唤醒时,就去执行这个函数
6.配置MISCCR[1:0]使能数据总线的上拉电阻
rMISCCR & =~(3<<0);
7.清除LCDCON1.ENVID以停止LCD
rLCDCON1 &= ~(1<<0);
注意8~12使用汇编来实现。
8.读这2个寄存器: rREFRESH and rCLKCON以便填充TLB。如果不使用MMU的话,这一步可以忽略。这里没有使用MMU,看下面的分析就知道了。
参考内核源码:
arch\arm\mach-s3c2410\sleep.S
在boot目录cpu\arm920t\下,创建suspend.S
9.设置REFRESH[22]=1b,让SDRAM进入self-refresh mode
10.等待SDRAM进入self-refresh mode
11.设置 MISCCR[19:17]=111b以保护SDRAM signals(SCLK0,SCLK1 and SCKE)
12.设置 CLKCON register的SLEEP mode位,让系统进入sleep mode
对于2440的Arm开发板,里面有一个CPU,访问SDRAM,指令和数据都存在SDRAM里面。
现在想让系统休眠,过程是先让SDRAM进入自刷新模式(它可以保持里面的数据不被损坏,但是CPU就不能再去访问它了)
休眠时:
1)让SDRAM自刷新,意味着CPU无法访问它;
2)自刷新后,设置CLK以进入sleep
在1)和2)之间应该有代码执行,本来代码是存在SDRAM上的,代码应该去哪里取呢?
如果内核启动了MMU,需要有页表。如果使用的是虚拟地址,CPU需要根据页表找到物理地址,页表就在SDRAM上。现在SDRAM没法访问,怎么办?
因此需要页表以支持MMU进行地址映射;
看一下在内核里面的代码:
ldr r4, =S3C2410_REFRESH
ldr r5, =S3C24XX_MISCCR
ldr r6, =S3C2410_CLKCON
ldr r7, [ r4 ] @ get REFRESH (and ensure in TLB)
ldr r8, [ r5 ] @ get MISCCR (and ensure in TLB)
ldr r9, [ r6 ] @ get CLKCON (and ensure in TLB)
orr r7, r7, #S3C2410_REFRESH_SELF @ SDRAM sleep command
orr r8, r8, #S3C2410_MISCCR_SDSLEEP @ SDRAM power-down signals
orr r9, r9, #S3C2410_CLKCON_POWER @ power down command
teq pc, #0 @ first as a trial-run to load cache
bl s3c2410_do_sleep
teq r0, r0 @ now do it for real
b s3c2410_do_sleep @
@@ align next bit of code to cache line
.align 5
s3c2410_do_sleep:
streq r7, [ r4 ] @ SDRAM sleep command
streq r8, [ r5 ] @ SDRAM power-down config
streq r9, [ r6 ] @ CPU sleep
1: beq 1b
mov pc, r14
S3C2410_REFRESH、S3C24XX_MISCCR、S3C2410_CLKCON这三个都是虚拟地址,先故意去读一下虚拟地址中的内容,那么这个映射关系就会存在TLB中,也就是说把地址的映射关系存在TLB中临时使用。
在关闭SDRAM后,再去访问相关的寄存器,可以从TLB中得到映射关系。不需要去SDRAM上读取页表了。
同理,在uboot中需要执行1)和2)之间的指令时,也可以先去读一次这些指令,这些指令就会存入Icache中。当把SDRAM关闭后,再次执行前面的那些指令时,它就会从ICache中得到那些指令,不需要去访问SDRAM。这个设计是非常巧妙的。
代码如下:
1.在common/目录下,添加文件cmd_suspend.c
#include <common.h>
#include <command.h>
#include <def.h>
#include <nand.h>
#include <s3c24x0.h>
extern void s3c2440_cpu_suspend(void);
static void delay(volatile int d)
{
while(d--);
}
int do_suspend (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
/* 休眠: */
/* 1. 配置GPIO: 比如想维持LED亮或灭, 用于唤醒CPU的引脚要设为中断功能 */
/* 对于NAND启动: 要设置EINT23,22,21为输入引脚 */
rGPGCON &= ~((3<<30) | (3<<28) | (3<<26));
/* JZ2440只有S2/S3/S4可用作唤醒源,设置它们对应的GPIO用于中断模式 */
rGPFCON &= ~((3<<0) | (3<<4));
rGPFCON |= ((2<<0) | (2<<4));
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使用汇编来实现,参考内核源码:
* 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);
s3c2440_cpu_suspend(); /* 执行到这里就不会返回,直到CPU被唤醒 */
/* 恢复运行: 重新初始化硬件 */
serial_init();
printf("wake up\n");
return 0;
}
U_BOOT_CMD(
suspend, 1, 0, do_suspend,
"suspend - suspend the board\n",
" - suspend the board"
);
2.在cpu/arm920t目录下,添加文件suspend.S
/* s3c2440_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 REFRESH (0x48000024)
#define MISCCR (0x56000080)
#define DCLKCON (0x56000084)
.globl s3c2440_cpu_suspend
@@ prepare cpu to sleep
s3c2440_cpu_suspend:
ldr r4, =REFRESH
ldr r5, =MISCCR
ldr r6, =CLKCON
ldr r7, [ r4 ] @ get REFRESH
ldr r8, [ r5 ] @ get MISCCR
ldr r9, [ r6 ] @ get CLKCON
orr r7, r7, #S3C2440_REFRESH_SELF @ SDRAM sleep command
orr r8, r8, #S3C2440_MISCCR_SDSLEEP @ SDRAM power-down signals
orr r9, r9, #S3C2440_CLKCON_POWER @ power down command
teq pc, #0 @ first as a trial-run to load cache
bl s3c2410_do_sleep
teq r0, r0 @ now do it for real
b s3c2410_do_sleep @
@@ align next bit of code to cache line
.align 5
s3c2410_do_sleep:
streq r7, [ r4 ] @ SDRAM sleep command
streq r8, [ r5 ] @ SDRAM power-down config
streq r9, [ r6 ] @ CPU sleep
1: beq 1b
mov pc, r14