原文地址: 本文带有译者注释,注释部分为笔者原创,其他翻译自原文。如果你的omap3530/omap3515/omap3503经常有一些域比如CORE/MPU/PER不能进入休眠模式,那么这篇文章可能会对你有帮助。
译者:agan
目录:
1. MPU
2. CORE
3. Core Power Domain (Core PM域) 进入IDLE模式的Checklist
MPU
休眠过程首先要让MPU进入休眠状态(RET/OFF)。用户可检查PM_PREPWSTST_MPU.LASTPOWERSTATEENTERED位,这一组位说明在上一次休眠过程中MPU所处的状态。用户需要手动清除这个寄存器,以便跟踪MPU的状态变化。MPU域在活动状态下才能清除此寄存器,清除后状态变成‘ON’。
译者注:
PM_PREWSTST_MPU.LASTPOWERSTATEENTERED位取值:
0x0: MPU domain 之前进入了 OFF 状态
0x1: MPU domain 之前进入了 RET 状态
0x2: MPU domain 之前进入了 INACTIVE 状态
0x3: MPU domain 之前一直处于 ON 状态
实际上,每个PM域都有这样的寄存器和对应的位,在软件(Linux Kernel)里面,这些位似乎是判断一个PM域是否休眠过的唯一检查点。清除此位的动作通常发生在域被唤醒之后。
为了能让MPU进入休眠状态,系统当前不能有未处理的中断,也不能有排队等待响应的唤醒事件。唤醒事件通常来自于MPU所依赖的其他域发生了唤醒事件。
译者注:
MPU可配置成与下列域有依赖关系(即以下PM域的唤醒动作可唤醒MPU):PER/DSS/IVA2/CORE。与唤醒事件及域之间依赖关系的配置寄存器包括:
PM_WKDEP_XXX:配置XXX域与其他哪些域有依赖关系。
PM_MPUGRPSEL1_XXX:配置XXX域的哪些唤醒事件能够唤醒MPU。
IVA作为一个独立的CPU核,唤醒以来关系的配置类似MPU。
后面列出的两点步骤可用于调试MPU域不能睡眠的情况。强烈建议在WFI(1)指令执行之前DUMP所有MPU的CM和PM相关寄存器的值,以供分析确诊。当然也可以在WFI之前加入一个死循环,然后链接调试器查看对应寄存器的值。
1. 确认已有中断已全部处理并清除。检查INTC寄存器查看是否还有IRQ正在排队,所有排队的IRQ必须通过清除IRQSTATUS的方法来予以清除。
2. 检查PM_WKDEP_MPU寄存器,确认MPU对其他域最小依赖,通常情况下只需依赖WKUP域即可。
译者注: (1)WFI指令: Wait For Interrupt指令,是指内存进入自刷新状态时所运行的一段汇编指令,用于等待唤醒中断。
代码追溯:
kernel/arch/arm/mach-omap2/pm34xx.c:omap_sram_idle() -> sleep34xx.S:omap34xx_cpu_suspend()
DUMP寄存器的最佳地点,在调用_omap_sram_idle(omap3_arm_context, save_state)之前。
通常如果发现MPU域和其他域都没有进入休眠状态,则可能是由WKDEP依赖关系引起。
第一条"中断处理"在新的LinuxKernel版本中实际上处理已非常完善,不大可能在这里出错。
当MPU处于休眠状态(WFI指令执行期间),一个唤醒事件外加一个中断能让MPU跳出休眠状态。唤醒事件可让PRCM模块恢复MPU的Clock和Power输入,紧跟着的中断能让MPU跳出WIFI指令,转而执行后续的唤醒程序。假如只有唤醒事件而没有中断,那么即使MPU有了Power和Clock也不能跳出WFI指令,因此亦不能唤醒。
CORE
CORE的休眠调试非常复杂,因为它跟许多外围设备总线都有联系。跟MPU的调试一样,强烈建议在WFI指令之前DUMP所有与CORE相关的CM和PRM域寄存器,以供分析确诊。
CORE是否休眠过,也是通过PM_PREPWSTST_CORE.LASTPOWERSTATEENTERED来判断. 如果发现没有休眠,下面的调试方法可能有用。
方法1. 确认PM_WKST1_CORE和PM_WKST3_CORE两个寄存器的值能被及时清除。这两个寄存器记录CORE域的唤醒事件由谁产生,但同时也需要及时的清除,不然它会阻碍CORE的下一个状态变化。
方法2. 检查CM_IDLEST_XXX寄存器(XXX指域的名称),这个(些)寄存器能告诉你对应域的哪些Clock仍然处于活动状态。但需要注意,即使CM_IDLEST告诉你所有外围Clock都已不可达(不活动,或者说不可访问),这也不能说明对应的所有外围设备都已进入休眠状态(因此也不能说明当前域可以进入休眠)。但是,只要有一个Clock处于活动状态,当前域肯定不能休眠。
方法3. 检查CM_IDLEST_CKGEN(这是PLL域的IDLEST,用于监听Clock活动)。(在WFI指令执行之前,)所有的Clock,只允许DPLL3处于bypass状态,其他都必须为‘not active’或者‘locked’状态。DPLL3为CORE域提供Clock,DPLL3未进入休眠模式是因为DUMP寄存器的时间点Core还未进入休眠模式(不然还怎么DUMP寄存器?)。举例,假如发现96MHz Clock处于Active状态,那肯定是这个Clock的一个用户(比如一个driver)还处于活动状态,顺藤摸瓜发现系统只有串口驱动在使用这个时钟,因此可以确定串口驱动的休眠处理部分有问题。
方法4. 假如以上几点都帮不上忙,那就只有挨个检查每个模块的SYSCONFIG寄存器,确认寄存器里面配置的IDLEMODE要么是'smart idle',要么是'force idle',绝对不能是'no idle'。
译者注:
注意,CM_IDLEST1_CORE一般取值0xffffffbd,即只有SDRC还处于活动状态(因为后续的WFI指令靠DRAM自刷新运行)。如发现WFI之前还有其他模块在活动,则一律视作不正常,找它去! 对于SYSCONFIG,细数OMAP3530一共有56个SYSCONFIG寄存器,基本上每一个功能硬件对应一个,在这里就不一一列出。
Core PM域进入休眠模式的检查点(Checklist)
OMAP3的Core PM包含四个CM域:
* CORE_L3
* CORE_L4
* CORE_D2D
* CORE_CM
CORE PM域进入低功耗模式之前,这4个CM域必须首先进入休眠模式。
这4个CM域的每一个都与其他域有依赖关系,Core进入休眠模式之前,其他有依赖关系的域都必须先进入休眠状态。休眠的状态变化有Initors(发起)和Targets(目标)两个角色参与,发起者主动休眠,目标被动休眠。一个域进入休眠通常需要发起者先处于不活动的状态(standby/mute/不再发起任何状态变化),然后目标才能进入休眠。
如果CORE域未能进入休眠状态,请检查以下项目,它们有可能阻止CORE进入休眠:
1. D2D IDle Ack 和 D2D MSstandby的PIN复用配置,必须把两只脚拉高,并且允许SAD2D_IDLEACK输入。
译者注: 译者使用的软件环境 u-boot1.3.3 (sbc8100) + linux-omap-pm 2.6.38 (beagleboard)。在这套编译环境中,可在bootloader的sbc8100.c或者kernel/arch/arm/omap2/board-omap3sbc8100.c中配置PIN脚服用
2. 对于ES3.0及其以后的芯片封装,D2D接口时钟(Iclk)在reset后自动enable. 这个时钟必须被关掉,寄存器配置在CM_ICLKEN1_CORE Bit 3。
3. 如果使用了调试器,最好在休眠调试的时候拔掉JTAG调试器。
4. CORE/PER/WKUP域的WKST寄存器必须被清除,不然它们会阻止对应域的状态变化。(这一点在上面CORE的休眠中已讲到)
5. 如果需要某个模块产生唤醒事件,大多数模块都需要打开PRCM的PM_WKEN_XXX寄存器对应的位(XXX指模块的名称),以及模块内部寄存器相应的配置位
译者注:
举例:如需要USB_OTG产生唤醒事件,需要打开PRCM.PM_WKEN1_CORE.EN_HSOTGUSB, 还需要打开OTG_SYSCONFIG.ENABLEWAKEUP
如果要让模块产生唤醒中断,PRM_IRQENABLE_MPU中模块对应的位必须打开。同时,IRQSTATUS位必须及时清除。
IRQSTATUS位包括PRM_IRQSTATUS_MPU中对应的位和某些模块内部对应的IRQSTATUS寄存器位。
注意,GPIO模块有两个IRQ status寄存器(IRQSTATUS1和IRQSTATUS2),这两者在处理完后必须都被清除。
6. 检查CM_IDLEST_XXX寄存器,这在上面CORE的描述中已有说明。这个寄存器的位只能说明对应的时钟是否可达,并不能说明对应的硬件是否已休眠。但是如果目标模块要休眠,对应的CM_IDLEST各位基本上都必须处于不可达的状态。
7. 休眠需要关掉接口时钟(CM_ICLKEN_XXX),或者不关接口时钟而打开CM_AUTOIDLE_XXX。
8. 通过CM_FCLKEN_XXX手动关闭功能时钟。
9. 注意关闭FCLK并不就一定能让模块进入休眠。
10. 对于所有模块的SYSCONFIG寄存器(上面提到的56个寄存器), CLOCKACTIVITY位建议设置成0, 即允许关闭接口时钟和功能时钟。 这不是休眠所必需的,但是可以减小功耗。
译者注:
很多模块的SYSCONFIG.CLOCKACTIVITY本身就是只读的,而且默认值就是0。这要对照手册来看。
11. 对于休眠的目标模块,模块的SYSCONFIG寄存器,确保SIDLEMODE(Slave Idle Mode: Target模块为从模块)被设置成FORCE_IDLE或者SMART_IDLE,而非NO_IDLE。推荐设置为SMART_IDLE。
12. 对于休眠的发起模块,模块的SYSCONFIG寄存器,确保MIDLEMODE(Master Idle Mode: Initor模块为主模块)被设置程FORCE_STANDBY或者SMART_STANDBY,而非NO_STANDBY,推荐设置为MART_STANDBY。
13. 如果SYS_BOOT(s)引脚跳线设置成UART3外设启动,那么Boot程序会设置UART3_SYSCONFIG.IDLEMODE 为 NO_IDLE。那么在休眠之前需要把它改成SMART_IDLE或者FORCE_IDLE.
14. 如果SYS_BOOT(s)引脚跳线设置成从MMC1外部设备启动,那么BOOT程序会设置MMCHS1_SYSCONFIG.SIDLEMODE 为 NO_IDLE。 同样在休眠之前需要改成FORCE_IDLE.
译者注:这一点非常重要,因为很多人在调试OMAP35x的时候都喜欢从SD卡启动。
15. 如果SYS_BOOT(s)引脚跳线设置成从USB外设启动,那么BOOT程序会设置OTG_SYSCONFIG.SIDLEMODE 为 NO_IDLE。同样需要在休眠之前改成FORCE_IDLE。
译者注:
关于SYS_BOOT的配置,请查看手册Boot Configuration章节(Rev.S)。
只要从上述三个设备中启动,就要留意观察SYSCONFIG.IDLEMODE的值。
16. 经常查看SCM.CONTROL_SYSCONFIG, 确保IDLEMODE为SMART_IDLE,这一点容易被忽略。
译者注:举例:如果用户发现CM_IDLEST1_CORE的SCM时钟仍然Active,则接下来可以查看寄存器SCM.CONTROL_SYSCONFIG,看它是否真的被设置成了NO_IDLE。
17. 所有DPLL(s)必须配成AUTOIDLE(CM_AUTOIDLE)。
译者注:
OMAP35x一共有5个DPLLs(OMAP3503可能少一个DPPL2_IVA2),他们对应的寄存器配置如下:
名称 消费时钟的域 寄存器名称及说明
----- ----- -----
DPLL1 MPU CM_AUTOIDLE_PLL_MPU
DPLL2 IVA2 CM_AUTOIDLE_PLL_IVA2
DPLL3 CORE CM_AUTOIDLE_PLL
DPLL4 PER CM_AUTOIDLE_PLL (supply 48Mhz/54MHz/96Mhz/...)
DPLL5 PER CM_AUTOIDLE2_PLL (supply 120Mhz/...)
18. 所有DMA传输必须在休眠动作发起之前完成。
19. 所有SDRAM传输必须在休眠动作发起前完成。比如使用DMA在内存区域之间移动数据。
20. SDRAM在收到休眠请求时,必须允许自刷新。这可通过SDRC_POWER_REG.SRFRONIDLEREQ来配置。
译者注:
举例:源代码追溯: arch/arm/mach-omap2/sram34xx.S。
注意SDRC为CPU端的寄存器。
21. 配置所有域的CM_CLKSTCTRL_XXX.CLKTRCTRL_XXX位,允许HW Supervised Transition(硬件主导状态变化),包括EMU域。这里第一个XXX只CM域的名称,第二个XXX指当前域的子功能(比如CORE包括L3/L4)。D2D的这个功能对应CM_CLKSTCTRL_CORE寄存器的4-5位。
22. PM_PWSTCTRL_XXX.POWERSTATE必须被设置正确的值。
译者注:
PM_PWSTCTRL_XXX寄存器配置XXX域在发生状态变化时的一些硬件控制信息。其中POWERSTATE记录休眠模式的目标状态。
这个多由操作系统的休眠代码转换成平台相关的代码这部分功能这部分所为,感觉一般不大可能出错。
23. 由ARM核发起的WFI指令,推荐在I-CACHE或者内部SRAM中运行WFI。需要注意,当WFI运行于I-CACHE中,在SDRAM可访问之前,指令是不会跳转到SDRAM中的。WFI指令可以在其他内存位置中运行,但这并没有被完全测试过。
译者注:笔者使用的硬件很像beagleboard,使用主芯片在OMAP3530和OMAP3503之间来回切换(因为它们PIN脚兼容),使用的Linux Kernel来自于 (2.6.38版本,当然有一些修改)。调试低功耗的时候基本上比较顺利,DUMP寄存器的值也基本上符合当前文档的描述。笔者的意思是,如果用户能及时更新自己的kernel版本,可能会减少很多麻烦。
阅读(3837) | 评论(2) | 转发(1) |