本来这些是在上一篇文章的,写着写着感觉太多了,所以单独出来。这篇太基于平台了。
我的是mini2440,如果你不用这个,你就看一下platform_suspend_ops解释。
源码对它解释很详细。简单说一下。
struct platform_suspend_ops {
int (*valid)(suspend_state_tstate);//告诉系统平台是否可以睡眠,enter_state()开始就判断了,我没细说。
int(*begin)(suspend_state_t state);// 睡眠前的一个初始过渡,suspend_devices_and_enter()开始时调用了,这个接口让我们可以把一些需要在系统睡眠前要先睡眠的设备先执行睡眠。
int (*prepare)(void);//就是在begin之后prepare_late之前要做的一些事
int(*prepare_late)(void);//在设备驱动suspend之后,关nonboot-cpus之后执行
int(*enter)(suspend_state_t state);//进入suspend
void (*wake)(void);//唤醒,在开nonboot-cpus之后,设备驱动resume之前执行,对应prepare_late.
void (*finish)(void);//一些系统唤醒后要唤醒的设备,和begin或prepare对应。
bool(*suspend_again)(void);//判断是否要再次睡眠
void (*end)(void);//唤醒设备后通知PM核
void (*recover)(void);//恢复平台如果suspend失败。
};
你可以根据这些给你的系统添加一些关于suspend的硬件操作。
看我的平台
arch/arm/plat-samsung/pm.c
static const struct platform_suspend_opss3c_pm_ops = {
.enter = s3c_pm_enter,
.prepare = s3c_pm_prepare,
.finish = s3c_pm_finish,
.valid =suspend_valid_only_mem,
};
从platform_suspend_ops顺序看
valid:用的是系统提供的。
int suspend_valid_only_mem(suspend_state_tstate)
{
return state == PM_SUSPEND_MEM;
}
简单不说了
prepare:会调用s3c_pm_check_prepare()
arch/arm/plat-samsung/pm-check.c
void s3c_pm_check_prepare(void)
{
crc_size = 0;
s3c_pm_run_sysram(s3c_pm_countram, &crc_size);
S3C_PMDBG("s3c_pm_prepare_check: %uchecks needed\n", crc_size);
crcs = kmalloc(crc_size+4, GFP_KERNEL);
if (crcs == NULL)
printk(KERN_ERR "Cannotallocated CRC save area\n");
}
s3c_pm_run_sysram会调用
staticvoid s3c_pm_run_res(struct resource *ptr, run_fn_t fn, u32 *arg)
{
/*
ptr为&iomem_resource
struct resource iomem_resource = {
.name = "PCI mem",
.start = 0,
.end = -1,
.flags = IORESOURCE_MEM,
};
*/
while (ptr != NULL) {
if (ptr->child != NULL)
s3c_pm_run_res(ptr->child, fn, arg);//递归调用
if ((ptr->flags & IORESOURCE_MEM) &&
strcmp(ptr->name, "System RAM") == 0) {
S3C_PMDBG("Found system RAM at %08lx..%08lx\n",
(unsignedlong)ptr->start,
(unsigned long)ptr->end);
arg = (fn)(ptr, arg);
/*
(fn)(ptr, arg);
就是
static u32 *s3c_pm_countram(struct resource *res, u32 *val)
res为
for_each_memblock(memory, region) {
res =alloc_bootmem_low(sizeof(*res));
res->name = "SystemRAM";
res->start =__pfn_to_phys(memblock_region_memory_base_pfn(region));
res->end =__pfn_to_phys(memblock_region_memory_end_pfn(region)) - 1;
res->flags = IORESOURCE_MEM |IORESOURCE_BUSY;
有很多块mem
val为&crc_size
{
u32 size = (u32)resource_size(res);
size += CHECK_CHUNKSIZE-1;//CHECK_CHUNKSIZE编译内核可配
size /= CHECK_CHUNKSIZE;
S3C_PMDBG("Area %08lx..%08lx, %d blocks\n",
(unsigned long)res->start,(unsigned long)res->end, size);
*val += size * sizeof(u32);
return val;
}
可以看出执行完这个递归后会把PCImem的大小统计出来csc_size记录,最终分配crcs =kmalloc(crc_size+4, GFP_KERNEL);
*/
}
ptr = ptr->sibling;
}
}
enter:
芯片资料的话
睡眠模式
此模块与内部电源是分离的。因此这个模式没有因 CPU 和除唤醒逻辑以外的内部逻辑而产生的功耗。激活睡眠模式需要两个独立的供电源。两个电源之一提供电源给唤醒逻辑。另一个提供电源给包括 CPU 在内的其它内部逻辑,而且应当能够控制供电的开和关。在睡眠模式中,第二个为CPU 和内部逻辑供电电源将被关闭。可以由EINT[15:0]或RTC闹铃中断产生从睡眠模式中唤醒。
以下为进入睡眠模式的步骤
1. 为睡眠模式合理设置GPIO配置。
2. 屏蔽 INTMSK 寄存器中所有中断。
3. 合理配置包括RTC 闹钟在内的唤醒源。(不需要屏蔽唤醒源在EINTMASK 中的对应位,目的是使得SRCPND或 EINTPEND 的对应位能置位。然而引发了唤醒源并且屏蔽了 EINTMASK 的对应位,唤醒也将发生但SRCPND 或EINTPEND的对应位将不会被置位。 )//这里有个概念的唤醒源是中断但不产生中断事件(软件上)
4. 设置USB端口为挂起模式(MISCCR[13:12]=11b) 。
5. 保存一些有特殊含义的值到GSTATUS[4:3]寄存器。这些寄存器在睡眠模式期间是被保护的。
6. 为数据总线D[31:0]的上拉电阻配置 MISCCR[1:0]。如果有的外部总线保持器,例如74LVCH162245,关闭上
拉电阻。如果没有开启上拉电阻。另外存储器相关引脚设置为两种类型,另一个是非活动状态。
7. 清除 LCDCON1.ENVID位来停止LCD。
8. 读取 rREFRESH和rCLKCON 寄存器以填充TLB。
9. 设置REFRESH[22]=1b使得SDRAM进入自刷新模式。
10. 等待直到SDRAM自刷新有效。
11. 设置MISCCR[19:17]=111b使得SDRAM信号(SCLK0,SCLK1 和SCKE)在睡眠模式期间受到保护。
12. 设置CLKCON 寄存器中的睡眠模式位。
下面代码会看到这些过程
static int s3c_pm_enter(suspend_state_tstate)
{
/* ensure the debug is initialised (if enabled) */
s3c_pm_debug_init();
S3C_PMDBG("%s(%d)\n", __func__, state);
if (pm_cpu_prep == NULL || pm_cpu_sleep == NULL) {
printk(KERN_ERR "%s:error: no cpu sleep function\n", __func__);
return -EINVAL;
}
/*
pm_cpu_prep和pm_cpu_sleep初始化赋值,两个函数指针
pm_cpu_prep = s3c2410_pm_prepare;
pm_cpu_sleep = s3c2410_cpu_suspend;
*/
/* check if we have anything to wake-up with... bad things seem
* to happen if you suspend with no wakeup (system will often
* require a full power-cycle)
*/
//判断有没有唤醒的中断,不然取消睡眠
if (!any_allowed(s3c_irqwake_intmask, s3c_irqwake_intallow) &&
!any_allowed(s3c_irqwake_eintmask, s3c_irqwake_eintallow)) {
printk(KERN_ERR "%s: Nowake-up sources!\n", __func__);
printk(KERN_ERR "%s:Aborting sleep\n", __func__);
return -EINVAL;
}
/*
#define any_allowed(mask, allow) (((mask) & (allow)) != (allow))
s3c_irqwake_intallow = 1L <<(IRQ_RTC - IRQ_EINT0) | 0xfL;//允许EINT0~EINT15和IRQ_RTC做为唤醒源
s3c_irqwake_intmask =0xffffffffL;
s3c_irqwake_eintallow =0x0000fff0L;//外部中断是否允许
s3c_irqwake_eintmask =0xffffffffL;
*/
/* save all necessary core registers not covered by the drivers */
samsung_pm_save_gpios();
/*
保存gpio相关寄存器
void samsung_pm_save_gpios(void)
{
structsamsung_gpio_chip *ourchip;
unsigned int gpio_nr;
for (gpio_nr = 0;gpio_nr < S3C_GPIO_END;) {//所有的GPIO
ourchip = samsung_gpiolib_getchip(gpio_nr);//这个会根据gpio_nt偏移指向s3c24xx_gpios[]的一个元素,最终寄存器值就存在s3c24xx_gpios中。
if (!ourchip){
gpio_nr++;
continue;
}
samsung_pm_save_gpio(ourchip);
/*
static void samsung_pm_save_gpio(struct samsung_gpio_chip *ourchip)
{
structsamsung_gpio_pm *pm = ourchip->pm;
if (pm == NULL ||pm->save == NULL)
S3C_PMDBG("%s: no pm for %s\n", __func__,ourchip->chip.label);
else
pm->save(ourchip);//保存CON、DAT、UP寄存器
}
*/
S3C_PMDBG("%s: save %08x,%08x,%08x,%08x\n",
ourchip->chip.label,
ourchip->pm_save[0],
ourchip->pm_save[1],
ourchip->pm_save[2],
ourchip->pm_save[3]);
gpio_nr +=ourchip->chip.ngpio;
//ngpio主要是记录控制寄存器的位数,s3c6410就有几个是16位,分con0和con1,pm_save在只有一个con时,[0]为CON,[1]为DAT,[2]为UP;16位CON,那就是[0]为CON0,[1]为CON1,[2]为DAT,[3]为UP。s3c2440只有一个CON
gpio_nr +=CONFIG_S3C_GPIO_SPACE;
}
}
*/
samsung_pm_saved_gpios();
// static inline void samsung_pm_saved_gpios(void) { }
s3c_pm_save_uarts();//不细说,记录ULCON、UCON等寄存器,用uart_save[CONFIG_SERIAL_SAMSUNG_UARTS]存储
s3c_pm_save_core();//存储时钟信息和存储器配置信息
/* set the irq configuration for wake */
s3c_pm_configure_extint();//EINT0~EINT15是否是外部中断源,是的话判断对应口是否为输入,不是就设为输入。
S3C_PMDBG("sleep: irq wakeup masks: %08lx,%08lx\n",
s3c_irqwake_intmask, s3c_irqwake_eintmask);
s3c_pm_arch_prepare_irqs();
/*
static inline void s3c_pm_arch_prepare_irqs(void)
{
__raw_writel(s3c_irqwake_intmask, S3C2410_INTMSK);
__raw_writel(s3c_irqwake_eintmask,S3C2410_EINTMASK);
// 关闭所有中断
/* ack any outstandingexternal interrupts before we go to sleep */
//下面是清楚所有中断标志位
__raw_writel(__raw_readl(S3C2410_EINTPEND), S3C2410_EINTPEND);
__raw_writel(__raw_readl(S3C2410_INTPND), S3C2410_INTPND);
__raw_writel(__raw_readl(S3C2410_SRCPND), S3C2410_SRCPND);
}
*/
/* call cpu specific preparation */
pm_cpu_prep();
/*
pm_cpu_prep就是s3c2410_pm_prepare
static void s3c2410_pm_prepare(void)
{
/* ensure at least GSTATUS3 has the resumeaddress */
__raw_writel(virt_to_phys(s3c_cpu_resume), S3C2410_GSTATUS3);
//这一句太酷了,s3c_cpu_resume是一段汇编代码入口,把这个地址放如信息寄存器中。resume时调用。但我在后面没看到它的调用,要么我的平台不需要,要么我漏掉了
S3C_PMDBG("GSTATUS3 0x%08x\n", __raw_readl(S3C2410_GSTATUS3));
S3C_PMDBG("GSTATUS4 0x%08x\n",__raw_readl(S3C2410_GSTATUS4));
…//下面应该是对不同的平台的我没看到mini2440,就不写了
}
*/
flush_cache_all();
/*
#define flush_cache_all() __cpuc_flush_kern_all()
无条件清楚全部的cache并使其无效
*/
s3c_pm_check_store();
/*
根据之前分配的crcs,先做一些检测然后利用crc32_le()生成校验码
*/
/* send the cpu to sleep... */
s3c_pm_arch_stop_clocks();
/*
关闭所有时钟
static inline void s3c_pm_arch_stop_clocks(void)
{
__raw_writel(0x00,S3C2410_CLKCON); /* turn off clocks oversleep */
}
*/
/* this will also act as our return point from when
* we resume as it saves its own register state and restores it
* during the resume. */
cpu_suspend(0, pm_cpu_sleep);
/*
int cpu_suspend(unsigned long arg, int (*fn)(unsigned long))
{
…
ret =__cpu_suspend(arg, fn);
…
}
SMP和多CPU我用不到,把它去掉了
ENTRY(__cpu_suspend)
stmfd sp!, {r4 - r11, lr}//入栈
ldr r4, =cpu_suspend_size//我的就是.equ cpu_arm920_suspend_size, 4 * 3
mov r5, sp @ current virtual SP //SP存入r5
add r4, r4, #12 @ Space for pgd, virt sp, physresume fn
//给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
//参数入栈r0为0,r1就是pm_cpu_sleep
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
bl __cpu_suspend_save
//这又是void __cpu_suspend_save(u32 *ptr, u32 ptrsz, u32 sp, u32 *save_ptr)
函数参数对应上面r0, r1, r2, r3。这个函数会保存这些信息,不深入了
adr lr, BSYM(cpu_suspend_abort)
ldmfd sp!, {r0, pc} @ call suspend fn
//调用pm_cpu_sleep,pc指针会指向pm_cpu_sleep就是s3c2410_cpu_suspend
ENDPROC(__cpu_suspend)
cpu_suspend_abort:
ldmia sp!, {r1 - r3} @ pop phys pgd, virt SP, phys resumefn
teq r0, #0
moveq r0, #1 @ force non-zero value
mov sp, r2
ldmfd sp!, {r4 - r11, pc}
//pc就是lr就是__cpu_suspend调用的地方,相当于函数返回
ENDPROC(cpu_suspend_abort)
s3c2410_cpu_suspend就是进入睡眠的核心操作
ENTRY(s3c2410_cpu_suspend)
@@ prepare cpu tosleep
ldr r4, =S3C2410_REFRESH
ldr r5, =S3C24XX_MISCCR
ldr r6, =S3C2410_CLKCON
ldr r7, [ r4 ] @ get REFRESH (and ensure in TLB)
ldr r8, [ r5 ] @ getMISCCR (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 @ SDRAMpower-down signals
orr r9, r9, #S3C2410_CLKCON_POWER @ power down command
//经过上面的操作r4\r5\r6代表寄存器REFRESH、MISCCR、CLKCON。R7的22位为1,SDRAM自刷新,R8与7 << 17,SCLK0和SCLK1为0,自刷新保持使能,R9的第三位为1,SLEEP标志为1
teq pc, #0 @ first as a trial-run toload cache
bl s3c2410_do_sleep
teq r0, r0 @ now do it for real
b s3c2410_do_sleep @
@@ align next bit ofcode 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
// 还记得上面的adr lr,BSYM(cpu_suspend_abort)
所以mov pc,14就是调用cpu_suspend_abort。
*/
/* restore the system state */
芯片资料:
以下为从睡眠模式中唤醒的步骤
1. 如果引发了唤醒源之一将发出内部复位信号。它将与触发了外部 nRESET引脚的情况相同。此复位持续时间由内部16位控制逻辑和由tRST = (65535 / XTAL_频率)计算得到的复位触发时间而决定。
2. 检查 GSTATUS2[2]以了解是否是上电使得从睡眠模式中唤醒。
3. 设置 MISCCR[19:17]=000b释放SDRAM信号保护。
4. 配置SDRAM存储器控制器。
5. 等待直到SDRAM自刷新被释放。通常SDRAM需要刷新所有SDRAM行的周期。
6. GSTATUS[3:4]中的信息可以用于用户自己的目的,因为在睡眠模式期间GSTATUS[3:4]中的值是被保护的。
7. –对于EINT[3:0],检查SRCPND寄存器。
–对于 EINT[15:4],检查 EINTPEND而不是 SRCPND(SRCPND将不会被置位尽管 EINTPEND的某些位会
被置位)。
s3c_pm_restore_core();
s3c_pm_restore_uarts();
samsung_pm_restore_gpios();
s3c_pm_restored_gpios();
s3c_pm_debug_init();
/* 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();
//获取crc_size
/* ok, let's return from sleep */
S3C_PMDBG("S3C PM Resume (post-restore)\n");
return 0;
}
最后
finish:
就调用它
void s3c_pm_check_cleanup(void)
{
kfree(crcs);
crcs = NULL;
}