
On modern x86 devices we can power the system off using the power- management features of the PCH. Add an implementation for this.
Signed-off-by: Simon Glass sjg@chromium.org ---
Changes in v2: - Add new patch to implement power-off if available
drivers/sysreset/sysreset_x86.c | 82 +++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+)
diff --git a/drivers/sysreset/sysreset_x86.c b/drivers/sysreset/sysreset_x86.c index d484ec5de49..bd759aa8bf4 100644 --- a/drivers/sysreset/sysreset_x86.c +++ b/drivers/sysreset/sysreset_x86.c @@ -7,14 +7,80 @@
#include <common.h> #include <dm.h> +#include <pch.h> #include <sysreset.h> #include <asm/io.h> #include <asm/processor.h> #include <efi_loader.h>
+struct x86_sysreset_platdata { + struct udevice *pch; +}; + +#define PWRBTN_STS (1 << 8) + +#define SLP_EN (1 << 13) +#define SLP_TYP (7 << 10) +#define SLP_TYP_S5 7 + +/* + * Power down the machine by using the power management sleep control + * of the chipset. This will currently only work on Intel chipsets. + * However, adapting it to new chipsets is fairly simple. You will + * have to find the IO address of the power management register block + * in your southbridge, and look up the appropriate SLP_TYP_S5 value + * from your southbridge's data sheet. + * + * This function never returns. + */ +int pch_sysreset_power_off(struct udevice *dev) +{ + struct x86_sysreset_platdata *plat = dev_get_platdata(dev); + struct pch_pmbase_info pm; + u32 reg32; + int ret; + + if (!plat->pch) + return -ENOENT; + ret = pch_ioctl(plat->pch, PCH_REQ_PMBASE_INFO, &pm, sizeof(pm)); + if (ret) + return ret; + + + /* Mask interrupts or system might stay in a coma + * (not executing code anymore, but not powered off either) + */ + asm("cli"); + + /* + * Avoid any GPI waking the system from S5* or the system might stay in + * a coma + */ + outl(0x00000000, pm.base + pm.gpio0_en_ofs); + + /* Clear Power Button Status */ + outw(PWRBTN_STS, pm.base + pm.pm1_sts_ofs); + + /* PMBASE + 4, Bit 10-12, Sleeping Type, * set to 111 -> S5, soft_off */ + reg32 = inl(pm.base + pm.pm1_cnt_ofs); + + /* Set Sleeping Type to S5 (poweroff) */ + reg32 &= ~(SLP_EN | SLP_TYP); + reg32 |= SLP_TYP_S5; + outl(reg32, pm.base + pm.pm1_cnt_ofs); + + /* Now set the Sleep Enable bit */ + reg32 |= SLP_EN; + outl(reg32, pm.base + pm.pm1_cnt_ofs); + + for (;;) + asm("hlt"); +} + static int x86_sysreset_request(struct udevice *dev, enum sysreset_t type) { int value; + int ret;
switch (type) { case SYSRESET_WARM: @@ -23,6 +89,11 @@ static int x86_sysreset_request(struct udevice *dev, enum sysreset_t type) case SYSRESET_COLD: value = SYS_RST | RST_CPU | FULL_RST; break; + case SYSRESET_POWER_OFF: + ret = pch_sysreset_power_off(dev); + if (ret) + return ret; + return -EINPROGRESS; default: return -ENOSYS; } @@ -57,6 +128,15 @@ void __efi_runtime EFIAPI efi_reset_system( } #endif
+static int x86_sysreset_probe(struct udevice *dev) +{ + struct x86_sysreset_platdata *plat = dev_get_platdata(dev); + + /* Locate the PCH if there is one. It isn't essential */ + uclass_first_device(UCLASS_PCH, &plat->pch); + + return 0; +}
static const struct udevice_id x86_sysreset_ids[] = { { .compatible = "x86,reset" }, @@ -72,4 +152,6 @@ U_BOOT_DRIVER(x86_sysreset) = { .id = UCLASS_SYSRESET, .of_match = x86_sysreset_ids, .ops = &x86_sysreset_ops, + .probe = x86_sysreset_probe, + .platdata_auto_alloc_size = sizeof(struct x86_sysreset_platdata), };