s3c2440电源管理主要问题及其解决方法 -- 转

sblpp   2010-11-15 23:35 楼主

最近由于项目需要,得做2440上的电源管理,之前没有做过,所以出现了不少问题,现将过程中出现的主要问题及其解决方法列出如下:

1、系统睡眠与唤醒,拿到普通的代码,出现的问题经常是进入睡眠后,GPIO唤醒总是导致系统重新启动,其实这是因为没有设置CPU的运行模式,而这运行模式是通过设置GPG13

,GPG14,GPG15来进行的。所以就要想唤醒后恢复到睡眠之前的状态,则要在进入睡眠前设置这三个GPIO的模式,可以在arch/arm/plat-s3c24xx/pm.c文件中的s3c2410_pm_enter

()中在进入谁面前,也就是执行__raw_writel(0x00, S3C2410_CLKCON)前加入如下三条语句:
__raw_writel(__raw_readl(S3C2410_EINTPEND), S3C2410_EINTPEND);
__raw_writel(__raw_readl(S3C2410_INTPND), S3C2410_INTPND);
__raw_writel(__raw_readl(S3C2410_SRCPND), S3C2410_SRCPND);
即可使系统恢复正常。
附:
我修改的s3c2410_pm_enter函数如下:
static int s3c2410_pm_enter(suspend_state_t state)
{
unsigned long regs_save[16];
int tmp;
/* ensure the debug is initialised (if enabled) */

s3c2410_pm_debug_init();

DBG("s3c2410_pm_enter(%d)\n", state);
/* our board doesn't support hard disk.*/
if (state != PM_SUSPEND_MEM) {
printk(KERN_ERR PFX "error: only PM_SUSPEND_MEM supported\n");
return -EINVAL;
}

if (pm_cpu_prep == NULL || pm_cpu_sleep == NULL) {
printk(KERN_ERR PFX "error: no cpu sleep functions set\n");
return -EINVAL;
}
/* configure pin GPF4 for wake-up */
// enable_irq_wake(IRQ_EINT4);
// s3c2410_gpio_cfgpin(S3C2410_GPF4,S3C2410_GPF4_EINT4);
// set_irq_type(IRQ_EINT4, IRQT_BOTHEDGE);

tmp = __raw_readl(S3C2410_EINTMASK);
tmp &= ~(1UL<<4);
__raw_writel(tmp,S3C2410_EINTMASK);

s3c2410_gpio_cfgpin(S3C2410_GPG13,S3C2410_GPG13_INP);
s3c2410_gpio_cfgpin(S3C2410_GPG14,S3C2410_GPG14_INP);
s3c2410_gpio_cfgpin(S3C2410_GPG15,S3C2410_GPG15_INP);

/* 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 PFX "No sources enabled for wake-up!\n");
printk(KERN_ERR PFX "Aborting sleep\n");
return -EINVAL;
}

/* prepare check area if configured */

s3c2410_pm_check_prepare();
/* set USB pad as suspend mode , */
__raw_writel(__raw_readl(S3C2410_MISCCR) | (3<<12),S3C2410_MISCCR);

/* store the physical address of the register recovery block */

s3c2410_sleep_save_phys = virt_to_phys(regs_save);

DBG("s3c2410_sleep_save_phys=0x%08lx\n", s3c2410_sleep_save_phys);
/* save resume address */
__raw_writel(virt_to_phys(s3c2410_cpu_resume), S3C2410_GSTATUS3);

/*clear the General Status Registers reg 2*/
__raw_writel(0xff,S3C2410_GSTATUS2);

/* save all necessary core registers not covered by the drivers */
s3c2410_pm_do_save(misc_save, ARRAY_SIZE(misc_save));

s3c2410_pm_do_save(gpio_save, ARRAY_SIZE(gpio_save));
s3c2410_pm_do_save(core_save, ARRAY_SIZE(core_save));
s3c2410_pm_do_save(uart_save, ARRAY_SIZE(uart_save));

/* set the irq configuration for wake */

s3c2410_pm_configure_extint();

DBG("sleep: irq wakeup masks: %08lx,%08lx\n",
s3c_irqwake_intmask, s3c_irqwake_eintmask);

__raw_writel(s3c_irqwake_intmask, S3C2410_INTMSK);
__raw_writel(s3c_irqwake_eintmask, S3C2410_EINTMASK);

/* ack any outstanding external 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();

/* flush cache back to ram */

flush_cache_all();

s3c2410_pm_check_store();

/* send the cpu to sleep... */

__raw_writel(0x00, S3C2410_CLKCON); /* turn off clocks over sleep */

/* s3c2410_cpu_save will also act as our return point from when
* we resume as it saves its own register state, so use the return
* code to differentiate return from save and return from sleep */

if (s3c2410_cpu_save(regs_save) == 0) {
flush_cache_all();
pm_cpu_sleep();
}

/* restore the cpu state */

cpu_init();
/*unset the return-from-sleep flag, to ensure reset */
tmp = __raw_readl(S3C2410_GSTATUS2);
tmp |= S3C2410_GSTATUS2_OFFRESET;
__raw_writel(tmp, S3C2410_GSTATUS2);

/* restore the system state */

s3c2410_pm_do_restore_core(core_save, ARRAY_SIZE(core_save));
s3c2410_pm_do_restore(misc_save, ARRAY_SIZE(misc_save));//
s3c2410_pm_do_restore(gpio_save, ARRAY_SIZE(gpio_save));
s3c2410_pm_do_restore(uart_save, ARRAY_SIZE(uart_save));

s3c2410_pm_debug_init();

/* check what irq (if any) restored the system */

DBG("post sleep: IRQs 0x%08x, 0x%08x\n",
__raw_readl(S3C2410_SRCPND),
__raw_readl(S3C2410_EINTPEND));

s3c2410_pm_show_resume_irqs(IRQ_EINT0, __raw_readl(S3C2410_SRCPND),
s3c_irqwake_intmask);

s3c2410_pm_show_resume_irqs(IRQ_EINT4-4, __raw_readl(S3C2410_EINTPEND),
s3c_irqwake_eintmask);

DBG("post sleep, preparing to return\n");
s3c2410_pm_check_restore();

/* ok, let's return from sleep */
dump_irq_reg();

DBG("S3C2410 PM Resume (post-restore)\n");
return 0;
}

2、在调试的过程中,出现过系统无法进入睡眠的情况。情况大概是串口终端已经进入睡眠了,系统停止了,但是用精密电源去测,发现电流还是没有下降,通过跟踪,才知道系

统逐个调用各个驱动的suspend,按照s3c2410-ts.c原来提供的驱动结构:
static struct platform_driver s3c2410ts_driver = {
.name = "s3c2410-ts",
.bus = &platform_bus_type,
.probe = s3c2410ts_probe,
.remove = s3c2410ts_remove,
};


,系统无法使得触摸品进入睡眠,至于什么原因,不是很理解,将驱动的系统注册接口改为如下:
static struct platform_driver s3c2410ts_driver= {
.probe = s3c2410ts_probe,
.remove = s3c2410ts_remove,
.suspend = s3c2410ts_suspend,
.resume = s3c2410ts_resume,
.driver = {
.name = "s3c2410-ts",
.owner = THIS_MODULE,
},


};

系统就可以进入睡眠了。

3.在调试的过程中,还出现过跟LCD唤醒相关的问题。主要就是两个问题
一个问题是,在唤醒LCD之后,LCD会花屏。原以为拿到的这个代码已经做好了睡眠/唤醒,看了代码才发现,这个代码在睡眠之前,没有保存LCD的设置,在唤醒之后是重新初始化

LCD的控制器,所以会出现花屏的现象,所以自己重新实现了s3c2410fb_suspend和s3c2410fb_resume两个函数,代码如下:
static int s3c2410fb_suspend(struct platform_device *dev, pm_message_t state)
{
struct fb_info *fbinfo = platform_get_drvdata(dev);
struct s3c2410fb_info *info = fbinfo->par;
unsigned long flags;

s3c2410fb_stop_lcd(info);

local_irq_save(flags);
lcdcon1 = readl(info->io + S3C2410_LCDCON1);
lcdcon2 = readl(info->io + S3C2410_LCDCON2);
lcdcon3 = readl(info->io + S3C2410_LCDCON3);
lcdcon4 = readl(info->io + S3C2410_LCDCON4);
lcdcon5 = readl(info->io + S3C2410_LCDCON5);

lcdsaddr1 = readl(info->io + S3C2410_LCDSADDR1);
lcdsaddr2 = readl(info->io + S3C2410_LCDSADDR2);
lcdsaddr3 = readl(info->io + S3C2410_LCDSADDR3);

redlut = readl(info->io + S3C2410_REDLUT);
greenlut = readl(info->io + S3C2410_GREENLUT);
bluelut = readl(info->io + S3C2410_BLUELUT);

dithmode = readl(info->io + S3C2410_DITHMODE);
tpal = readl(info->io + S3C2410_TPAL);
lcdintpnd = readl(info->io + S3C2410_LCDINTPND);
lcdsrcpnd = readl(info->io + S3C2410_LCDSRCPND);
lcdintmsk = readl(info->io + S3C2410_LCDINTMSK);
lpcsel = readl(info->io + S3C2410_LPCSEL);
lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
local_irq_restore(flags);


/* sleep before disabling the clock, we need to ensure
* the LCD DMA engine is not going to get back on the bus
* before the clock goes off again (bjd) */

msleep(1);
clk_disable(info->clk);
/*
* shutdown the LCD power and backlight power
*/
s3c2410_gpio_setpin(S3C2410_GPG12, 0);
s3c2410_gpio_setpin(S3C2410_GPG4, 1);

return 0;
}


static int s3c2410fb_resume(struct platform_device *dev)
{
struct fb_info *fbinfo = platform_get_drvdata(dev);
struct s3c2410fb_info *info = fbinfo->par;
struct s3c2410fb_mach_info *mach_info = info->dev->platform_data;
unsigned long flags;
int i;
printk("=======framebuffer resume.=====\n");
s3c2410_gpio_setpin(S3C2410_GPG4, 0);
msleep(10);
// s3c2410_gpio_setpin(S3C2410_GPG12, 1);
// msleep(10);


clk_enable(info->clk);

msleep(1);
local_irq_save(flags);
writel(lcdcon1,info->io + S3C2410_LCDCON1);
writel(lcdcon2,info->io + S3C2410_LCDCON2);
writel(lcdcon3,info->io + S3C2410_LCDCON3);
writel(lcdcon4,info->io + S3C2410_LCDCON4);
writel(lcdcon5,info->io + S3C2410_LCDCON5);

writel(lcdsaddr1,info->io + S3C2410_LCDSADDR1);
writel(lcdsaddr2 ,info->io + S3C2410_LCDSADDR2);
writel(lcdsaddr3,info->io + S3C2410_LCDSADDR3);

writel(redlut,info->io + S3C2410_REDLUT);
writel(greenlut,info->io + S3C2410_GREENLUT);
writel(bluelut,info->io + S3C2410_BLUELUT);

writel(dithmode,info->io + S3C2410_DITHMODE);
writel(tpal,info->io + S3C2410_TPAL);
writel(lcdintpnd,info->io + S3C2410_LCDINTPND);
writel(lcdsrcpnd,info->io + S3C2410_LCDSRCPND);
writel(lcdintmsk,info->io + S3C2410_LCDINTMSK);
writel(lpcsel,info->io + S3C2410_LPCSEL);


local_irq_restore(flags);
msleep(1);

local_irq_save(flags);

/* modify the gpio(s) with interrupts set (bjd) */

modify_gpio(S3C2410_GPCUP, mach_info->gpcup, mach_info->gpcup_mask);
modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);
modify_gpio(S3C2410_GPDUP, mach_info->gpdup, mach_info->gpdup_mask);
modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);



// s3c2410fb_init_registers(fbinfo);
s3c2410_gpio_cfgpin(S3C2410_GPG12, S3C2410_GPG12_OUTP); // back light control
s3c2410_gpio_cfgpin(S3C2410_GPG4, S3C2410_GPG4_OUTP);
local_irq_restore(flags);

// s3c2410_gpio_setpin(S3C2410_GPG12, 1);
// s3c2410_gpio_setpin(S3C2410_GPG4, 0);

// s3c2440_pwm_set(1);

writel(readl(info->io + S3C2410_LCDCON1) | S3C2410_LCDCON1_ENVID, info->io+S3C2410_LCDCON1);
msleep(50);
s3c2410_gpio_setpin(S3C2410_GPG12, 1);
msleep(10);
return 0;
}
大致意思就是在进入睡眠之前要保存LCD的各个控制器中的设置内容,关闭LCD,关闭背光;在唤醒之后打开LCD,恢复LCD控制的内容,特别注意在最后使能LCD,也就是ritel

(readl(info->io + S3C2410_LCDCON1) | S3C2410_LCDCON1_ENVID, info->io+S3C2410_LCDCON1),否则就会出现LCD显示错位的情况,然后打开背光。

另一个问题是:在唤醒之后LCD无显示,跟踪了一下,发现在进入LCD的suspend之前,framebuffer的数据已经被清零了,这个问题困扰了我很长时间,后来在论坛上看到帖子有人

提供了思路说是在进入睡眠之前系统会切换控制台,所以framebuffer的数据会被清零,根据此思路,修改kernel/power/console.c文件中的pm_prepare_console()
修改如下:
int pm_prepare_console(void)
{
acquire_console_sem();

orig_fgconsole = fg_console;
#if 0

if (vc_allocate(SUSPEND_CONSOLE)) {
/* we can't have a free VC for now. Too bad,
* we don't want to mess the screen for now. */
release_console_sem();
return 1;
}

if (set_console(SUSPEND_CONSOLE)) {
/*
* We're unable to switch to the SUSPEND_CONSOLE.
* Let the calling function know so it can decide
* what to do.
*/
release_console_sem();
return 1;
}
#endif
release_console_sem();
/*
if (vt_waitactive(SUSPEND_CONSOLE)) {
pr_debug("Suspend: Can't switch VCs.");
return 1;
}
*/
orig_kmsg = kmsg_redirect;
kmsg_redirect = SUSPEND_CONSOLE;
return 0;
}
如上修改即可实现唤醒后,LCD上显示原来的图像。


另,可以用应用程序使得系统进入睡眠

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/ioctl.h>
#define APM_IOC_STANDBY _IO('A', 1)
#define APM_IOC_SUSPEND _IO('A', 2)
int main (void)
{
int fd;
//char buf[32];
fd = open ("/dev/apm_bios",O_RDWR);
if (fd < 0) {
printf ("fd open failed\n");
exit(0);
}

if (ioctl (fd, APM_IOC_SUSPEND)<0) {
printf("\nset suspend err\n");
}

close (fd);
return 0;
}


(另,我想说说我对论坛的一些看法,有时候我会进论坛看看,发现论坛里绝大多数的帖子都有这样一个情况,就是最开始是问题,但到最后都没有结贴的,感觉就像是有问题才想到来论坛来求救,而在论坛得到帮助之后,却自顾走人,没有想到要把解决问题的方法贴出来,回馈给论坛,使得很多遇到类似问题的网友在重复地在某个问题上无从下手,我想这是不是我们国人有一种习惯,就是只懂得索取?我想,这是我们国家在技术上落后别人的原因之一吧。

以上看法,谢绝有意见的网友进行攻击,如果斑竹觉得有问题可以把我的帖子删了)

回复评论 (1)

呵呵 sblpp 以来这个论坛就看见你的名字和头像了
自信诚信,天道酬勤!
点赞  2010-11-22 01:57
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复