]>
Commit | Line | Data |
---|---|---|
fabb2b4c BM |
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 | ||
d678a59d | 8 | #include <common.h> |
fabb2b4c | 9 | #include <dm.h> |
d6b1ba2f SG |
10 | #include <efi_loader.h> |
11 | #include <pch.h> | |
fabb2b4c | 12 | #include <sysreset.h> |
3cabcf96 | 13 | #include <acpi/acpi_s3.h> |
fabb2b4c BM |
14 | #include <asm/io.h> |
15 | #include <asm/processor.h> | |
6a2350f8 | 16 | #include <asm/sysreset.h> |
d6b1ba2f SG |
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 | */ | |
28 | int pch_sysreset_power_off(struct udevice *dev) | |
29 | { | |
8a8d24bd | 30 | struct x86_sysreset_plat *plat = dev_get_plat(dev); |
d6b1ba2f SG |
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 | } | |
fabb2b4c | 71 | |
40476f4a | 72 | static int x86_sysreset_request(struct udevice *dev, enum sysreset_t type) |
fabb2b4c BM |
73 | { |
74 | int value; | |
d6b1ba2f | 75 | int ret; |
fabb2b4c BM |
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; | |
d6b1ba2f SG |
84 | case SYSRESET_POWER_OFF: |
85 | ret = pch_sysreset_power_off(dev); | |
86 | if (ret) | |
87 | return ret; | |
88 | return -EINPROGRESS; | |
fabb2b4c | 89 | default: |
da35ab68 | 90 | return -EPROTONOSUPPORT; |
fabb2b4c BM |
91 | } |
92 | ||
93 | outb(value, IO_PORT_RESET); | |
94 | ||
95 | return -EINPROGRESS; | |
96 | } | |
97 | ||
f77ab00e SG |
98 | static int x86_sysreset_get_last(struct udevice *dev) |
99 | { | |
100 | return SYSRESET_POWER; | |
101 | } | |
102 | ||
3eeb09b4 AG |
103 | #ifdef CONFIG_EFI_LOADER |
104 | void __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 | { | |
40476f4a SG |
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 | */ | |
3eeb09b4 AG |
115 | if (reset_type == EFI_RESET_COLD || |
116 | reset_type == EFI_RESET_PLATFORM_SPECIFIC) | |
40476f4a SG |
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); | |
3eeb09b4 AG |
121 | |
122 | /* TODO EFI_RESET_SHUTDOWN */ | |
123 | ||
124 | while (1) { } | |
125 | } | |
126 | #endif | |
127 | ||
d6b1ba2f SG |
128 | static int x86_sysreset_probe(struct udevice *dev) |
129 | { | |
8a8d24bd | 130 | struct x86_sysreset_plat *plat = dev_get_plat(dev); |
d6b1ba2f | 131 | |
6624392d SG |
132 | /* |
133 | * Locate the PCH if there is one. It isn't essential. Avoid this before | |
134 | * relocation as we shouldn't need reset then and it needs a lot of | |
135 | * memory for PCI enumeration. | |
136 | */ | |
137 | if (gd->flags & GD_FLG_RELOC) | |
138 | uclass_first_device(UCLASS_PCH, &plat->pch); | |
d6b1ba2f SG |
139 | |
140 | return 0; | |
141 | } | |
3eeb09b4 | 142 | |
fabb2b4c BM |
143 | static const struct udevice_id x86_sysreset_ids[] = { |
144 | { .compatible = "x86,reset" }, | |
145 | { } | |
146 | }; | |
147 | ||
148 | static struct sysreset_ops x86_sysreset_ops = { | |
149 | .request = x86_sysreset_request, | |
f77ab00e | 150 | .get_last = x86_sysreset_get_last, |
fabb2b4c BM |
151 | }; |
152 | ||
9d20db04 SG |
153 | U_BOOT_DRIVER(x86_reset) = { |
154 | .name = "x86_reset", | |
fabb2b4c BM |
155 | .id = UCLASS_SYSRESET, |
156 | .of_match = x86_sysreset_ids, | |
157 | .ops = &x86_sysreset_ops, | |
d6b1ba2f | 158 | .probe = x86_sysreset_probe, |
8a8d24bd | 159 | .plat_auto = sizeof(struct x86_sysreset_plat), |
fabb2b4c | 160 | }; |