uboot/drivers/sysreset/sysreset_x86.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
   4 *
   5 * Generic reset driver for x86 processor
   6 */
   7
   8#include <common.h>
   9#include <dm.h>
  10#include <efi_loader.h>
  11#include <pch.h>
  12#include <sysreset.h>
  13#include <acpi/acpi_s3.h>
  14#include <asm/io.h>
  15#include <asm/processor.h>
  16#include <asm/sysreset.h>
  17
  18/*
  19 * Power down the machine by using the power management sleep control
  20 * of the chipset. This will currently only work on Intel chipsets.
  21 * However, adapting it to new chipsets is fairly simple. You will
  22 * have to find the IO address of the power management register block
  23 * in your southbridge, and look up the appropriate SLP_TYP_S5 value
  24 * from your southbridge's data sheet.
  25 *
  26 * This function never returns.
  27 */
  28int pch_sysreset_power_off(struct udevice *dev)
  29{
  30        struct x86_sysreset_plat *plat = dev_get_plat(dev);
  31        struct pch_pmbase_info pm;
  32        u32 reg32;
  33        int ret;
  34
  35        if (!plat->pch)
  36                return -ENOENT;
  37        ret = pch_ioctl(plat->pch, PCH_REQ_PMBASE_INFO, &pm, sizeof(pm));
  38        if (ret)
  39                return ret;
  40
  41        /*
  42         * Mask interrupts or system might stay in a coma, not executing code
  43         * anymore, but not powered off either.
  44         */
  45        asm("cli");
  46
  47        /*
  48         * Avoid any GPI waking the system from S5* or the system might stay in
  49         * a coma
  50         */
  51        outl(0x00000000, pm.base + pm.gpio0_en_ofs);
  52
  53        /* Clear Power Button Status */
  54        outw(PWRBTN_STS, pm.base + pm.pm1_sts_ofs);
  55
  56        /* PMBASE + 4, Bit 10-12, Sleeping Type, * set to 111 -> S5, soft_off */
  57        reg32 = inl(pm.base + pm.pm1_cnt_ofs);
  58
  59        /* Set Sleeping Type to S5 (poweroff) */
  60        reg32 &= ~(SLP_EN | SLP_TYP);
  61        reg32 |= SLP_TYP_S5;
  62        outl(reg32, pm.base + pm.pm1_cnt_ofs);
  63
  64        /* Now set the Sleep Enable bit */
  65        reg32 |= SLP_EN;
  66        outl(reg32, pm.base + pm.pm1_cnt_ofs);
  67
  68        for (;;)
  69                asm("hlt");
  70}
  71
  72static int x86_sysreset_request(struct udevice *dev, enum sysreset_t type)
  73{
  74        int value;
  75        int ret;
  76
  77        switch (type) {
  78        case SYSRESET_WARM:
  79                value = SYS_RST | RST_CPU;
  80                break;
  81        case SYSRESET_COLD:
  82                value = SYS_RST | RST_CPU | FULL_RST;
  83                break;
  84        case SYSRESET_POWER_OFF:
  85                ret = pch_sysreset_power_off(dev);
  86                if (ret)
  87                        return ret;
  88                return -EINPROGRESS;
  89        default:
  90                return -ENOSYS;
  91        }
  92
  93        outb(value, IO_PORT_RESET);
  94
  95        return -EINPROGRESS;
  96}
  97
  98static int x86_sysreset_get_last(struct udevice *dev)
  99{
 100        return SYSRESET_POWER;
 101}
 102
 103#ifdef CONFIG_EFI_LOADER
 104void __efi_runtime EFIAPI efi_reset_system(
 105                        enum efi_reset_type reset_type,
 106                        efi_status_t reset_status,
 107                        unsigned long data_size, void *reset_data)
 108{
 109        int value;
 110
 111        /*
 112         * inline this code since we are not caused in the context of a
 113         * udevice and passing NULL to x86_sysreset_request() is too horrible.
 114         */
 115        if (reset_type == EFI_RESET_COLD ||
 116                 reset_type == EFI_RESET_PLATFORM_SPECIFIC)
 117                value = SYS_RST | RST_CPU | FULL_RST;
 118        else /* assume EFI_RESET_WARM since we cannot return an error */
 119                value = SYS_RST | RST_CPU;
 120        outb(value, IO_PORT_RESET);
 121
 122        /* TODO EFI_RESET_SHUTDOWN */
 123
 124        while (1) { }
 125}
 126#endif
 127
 128static int x86_sysreset_probe(struct udevice *dev)
 129{
 130        struct x86_sysreset_plat *plat = dev_get_plat(dev);
 131
 132        /* Locate the PCH if there is one. It isn't essential */
 133        uclass_first_device(UCLASS_PCH, &plat->pch);
 134
 135        return 0;
 136}
 137
 138static const struct udevice_id x86_sysreset_ids[] = {
 139        { .compatible = "x86,reset" },
 140        { }
 141};
 142
 143static struct sysreset_ops x86_sysreset_ops = {
 144        .request = x86_sysreset_request,
 145        .get_last = x86_sysreset_get_last,
 146};
 147
 148U_BOOT_DRIVER(x86_reset) = {
 149        .name = "x86_reset",
 150        .id = UCLASS_SYSRESET,
 151        .of_match = x86_sysreset_ids,
 152        .ops = &x86_sysreset_ops,
 153        .probe = x86_sysreset_probe,
 154        .plat_auto      = sizeof(struct x86_sysreset_plat),
 155};
 156