WINCE60+S3C6410睡眠和唤醒的实现Word文档格式.docx
《WINCE60+S3C6410睡眠和唤醒的实现Word文档格式.docx》由会员分享,可在线阅读,更多相关《WINCE60+S3C6410睡眠和唤醒的实现Word文档格式.docx(49页珍藏版)》请在冰豆网上搜索。
7)因为外部存储器(这里是指SDRAM,我的理解)的内容在睡眠模式下必须要保存起来(如果不保存起来,系统在唤醒的时候就无法从原来进入睡眠的地方唤醒),系统控制器请求外部存储控制器进入自刷新模式(这样就可以让保存在SDRAM中的内容得以保持)。
8)当存储控制器进入自刷新模式后发送确认信息。
9)如果PLL还在使用,系统控制器改变时钟源由PLL输出改为外部振荡器输出。
10)系统控制器关闭PLL操作和晶体振荡器。
11)最后,系统控制器通过让XPWRRGTON引脚维持在低电平来禁用外部电源源,XPWRRGTON信号控制外部调节器。
图5
1.3唤醒源
下图描述了系统各种模式下的唤醒源
图6
1.4退出睡眠模式
当我们激活唤醒源后,系统会进入退出睡眠模式的流程,如下所示:
图7
1)系统控制器通过维持XPWRRGTON引脚为高电平来启动外部电源,并且通过配置PWR_STABLE寄存器来配置等待时钟稳定输出的时间。
2)系统控制器产生系统时钟,包括HCLK、PCLK和ARMCLK。
3)系统控制器释放复位信号,包括HRESETn和PRESETn。
4)系统控制器释放ARM复位信号。
2.供电电路
图8
2.1ALIVE模块供电电路
图9
ALIVE模块的电压范围是1.15到1.25V
2.2RTC模块供电电路
图10
RTC模块工作电压范围是1.7到3.3V
MemoryinterfaceVDDm0工作电压范围是1.7到3.6,VDDm1是1.75到2.7V
2.3
3.睡眠和唤醒的具体实现
3.1睡眠的过程
3.1.1进入睡眠的几种方式
一般情况下,我们有两种方式让系统进入睡眠状态,如下:
1)在系统没有用户相互并且没有其他工作的情况下,在时间timeout后系统自动进入睡眠模式,见common.reg中相关的注册表信息:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\Timeouts]
;
@CESYSGENIFPM_PM_DEFAULT_PDD
"
ACUserIdle"
=dword:
3c;
inseconds
ACSystemIdle"
12c;
ACSuspend"
0;
BattUserIdle"
BattSystemIdle"
b4;
BattSuspend"
@CESYSGENENDIF;
PM_PM_DEFAULT_PDD
@CESYSGENIFPM_PM_PDA_PDD
PM_PM_PDA_PDD
当然了,如果"
12c该为"
0,那么系统就不会自动进入睡眠模式。
从上面的信息可知,如果没有用户的动作,1分钟后系统会进入useridle;
此后三分钟进入systemidle;
接着过了6分钟进入suspendidle,也就是说第11分钟开始的时候进入suspendidle。
2)选择”开始->
挂起”。
3)我们在应用程序和驱动中调用下面函数
SetSystemPowerState(NULL,POWER_STATE_SUSPEND,POWER_FORCE)
起始这种方式和2)本质是一样的。
3.1.2BSP包中进入睡眠的流程
我们以上面提到的第3)中方式来学习和描述,在按键驱动中我们检测到按下GPL9/EINT17按下时,我们调用SetSystemPowerState函数来让系统进入睡眠模式:
图11
这里调用了SetSystemPowerState函数之后,在BSP包中的流程如下:
电源管理器(PM)会根据common.reg中下面的内容
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\State\Suspend\{98C5250D-C29A-4985-AE5F-AFE5367E5006}]
"
Default"
4;
D4
获取到系统睡眠(也即挂起)状态对应的系统电源level为D4,这样,PM就通知支持电源管理的驱动先对这个电源状态做相应的处理,比如对于背光驱动来说,因为其支持电源管理,所以PM会以控制码调用BKL_IOControl函数IOCTL_POWER_SET,见下图
图12
那么背光驱动BackLightSetState函数是如何响应这个电源请求的呢?
见其实现:
图13
一般系统中有多个驱动支持电源管理,那么先调用哪个呢?
系统根据每个驱动注册表信息的Order值来调用的,这个值越大,就越先被调用,处理完支持电源管理驱动的XXX_IOControl()之后,根据同样的依据原则来调用每个驱动的XXX_PowerDown函数,此后,就会调用OEMPowerOff()来让系统进入睡眠模式。
调用OEMPowerOff()函数
此函数在s3c6410_sec_v1\oal\power\off.c下定义,其实现如下:
1)调用BSPPowerOff函数
此函数的动作如下:
调用VFL_Sync函数来等待NANDFLASH擦除或写操作完成。
调用ChangeClockDivider()函数来改变时钟,包括MFC、PCLK、HCLK*2和ARMCLK的时钟。
关闭RTC控制,当然了,如果是需要通过RTC来唤醒的,这里就不能关闭。
调用BSPConfigGPIOforPowerOff函数来根据硬件设计的具体情况配置GPIO口为相应的模式,原则就是减少功耗。
调用S3C6410_WakeUpSource_Configure函数来配置唤醒源,这里的唤醒源是GPN11/EINT11,配置为下降沿触发,此函数体如下:
图14
结合图6,可知MMC0到MMC2不是睡眠模式下的唤醒源,故关闭,在本设计中,只用到按键作为唤醒源。
2)保存VIC(VectoredInterruptController,矢量中断控制器)寄存器。
3)保存DMAC(DMA控制器)寄存器。
4)保存GPIO寄存器。
5)保存系统控制器寄存器,也即从0x7E00F000到0x7E00FA0C地址对应的寄存器。
图15
6)控制USB电源
图16
下面我来看LCD接口的配置图:
图17
7)调用OALCPUPowerOff函数让CPU进入睡眠模式
此函数在src\oal\oallib\startup.s中定义,下面按照调用的顺序来学习这个函数
图18
1)保存SVC模式下的寄存器到堆栈中。
这里先大概学习一下啊堆栈,堆栈是在RAM存储器(这里是指SDRAM)中开辟的一个特定的存储区域,在这个区域,信息的存入(入栈)和取出(出栈)是按照“后进先出”的原则进行存取的。
在子程序调用时要保存返回地址,在中断处理过程中要保存断点地址,进入子程序和中断处理后还要保留通用寄存器的值。
子程序执行完毕和中断处理完毕返回时,又要恢复通用寄存器的值,并分别将返回地址或断点地址恢复到指令指针寄存器中。
这些功能都要通过堆栈来实现。
堆栈的一端是固定的(也就是堆栈有个基地址),另一端是浮动的(但有堆栈的上限地址)。
堆栈固定端是堆栈的底部,称为栈底,堆栈浮动端可以存入或取出数据。
向堆栈存入数据时,新存入的数据存放在之前存入数据的上面,而最先存入的数据被存到堆栈底部,最后存入的数据堆放在堆栈顶部,这称为栈顶。
由于堆栈顶部是浮动的,为了指示现在堆栈中存放数据的位置,就需要一个堆栈指针SP(R13),它始终指向堆栈的顶部,这样,堆栈中数据的存入和取出都由SP来指示。
堆栈指针通常可以指向不同的位置,堆栈可以分为满栈和空栈两种,当栈指针指向栈顶元素,也即最后一个入栈的数据元素时,称为满(Full)栈;
当栈指针指向与栈顶元素相邻的一个可用数据单元时,称为空(Empty)栈。
另外根据数据栈的增长方向分为递增栈和递减栈两种,当数据栈向内存地址减少的方向增长时,称为递减(Descending)栈;
当数据栈向内存地址增加的方向增长时,称为递增(Ascending)栈。
综合这两种特点可有一下4中数据栈:
满递减栈:
FD(FullDescending)
空递减栈:
ED(EmptyDescending)
满递增栈:
FA(FullAscending)
空递增栈:
EA(EmptyAscending)
由于要遵守ATPCS规则(ARM-ThumbProduceCallStandard,ARM和Thumb程序调用基本规则),而ATPCS规定数据栈为FD(FullDescending,满栈递减)类型,所以当一个数据(32位)入栈时,SP(R13)的值-4向下浮动指向下一个地址,即新的栈顶;
当数据出栈时,SP(R13)的值+4向上浮动指向下新的栈顶。
ARM中入栈和出栈操作都是以字(32位)为单位的。
下面来分析这部分代码,我们知道R13常用作堆栈指针(sp),用于保存当前堆栈地址。
SVC:
表示处理器模式为管理模式。
Stm指令是多寄存器存储指令,在此表示把r4到r12这9个寄存器中的值存储到基址寄存器(sp)所指示的一片连续存储器中,stm后面的db表示每次传送前地址值减(-4);
!
表示数据加载与存储完毕之后,将最后的地址写入基址寄存器,如不使用!
,则基址寄存器的内容不改变。
假设sp=0x90050,stmdbsp!
{r4-r12}的操作如下:
第一步:
在存储前,0x90050的值减4(对于ARM指令是4,对Thumb指令是2),也即为0x9004c。
第二步:
把寄存器r12的值存储到0x9004c指向的存储单元。
第三步:
0x9004c-4=0x90048。
第四步:
把寄存器r11的值存储到0x90048指向的存储单元。
……………………………………
最后一步:
就是sp=0x90030
lr:
R14寄存器也称为子程序连接器(SubroutineLinkRegister)或连接寄存器LR。
Lr得到R15(程序计数器PC)的备份,也即用于保存子程序的返回地址,以便子程序调用完成后利用此地址返回到调用处,也就是OEMPowerOff()函数调用OALCPUPowerOff()函数的地方。
2)保存唤醒函数到指定的内存地址处
IMAGE_SLEEP_DATA_UA_START表示SDRAM中睡眠数据区域的基地址,这里=0xA0000000+0x00028000=0xA0028000,也就是说睡眠模式下的数据保存在此基地址的起始地址处,那么这块区域是多大呢?
这个基地址和这块区域的大小是在config.bib下定义的,如下:
图19
当然了,我们在image_cfg.inc下要根据这些值来设定好,这里我们把WakeUp_Address函数的地址保存在0xA0028000指向的存储单元处,为后面的唤醒做好准备,WakeUp_Address函数后面再介绍。
3)把控制寄存器C1的SBZ位清零及SBO置位后C1寄存器的值保存在睡眠数据区域
mrcp15,0,r2,c1,c0,0
把协处理器CP15的控制寄存器C1的值读取到r2中,这里先来学习一下C1:
图20
ldrr0,=SYSCTL_SBZ_MASK;
ShouldBeZeroMaskforSystemControlRegister
bicr2,r2,r0
SYSCTL_SBZ_MASK的值为0xCC1A0000,上面的代码主要是对C1寄存器的SBZ(shouldbezero)位清零,结合下图对C1寄存器位的描述理解:
图21
ldrr0,=SYSCTL_SBO_MASK;
ShouldBeOneMaskforSystemControlRegister
orrr2,r2,r0
SYSCTL_SBO_MASK=0x00000070,上面的代码是对C1寄存器的SBO(shouldbeone)位置1,见图21的描述。
strr2,[r3],#4;
[SleepState_SYSCTL]
把对C1寄存器相关位修改之后的值保存到0xA0028004存储单元处。
4)读取TTB寄存器的值并且对其SBZ清零后保存在睡眠数据区域
mrcp15,0,r2,c2,c0,0;
loadr2withTTBRegister0
读取协处理器CP15的TTBR0(TranslationTableBaseRegister0)寄存器的值到r2寄存器中,这个寄存器用于保存第一级转换表的物理地址,下面是TTBR0寄存器的描述
图22
根据我的理解,TTB寄存器保存的第一级转换表的物理地址就是g_oalAddressTable[DATA]数据的基地址,此数组在oemaddrtab_cfg.inc文件中定义,此数组是相当的重要。
ldrr0,=MMUTTB_SBZ_MASK;
ShouldBeZeroMaskforTTBRegister0
[SleepState_MMUTTB0]
MMUTTB_SBZ_MASK=0x00001FE0,上面的代码是对TTB寄存器的SBZ清零,清零之后TTB寄存器的值保存在0xA0028008内存单元处。
5)读取TTBR1寄存器的值保存在睡眠数据区域
mrcp15,0,r2,c2,c0,1;
loadr2withTTBRegister1
[SleepState_MMUTTB1]
读取协处理器CP15的TTBR1(TranslationTableBaseRegister1)寄存器的值到r2寄存器中,这个寄存器用于保存第一级页表的物理地址,下面是TTBR1寄存器的描述
图23
把TTBR1寄存器的值保存在0xA002800C内存单元处。
6)读取TTBCR寄存器的值保存在睡眠数据区域
mrcp15,0,r2,c2,c0,2;
loadr2withTTBControlRegister
[SleepState_MMUTTBCTL]
读取TTBCR(TranslationTableBasecontrolRegister)的值保存在0xA0028010内存单元处,下面见TTBCR的描述:
图24
7)读取DACR寄存器的值保存在睡眠数据区域
mrcp15,0,r2,c3,c0,0;
loadr2withDomainAccessControlRegister
[SleepState_MMUDOMAIN]
读取DACR(DomainAccessControlRegister)的值保存在0xA0028014内存单元处,此寄存器定义了ARM处理器的16个域的访问权限。
下面继续学习OALCPUPowerOff函数
图25
8)保存SVC模式下的堆栈指针SP(也即寄存器R13的内容)和SPSR寄存器的值保存到睡眠数据区域
因为CPU的每种工作模式(7种)均有自己独立的物理寄存器R13,一般都要初始化每种模式下的R13,使其指向该工作模式的栈空间,这样,当程序的运进入异常模式时,可以将需要保护的寄存器存入R13指向的堆栈,而当程序从异常模式返回时,则从对应的堆栈中恢复,采用这样的方式可以保证异常发生后程序的正常执行。
这里就是把堆栈指针SP(也即寄存器R13的内容)保存到0xA0028018内存单元处。
接下来把SPSR寄存器的内容保存在0xA002801C内存单元处,这里CPSR和SPSR寄存器及相关知识:
ARM体系结构中包含一个当前程序状态寄存器CPSR(CurrentProgramStatusResigter)和5个备份的程序状态寄存器(SPSRs,SavedProgramStatusResigter)。
CPSR可在任何工作模式下被访问,用来保存ALU(运算器)中的当前操作信息、控制使能和禁止中断、设置处理器的工作模式等。
而备份的程序状态寄存器用来进行异常处理,程序状态寄存器的格式如下:
图27
每种工作模式下(除了用户模式和系统模式,因为这两种模式不属于异常模式)都有一个专用的SPSR,当异常发生时,SPSR用户保存CPSR的当前直接,从异常退出时则可由SPSR来恢复CPSR,因为SVC模式是异常模式,所以需要保存SPSR,保存在0xA002801C内存单元处。
9)把FIQ模式下的寄存器值保存在睡眠数据区域
movr1,#Mode_FIQ|NOINT;
EnterFIQmode,nointerrupts
msrcpsr,r1
Mode_FIQ=0x11,NOINT=0xC0,结合图27理解,在此是让处理器进入管理模式(SVC),并且禁止FIQ和IRQ中断。
mrsr2,spsr;
StatusRegister
stmiar3!
{r2,r8-r12,sp,lr};
StoreFIQmoderegisters[SleepState_FIQ_SPSR~SleepState_FIQ_LR]
把FIQ模式下的SPSR,r8-r12,sp和lr分别寄存器保存到0xA002801C到0xA002803C内存单元处。
10)把ABT模式下的寄存器值保存在睡眠数据区域
进入终止模式(ABT),并且并且禁止FIQ和IRQ中断,然后把此模式下的spsr,sp和lr寄存器保存在0xA0028040到0xA0028048内存单元处。
11)把IRQ模式下的寄存器值保存在睡眠数据区域
进入中断请求模式(IRQ),并且并且禁止FIQ和IRQ中断,然后把此模式下的spsr,sp和lr寄存器保存在0xA0028040到0xA0028048内存单元处。
12)把UND模式下的寄存器值保存在睡眠数据区域
进入未定义模式(UND),并且并且禁止FIQ和IRQ中断,然后把此模式下的spsr,sp和lr寄存器保存在0xA002804C到0xA0028054内存单元处。
13)把系统模式下的寄存器值保存在睡眠数据区域
进入系统模式(SYS),并且并且禁止FIQ和IRQ中断,然后把此模式下的spsr和lr寄存器保存在0xA0028058到0xA002805C内存单元处。
14)把fpscr和fpscr寄存器保存在睡眠数据区域
Fpscr(Floating-PointStatusandControlRegister),fpscr(Floating-pointexceptionregister)
接着调用下面的
fstmiaxr3!
{d0-d15}
这里还不清楚是什么意思,有待学习
15)返回SVC模式,计算睡眠区域数据的校验码,并且把校验码保存在INFORM寄存器中。
图29
下面是CPU对INFORM寄存器的描述
图30
此寄存器一般用于保存用户的信息。
16)清除TLB和刷新cache
图31
先来学习一下TLB:
TLB简要学习
TLB:
translationlookasidebuffer,在CPU发出VA请求读取数据的时候,TLB接收到该地址。
TLB是MMU中的一块高速缓存(也是一种cache),它缓存最近查找过的VA对应的页表项,如果TLB里缓存了当前VA的页表项就不必做TranslationTableWalk了,否则去物理内存中读取页表项保存在TLB中,TLB缓存可以减少访问物理内存的次数。
TLB根据功能可以译为快表,直译可以为旁路转换缓冲,也可以理解成页表缓冲,里面存放的是一些页表文件(虚拟地址到物理地址的转换表)。
当处理器要在主内存寻址时,不是直接在内存的物理地址里查找,而是通过一组虚拟地址转换到主内存的物理地址,TLB就是负责将虚拟内存地址转化为实际的物理内存地址,而CPU寻址时会优先在TLB中进行寻址,处理器的性能和寻址的命中率有很大的关系。
为什么要使用TLB
映射机制必须使一个程序能断言某个地址在其自己的进程空间或地址空间内,并且能够高效的将其转换为真实的物理地址以访问内存。
一个方法是使用一个含有整个空间内所有页的入口(entry)的表(也即页表),每个入口包含这个页的正确物理地址,这是个相当大的数据结构,因为不得不存放于主存中。
由于CPU首先接到的是由程序传递过来的虚拟内存地址,所以CPU必须先到屋里内存中取页表,然后对应程序传来的虚拟页面号,在表里找到对应的物理页面号,最后才能访问实际的物理内存地址,也就是说要访问两次物理内存(实际上访问的次数可能更多)。
因此,为了减少CPU访问物理内存的次数,引入TLB(是一种cache,高速缓存)。
通常在ARM的实现中每个内存接口有一个TLB,特点如下:
:
有一个存储器接口的系统通常有一个唯一的TLB。
指令和数据的内存接口分开的系统通常有分开的指令TLB和数据TLB。
当存储器中的转换表被改变或选中了不同的转换表(通过写CP15的寄存器C2),先前在TLB中的转换表遍历结果将不再有效。
MMU结构提供了刷新TLB的操作,也允许特定的转换表遍历结果被锁定在一个TLB中,这就保证了对相关的存储器区域的访问绝不会导致转