最近由于项目需要,得做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;
} |
|