关于电源的一点小问题

问题

发现在执行poweroff之后通过按下 power 按键无法开机。
怀疑是在关机之后没有释放电源管理模块。

PMU数据手册

As with the enable sequence, a typical disable sequence is initiated when the user presses the push-button, which interrupts the processor via the nPBSTAT output. The actual disable sequence is completely software-controlled, but typically involved initiating various “clean-up” processes before the processor finally de-asserts PWRHLD.

也就是说,在执行完毕软件上的关机之后,需要拉低PWRHLD的电平

原理图

在原理图上,PMU上的PWRHLD与CPU的PSHOLD管脚是相连。

CPU数据手册

在CPU的数据手册中的PS_HOLD_CONTROL说明,定义了对PSHOLD的控制方法。

代码

在执行poweroff之后内核输出的最后一行是System halted.。通过在内核中搜索,我发现:
kernel/sys.c

void kernel_halt(void)
{
    kernel_shutdown_prepare(SYSTEM_HALT);
    syscore_shutdown();
    printk(KERN_EMERG "System halted.\n");
    kmsg_dump(KMSG_DUMP_HALT);
    machine_halt();
}

这里有一条语句输出了该信息,紧接着执行了machine_halt()

这个是机器相关的代码,应该在arch/目录下。
紧接着,在arch/arm/kernel/process.c中找到了machine_halt()的实现:

void machine_halt(void)
{
    machine_shutdown();
    while (1);
}

实际上在这里,执行完machine_shutdown()之后,CPU会进入死循环。而CPU的电源并没有被断开。

疑惑

不过我在观察代码上下文的时候发现也有一个kernel_power_off(),这和kernel_halt()有何不同呢?

我查阅了一下在内核中这两者的引用,发现这两者其实都是在reboot的系统调用例程中:

switch (cmd) {
...
case LINUX_REBOOT_CMD_HALT:
    kernel_halt();
    do_exit(0);
    panic("cannot halt");

case LINUX_REBOOT_CMD_POWER_OFF:
    kernel_power_off();
    do_exit(0);
    break;
...
}

也就是说,如果cmdLINUX_REBOOT_CMD_HALT的话,将执行kernel_halt();如果cmdLINUX_REBOOT_CMD_POWER_OFF的话,将执行kernel_power_off()

这两者的区别是什么呢?

reboot系统调用的man手册中,对这两者分别作了说明:

LINUX_REBOOT_CMD_HALT
(RB_HALT_SYSTEM, 0xcdef0123; since Linux 1.1.76).  The message "System halted." is printed, and the system is halted.  Control is given to the ROM monitor, if there is one.  If not preceded by a sync(2), data  will  be lost.
...
LINUX_REBOOT_CMD_POWER_OFF
(RB_POWER_OFF, 0x4321fedc; since Linux 2.1.30).  The message "Power down." is printed, the system is stopped, and all power is removed from the system, if possible.  If not preceded by a sync(2), data will be lost.

但是为什么我这里执行的是kernel_halt()而不是kernel_power_off()呢?
switch (cmd)之前,有:

if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)
                cmd = LINUX_REBOOT_CMD_HALT;    

即:若没有实现pm_power_off(),则使用kernel_halt()而不是kernel_power_off()

arch/arm/kernel/process.cmachine_power_off()代码如下:

void machine_power_off(void)
{
    machine_shutdown();
    if (pm_power_off)
        pm_power_off();
}

machine_halt()的区别在于:

{
    machine_shutdown();
-   while (1);
+   if (pm_power_off)
+           pm_power_off();
}

machine_halt()是让CPU陷入死循环,machine_power_off()则是执行pm_power_off()。而这里pm_power_off()需要实现的功能是让PMU断开CPU的供电。

代码

从上面可以看到,现在的问题所在是针对这块板子的pm_power_off()没有实现。
pm_power_off()所需要执行的工作就是拉低PMU的PWRHLD,即CPU上的PSHOLD。

{
    u32 tmp;
    tmp = __raw_readl(S5P_PMU_PS_HOLD_CONTROL);
    tmp |= BIT(9);
    tmp &= ~BIT(8);
    __raw_writel(tmp, S5P_PMU_PS_HOLD_CONTROL);
}

发表评论

您的电子邮箱地址不会被公开。