驱动代码部分,以全志 sunxi-3.4 代码以及 linux-3.4.2 的 samsung 的 s3c2410 代码为例

1. PM 核心模块 platform_suspend_ops->enter()

全志的代码

/*
*********************************************************************************************************
*                           全志 aw_pm_enter 代码注释(截取部分主体代码)
*Arguments  : state     系统睡眠状态;
*Return     : 成功时返回 0 值;
*********************************************************************************************************
*/
static int aw_pm_enter(suspend_state_t state)
{
    normal_standby_func standby;    standby = (int (*)(struct aw_pm_info *arg))SRAM_FUNC_START; /* 指向 SRAM 的起始位置,该位置的代码由全志 standby.bin 文件决定,可以参考其中的 Makefile 以及链接文件 */    //把 standby 代码拷贝到 SRAM 起始位置
    memcpy((void *)SRAM_FUNC_START, (void *)&standby_bin_start, (int)&standby_bin_end - (int)&standby_bin_start);
    /* 配置唤醒事件类型 */
    if(PM_SUSPEND_MEM == state || PM_SUSPEND_STANDBY == state){        standby_info.standby_para.axp_src = AXP_MEM_WAKEUP;
    }else if(PM_SUSPEND_BOOTFAST == state){        standby_info.standby_para.axp_src = AXP_BOOTFAST_WAKEUP;
    }
    standby_info.standby_para.event_enable = (SUSPEND_WAKEUP_SRC_EXINT | SUSPEND_WAKEUP_SRC_ALARM); //唤醒源为外部中断或者 alarm    if (standby_timeout != 0)
    {        standby_info.standby_para.event_enable = (SUSPEND_WAKEUP_SRC_EXINT | SUSPEND_WAKEUP_SRC_ALARM | SUSPEND_WAKEUP_SRC_TIMEOFF);
        standby_info.standby_para.time_off = standby_timeout;
    }
    /* 跳转到 SRAM 运行 standby 代码,此时会正式进入休眠状态 */
    standby(&standby_info);    /* 到这里已经从休眠状态返回了,重新使能看门狗 */
    dogMode = pm_enable_watchdog();    return 0;
}

搜索 standby_bin_start 得到在 arch\arm\mach-sun7i\pm\standby.S 里面有

    .globl  standby_bin_start
standby_bin_start:.incbin "arch/arm/mach-sun7i/pm/standby/standby.bin" // 包含二进制文件.globl  standby_bin_end
standby_bin_end:.align  2

进入 arch/arm/mach-sun7i/pm/standby/ 查看链接文件得知 standby.bin 的主函数为该目录下的 standby.c 文件中的 main 函数

/*
*********************************************************************************************************
*                                   standby 主处理程序段(截取部分主体代码)
* Description: standby main process entry.
* Arguments  : arg  pointer to the parameter that transfered from sys_pwm module.
* Returns    : none
* Note       : 该函数的参数是上面 standby(&standby_info); 一句传入的结构体
*********************************************************************************************************
*/
int main(struct aw_pm_info *arg)
{char    *tmpPtr;tmpPtr = (char *)&__bss_start;/* 冲洗指令与数据 TLB,分别有 32 项数据 TLB 与 指令 TLB吗, TLB 通常是轮流分配的。最旧的输入条目总是在下一次分配 */mem_flush_tlb();/* 重新加载 TLB */mem_preload_tlb();/* 清除 bss 段 */do{*tmpPtr ++ = 0;}while(tmpPtr <= (char *)&__bss_end);/* 从 DRAM 拷贝传入的结构体 */standby_memcpy(&pm_info, arg, sizeof(pm_info));pm_info.standby_para.event = 0;/* copy standby code & data to load tlb *///standby_memcpy((char *)&__standby_end, (char *)&__standby_start, (char *)&__bss_end - (char *)&__bss_start);/* backup dram traning area */standby_memcpy((char *)dram_traning_area_back, (char *)DRAM_BASE_ADDR, DRAM_TRANING_SIZE);/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! *//* init module before dram enter selfrefresh *//* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! *//* initialise standby modules */standby_clk_init();standby_clk_apbinit();mem_int_init();standby_tmr_init();standby_power_init(pm_info.standby_para.axp_src);/* 根据前面的设置,选择性初始化唤醒源 */if(pm_info.standby_para.event_enable & SUSPEND_WAKEUP_SRC_EXINT){mem_enable_int(INT_SOURCE_EXTNMI);}... .../* 保存栈指针, 转换为 sram 的栈空间 */sp_backup = save_sp();/* 使能 dram 进入自刷新模式 */dram_power_save_process(0);//mctl_self_refresh_entry();/* 进入 standby 模式,里面采用汇编的 WFI 指令实现暂停运行 */standby();/* 使能看门狗,保护 dram 的恢复过程 */standby_tmr_enable_watchdog();/* 恢复 dram 的运行 *///dram_power_up_process();//mctl_self_refresh_exit();init_DRAM(&pm_info.dram_para);/* 关闭看门狗 */standby_tmr_disable_watchdog();/* 恢复备份的栈指针,重新转到 dram */restore_sp(sp_backup);/* 根据设置选择性恢复模块 */if(pm_info.standby_para.event_enable & SUSPEND_WAKEUP_SRC_USB){standby_usb_exit();}... ...standby_power_exit(pm_info.standby_para.event_enable);standby_tmr_exit();mem_int_exit();standby_clk_apbexit();standby_clk_exit();/* restore dram traning area */standby_memcpy((char *)DRAM_BASE_ADDR, (char *)dram_traning_area_back, DRAM_TRANING_SIZE);/* 报告唤醒源 */arg->standby_para.event = pm_info.standby_para.event;return 0;
}

samsung 的代码

这里只是列举了 platform_suspend_ops->enter 部分的代码,其余的不是很重要,代码里面做的事情有

  1. 检查是否配置了唤醒源
  2. 保存一些寄存器的状态。比如 gpio、uart 等
  3. 配置唤醒源
  4. 做一些 cpu 的准备工作
  5. 清 cache,把 cpu 控制的各个模块关掉,如 uart 等等,给第 3 位写 1 的时候会进入 sleep 模式
  6. 保存栈,通用寄存器等,cpu 进入休眠状态
  7. 从 sleep 模式恢复的时候恢复 uart、gpio 等的状态
/* s3c_pm_enter* sleep/resume 核心处理模块
*/
static int s3c_pm_enter(suspend_state_t state)
{/* 检查是否有唤醒源 */if (!any_allowed(s3c_irqwake_intmask, s3c_irqwake_intallow) &&!any_allowed(s3c_irqwake_eintmask, s3c_irqwake_eintallow)) {printk(KERN_ERR "%s: No wake-up sources!\n", __func__);printk(KERN_ERR "%s: Aborting sleep\n", __func__);return -EINVAL;}/* 保存所有没被驱动涉及的必需的核心寄存器 */samsung_pm_save_gpios();samsung_pm_saved_gpios();s3c_pm_save_uarts();s3c_pm_save_core();/* 设置中断唤醒模块 */s3c_pm_configure_extint();s3c_pm_arch_prepare_irqs();/* 见下面注释 */pm_cpu_prep();/* 清除 cache */flush_cache_all();s3c_pm_check_store();    //校验一下 crc/* 使 cpu 进入 sleep 模式 */s3c_pm_arch_stop_clocks();    // 停止模块运行cpu_suspend(0, pm_cpu_sleep); // 正式进入 sleep 模式,代码注释见下面/* 恢复系统状态 */s3c_pm_restore_core();s3c_pm_restore_uarts();samsung_pm_restore_gpios();s3c_pm_restored_gpios();/* check what irq (if any) restored the system */s3c_pm_arch_show_resume_irqs();S3C_PMDBG("%s: post sleep, preparing to return\n", __func__);/* LEDs should now be 1110 */s3c_pm_debug_smdkled(1 << 1, 0);s3c_pm_check_restore();/* ok, let's return from sleep */S3C_PMDBG("S3C PM Resume (post-restore)\n");return 0;
}

下面函数对应 enter 函数里面的 pm_cpu_prep(); 一句,这里面主要做的事情是把恢复后要运行的第一个函数的地址保存到 S3C2410_GSTATUS3 寄存器里面,这个寄存器是约定好的恢复函数的存放位置,里面存放的是物理地址,使用 virt_to_phy() 来转换。

static void s3c2410_pm_prepare(void)
{/* GSTATUS3 寄存器里面保存唤醒后要执行的代码位置,这里就是 s3c_cpu_resume 函数 */__raw_writel(virt_to_phys(s3c_cpu_resume), S3C2410_GSTATUS3);/* 不同的芯片各有相应的微调设置 */if (machine_is_h1940()) {void *base = phys_to_virt(H1940_SUSPEND_CHECK);unsigned long ptr;unsigned long calc = 0;/* generate check for the bootloader to check on resume */for (ptr = 0; ptr < 0x40000; ptr += 0x400)calc += __raw_readl(base+ptr);__raw_writel(calc, phys_to_virt(H1940_SUSPEND_CHECKSUM));}... ...
}

cpu_suspend(0, pm_cpu_sleep);里面会调用到 __cpu_suspend(0, pm_cpu_sleep); 该函数如下所示。执行完该函数之后,该部分的栈空间保存的东西为

高地址
lr                     cpu_suspend 函数内的返回地址
r11                    通用寄存器
...
r4
cpu_suspend_size       cpu suspend 指定的寄存器
phys resume fn         恢复函数的物理地址(cpu_arm920_do_resume)
sp                     当前栈的虚拟地址
pgd                    一个结构体,page global directory
低地址

sleep_save_sp 指向的内存空间保存的是 sp 的物理地址,它指向 pgd
最后的 ldmfd sp!, {r0, pc} 跳转执行 s3c2410_cpu_suspend

值得一提的是 __cpu_suspend_save 里面的 virt_to_phys(cpu_do_resume); 相关的代码为

#define cpu_do_resume           __glue(CPU_NAME,_do_resume)#define __glue(name,fn)     ____glue(name,fn)
#define ____glue(name,fn)   name##fn#ifdef CONFIG_CPU_ARM920T
# ifdef CPU_NAME
#  undef  MULTI_CPU
#  define MULTI_CPU
# else
#  define CPU_NAME cpu_arm920
# endif
#endif

展开后 cpu_do_resume 等同于 cpu_arm920_do_resume ,该函数在 proc-arm920.S 里面定义


/** 保存 cpu 状态。保存 cpu 通用寄存器,在内核栈上分配空间来保存 cpu 指定寄存器和其它唤醒时用到的数据*  r0 = suspend 函数参数*  r1 = suspend 函数的地址*/
ENTRY(__cpu_suspend)stmfd   sp!, {r4 - r11, lr}    @保存 r4-r11,lr 到栈里面
#ifdef MULTI_CPUldr r10, =processorldr r4, [r10, #CPU_SLEEP_SIZE] @ size of CPU sleep state
#elseldr r4, =cpu_suspend_size
#endifmov r5, sp          @ 当前虚拟 SP 地址add r4, r4, #12     @ pgd, virt sp, phys resume fn 空间sub sp, sp, r4      @ allocate CPU state on stackstmfd   sp!, {r0, r1}       @ save suspend func arg and pointeradd r0, sp, #8      @ save pointer to save blockmov r1, r4          @ size of save blockmov r2, r5          @ virtual SPldr r3, =sleep_save_sp
#ifdef CONFIG_SMPALT_SMP(mrc p15, 0, lr, c0, c0, 5)ALT_UP(mov lr, #0)and lr, lr, #15add r3, r3, lr, lsl #2
#endifbl  __cpu_suspend_save    @根据 r0-r3 的值正式进行保存工作adr lr, BSYM(cpu_suspend_abort)  @BSYM,thumb 指令集中对 adr 的使用ldmfd   sp!, {r0, pc}       @ 调用 suspend 函数
ENDPROC(__cpu_suspend)

调用下面的函数之后就可以真正的进入 sleep 模式了,里面实现了 SDRAM 自刷新以及保护信号的开启,最终往芯片的 CLKCON 寄存器的第三位写入 1 进入休眠

ENTRY(s3c2410_cpu_suspend)@@ prepare cpu to sleepldr r4, =S3C2410_REFRESHldr r5, =S3C24XX_MISCCRldr r6, =S3C2410_CLKCONldr 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 自刷新orr r8, r8, #S3C2410_MISCCR_SDSLEEP @ SDRAM 保护信号orr r9, r9, #S3C2410_CLKCON_POWER   @ 断电teq pc, #0          @ 试执行使指令数据加载到 cache 以及 TLB 里面@ 在自刷新与断电之间还有指令要执行,所以才要试执行bl  s3c2410_do_sleepteq r0, r0          @ 正式执行指令b   s3c2410_do_sleep    @ 进入睡眠状态@@ align next bit of code to cache line.align  5
s3c2410_do_sleep:streq   r7, [ r4 ]          @ SDRAM sleep commandstreq   r8, [ r5 ]          @ SDRAM power-down configstreq   r9, [ r6 ]          @ CPU sleep
1:  beq 1bmov pc, r14

恢复过程

设置 cpsr_c 也就是 cpu 运行模式,调用 cpu_resume

ENTRY(s3c_cpu_resume)mov r0, #PSR_I_BIT | PSR_F_BIT | SVC_MODEmsr cpsr_c, r0@@ load UART to allow us to print the two characters for@@ resume debugmov r2, #S3C24XX_PA_UART & 0xff000000orr r2, r2, #S3C24XX_PA_UART & 0xff000#if 0/* SMDK2440 LED set */mov r14, #S3C24XX_PA_GPIOldr r12, [ r14, #0x54 ]bic r12, r12, #3<<4orr r12, r12, #1<<7str r12, [ r14, #0x54 ]
#endif#ifdef CONFIG_DEBUG_RESUMEmov r3, #'L'strb    r3, [ r2, #S3C2410_UTXH ]
1001:ldrb    r14, [ r3, #S3C2410_UTRSTAT ]tst r14, #S3C2410_UTRSTAT_TXEbeq 1001b
#endif /* CONFIG_DEBUG_RESUME */b   cpu_resume

cpu_resume 函数提取被保存在 sleep_save_sp 的 sp 物理地址,取出 phys resume fn、sp、pgd 分别放入 pc、sp、r1 里面,于是代码继续跳转到 cpu_arm920_do_resume 执行

/** Note: 下面部分代码被放到了 .data 段里面,这样可以保证当我们不能依靠 MMU 的时候也可以访问到 sleep_save_sp。当然也可以把 sleep_save_sp 放到 .text 段,但是可能有一些设置使得 .text 段变得只读,由于休眠唤醒过程需要写入操作,所以还是放到 .data 段*/.data.align
ENTRY(cpu_resume)
#ifdef CONFIG_SMPadr r0, sleep_save_spALT_SMP(mrc p15, 0, r1, c0, c0, 5)ALT_UP(mov r1, #0)and r1, r1, #15ldr r0, [r0, r1, lsl #2]    @ stack phys addr
#elseldr r0, sleep_save_sp   @ 被保存的栈的物理地址
#endifsetmode PSR_I_BIT | PSR_F_BIT | SVC_MODE, r1  @ set SVC, irqs off@ load phys pgd, stack, resume fnARM(  ldmia   r0!, {r1, sp, pc}   )
THUMB(  ldmia   r0!, {r1, r2, r3}   )
THUMB(  mov sp, r2          )
THUMB(  bx  r3          )
ENDPROC(cpu_resume)sleep_save_sp:.rept   CONFIG_NR_CPUS.long   0               @ preserve stack phys ptr here.endr

执行上面的代码会依次调用到

cpu_arm920_do_resume 使无效 TLB,cache
cpu_resume_mmu       开启 mmu,icache
cpu_resume_after_mmu cpu 初始化,ldmfd  sp!, {r4 - r11, pc} 取回休眠前保存的值,返回到 cpu_suspend 函数继续执行

s3c2440 手册指出的睡眠过程
1. 配置 GPIO 以适应 SLEEP 模式
2. 在 INTMSK 寄存器中屏蔽所有中断,因为在 sleep 模式下引脚不起中断作用,只用于唤醒系统
3. 配置唤醒源,包括 RTC alarm (唤醒源对应的 EINTMASK 位不能被屏蔽,以便 SRCPND 或者 EINTPEND 相应位能够被设置)
4. 设置 USB 为 suspend 模式 (MISCCR[13:12]=11b)
5. 保存一些有用的值到 GSTATUS[4:3] 寄存器里面. 这些寄存器在 SLEEP 模式下会被保护
6. 设置 MISCCR[1:0] 使数据总线上拉,D[31:0]. 如果有外部总线,比如 74LVCH162245,要关闭其上拉电阻.否则的话就开启上拉电阻.此外,内存相关引脚被设置为两种状态,一种是 Hi-z,另一种是 Inactive 状态
7. 清 LCDCON1.ENVID 位以关闭 LCD
8. 读 rREFRESH 和 rCLKCON 寄存器来填充 TLB.
9. 设置 REFRESH[22]=1b 使 SDRAM 进入自刷新模式
10. 等待 SDRAM 正式进入自刷新模式
11. 设置 MISCCR[19:17]=111b 来保护 SDRAM 信号(SCLK0,SCLK1 and SCKE)
12. 设置 CLKCON 寄存器相应的 SLEEP 模式位

从睡眠模式返回
1. 当唤醒源被触发的时候会导致 reset 事件发生,等同于按下系统复位键
2. 检查 GSTATUS2[2] 来判断是按下了复位键还是从 sleep 模式返回
3. 设置 MISCCR[19:17]=000b 来释放 SDRAM 信号保护
4. 配置 SDRAM 控制器
5. 等待 SDRAM 自刷新模式被释放,一般需要一个行刷新周期
6. 使用 GSTATUS[3:4] 里面存放的信息,sleep 模式下这两个寄存器被保护起来了
7. 判断唤醒源

linux-3.4 的 pm-core 代码(2)相关推荐

  1. linux 电源管理 Generic PM之Suspend功能

    Linux电源管理(6)_Generic PM之Suspend功能 作者:wowo 发布于:2014-8-22 21:40 分类:电源管理子系统 1. 前言 Linux内核提供了三种Suspend: ...

  2. 一份简单的在 Linux下编译及调试 C 代码的指南

    摘要: 一份简单的在 Linux下编译及调试 C 代码的指南 对于Linux下的C程序员来说,几乎天天都会和Linux打交道.但在很多人的眼中,Linux是一个易用性极差.靠命令驱动的操作系统,根本无 ...

  3. linux内核5万行代码,[图]AMD为Linux内核贡献27.5万行代码 确认Van Gogh APU支持DDR5和VCN3...

    原标题:[图]AMD为Linux内核贡献27.5万行代码 确认Van Gogh APU支持DDR5和VCN3 在本周五发布的补丁更新中,AMD 为下一代 Van Gogh APU 向 Linux 贡献 ...

  4. linux越狱amd卡代码,为Linux内核贡献27.5万行代码中:AMD意外泄漏下一代APU信息

    原标题:为Linux内核贡献27.5万行代码中:AMD意外泄漏下一代APU信息 AMD在不经意间泄露了自家下一代APU的信息,其代号"Van Gogh(梵高)". 据外媒报道称,A ...

  5. SAP PM 入门系列8 - PM事务代码

    SAP PM 入门系列8 - PM事务代码 IA07 Display General Task List  IA03 Display Equipment Task List  IA13 Display ...

  6. Linux uart寄存器读写,Linux下读写UART串口的代码

    Linux下读写UART串口的代码,从IBM Developer network上拿来的东西,操作比較的复杂,就直接跳过了,好在代码能用,记录一下- 两个实用的函数- /** *@brief 设置串口 ...

  7. Linux自动备份MySQL数据库脚本代码

    Linux自动备份MySQL数据库脚本代码 下面这段Linux的Shell脚本用于每日自动备份MySQL数据库,可通过Linux的crontab每天定时执行 在脚本中可设置需要备份的数据库表清单,并且 ...

  8. linux indent命令: 调整C原始代码文件的格式

    linux indent命令: 调整C原始代码文件的格式 介绍: indent命令可辨识C的原始代码文件,并加以格式化,以方便程序员阅读. 语法: indent [参数][源文件] indent [参 ...

  9. Linux网络编程组播测试代码

    Linux网络编程组播测试代码 (转载) 组播客户端代码如下: #include <sys/types.h> #include <sys/socket.h> #include ...

  10. Linux 内核通知链和例程代码

    概念 大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣.为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制.通知链表只 ...

最新文章

  1. 测试一款CSDN免费下载软件
  2. jmeter中文_JMeter安装配置
  3. hbase java api 两种方式
  4. .NET常见问题汇总
  5. 什么时候用转发什么时候用重定向_验孕棒什么时候用最准确
  6. YUV与RGB互转各种公式 (YUV与RGB的转换公式有很多种,请注意区别!!!)
  7. scala解析csv文件写入mysql_scala实战之spark源码修改(能够将DataFrame按字段增量写入mysql数据表)...
  8. 大数据对企业竞争的作用
  9. 实现类似黑客帝国的字符流特效屏保
  10. 可靠性标准: TL9000
  11. LDAP简介及Java、客户端连接
  12. 算法学习 - 拼接成最大的数字
  13. 重装系统后电脑图片显示不出来怎么办
  14. 极坐标解圆锥曲线三角形面积范围问题
  15. 怎样删除Github中的项目
  16. Qt官方示例-虚拟键盘使用
  17. java虚拟机win10_主编解读win10系统Java虚拟机错误的详尽解决方法
  18. 女程序员,靠脸还是靠技术?
  19. (转载)魔兽世界任务制作教学
  20. 汽车限速器单片机c语言程序,以MSP430单片机为核心的电子汽车限速器的设计方案...

热门文章

  1. C++——vector容器的基本使用和模拟实现
  2. FastRNABindR:快速准确预测蛋白质-RNA界面残基
  3. 【Linux开发环境搭建】arm-linux-gnueabihf 交叉编译工具链安装
  4. tomcat 没有service.bat、tomcat8.exe、tomcat8w.exe、tomcatX.exe文件,官网下载方法及地址
  5. 群体遗传学---admixture软件快速群体分群
  6. 使用element-tiptap后报错Duplicate use of selection JSON ID cell解决方法
  7. 李开复——人工智能领域的中坚力量
  8. codevs 2618 核电站问题 题解报告
  9. 软件测试面试题库和答案解析
  10. 漫画 | 程序员最大的悲哀是什么?