linux-3.4 的 pm-core 代码(2)

驱动代码部分,以全志 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_CPU
    ldr r10, =processor
    ldr r4, [r10, #CPU_SLEEP_SIZE] @ size of CPU sleep state
#else
    ldr r4, =cpu_suspend_size
#endif
    mov r5, sp          @ 当前虚拟 SP 地址
    add r4, r4, #12     @ pgd, virt sp, phys resume fn 空间
    sub sp, sp, r4      @ allocate CPU state on stack
    stmfd   sp!, {r0, r1}       @ save suspend func arg and pointer
    add r0, sp, #8      @ save pointer to save block
    mov r1, r4          @ size of save block
    mov r2, r5          @ virtual SP
    ldr r3, =sleep_save_sp
#ifdef CONFIG_SMP
    ALT_SMP(mrc p15, 0, lr, c0, c0, 5)
    ALT_UP(mov lr, #0)
    and lr, lr, #15
    add r3, r3, lr, lsl #2
#endif
    bl  __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 sleep

    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 自刷新
    orr r8, r8, #S3C2410_MISCCR_SDSLEEP @ SDRAM 保护信号
    orr r9, r9, #S3C2410_CLKCON_POWER   @ 断电

    teq pc, #0          @ 试执行使指令数据加载到 cache 以及 TLB 里面
    @ 在自刷新与断电之间还有指令要执行,所以才要试执行
    bl  s3c2410_do_sleep
    teq r0, r0          @ 正式执行指令
    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

恢复过程

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

ENTRY(s3c_cpu_resume)
    mov r0, #PSR_I_BIT | PSR_F_BIT | SVC_MODE
    msr cpsr_c, r0

    @@ load UART to allow us to print the two characters for
    @@ resume debug

    mov r2, #S3C24XX_PA_UART & 0xff000000
    orr r2, r2, #S3C24XX_PA_UART & 0xff000

#if 0
    /* SMDK2440 LED set */
    mov r14, #S3C24XX_PA_GPIO
    ldr r12, [ r14, #0x54 ]
    bic r12, r12, #3<<4
    orr r12, r12, #1<<7
    str r12, [ r14, #0x54 ]
#endif

#ifdef CONFIG_DEBUG_RESUME
    mov r3, #'L'
    strb    r3, [ r2, #S3C2410_UTXH ]
1001:
    ldrb    r14, [ r3, #S3C2410_UTRSTAT ]
    tst r14, #S3C2410_UTRSTAT_TXE
    beq 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_SMP
    adr r0, sleep_save_sp
    ALT_SMP(mrc p15, 0, r1, c0, c0, 5)
    ALT_UP(mov r1, #0)
    and r1, r1, #15
    ldr r0, [r0, r1, lsl #2]    @ stack phys addr
#else
    ldr r0, sleep_save_sp   @ 被保存的栈的物理地址
#endif
    setmode PSR_I_BIT | PSR_F_BIT | SVC_MODE, r1  @ set SVC, irqs off
    @ load phys pgd, stack, resume fn
  ARM(  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. 判断唤醒源

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值