| /* |
| * Copyright 2012 Google Inc. |
| * |
| * See file CREDITS for list of people who contributed to this |
| * project. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but without any warranty; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
| * MA 02111-1307 USA |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include "base/io.h" |
| #include "drivers/bus/pci/pci.h" |
| #include "drivers/power/pch.h" |
| #include "drivers/power/power.h" |
| |
| #define PM1_STS 0x00 |
| #define PWRBTN_STS (1 << 8) |
| #define PM1_EN 0x02 |
| #define PM1_CNT 0x04 |
| #define SLP_EN (1 << 13) |
| #define SLP_TYP (7 << 10) |
| #define SLP_TYP_S0 (0 << 10) |
| #define SLP_TYP_S1 (1 << 10) |
| #define SLP_TYP_S3 (5 << 10) |
| #define SLP_TYP_S4 (6 << 10) |
| #define SLP_TYP_S5 (7 << 10) |
| |
| #define RST_CNT 0xcf9 |
| #define SYS_RST (1 << 1) |
| #define RST_CPU (1 << 2) |
| #define FULL_RST (1 << 3) |
| |
| /* |
| * Do a hard reset through the chipset's reset control register. This |
| * register is available on all x86 systems (at least those built in |
| * the last 10ys) |
| * |
| * This function never returns. |
| */ |
| static int pch_cold_reboot(PowerOps *me) |
| { |
| outb(SYS_RST | RST_CPU | FULL_RST, RST_CNT); |
| halt(); |
| } |
| |
| static void busmaster_disable_on_bus(int bus) |
| { |
| for (int slot = 0; slot < 0x20; slot++) { |
| for (int func = 0; func < 8; func++) { |
| pcidev_t dev = PCI_DEV(bus, slot, func); |
| uint32_t ids = pci_read_config32(dev, PciConfVendorId); |
| |
| if (ids == 0xffffffff || ids == 0x00000000 || |
| ids == 0x0000ffff || ids == 0xffff0000) |
| continue; |
| |
| // Disable Bus Mastering for this one device. |
| uint32_t cmd = pci_read_config32(dev, PciConfCommand); |
| cmd &= ~PciConfCommandBm; |
| pci_write_config32(dev, PciConfCommand, cmd); |
| |
| // If this is a bridge the follow it. |
| uint8_t hdr = pci_read_config8(dev, PciConfHeaderType); |
| hdr &= 0x7f; |
| if (hdr == PciConfHeaderTypeBridge || |
| hdr == PciConfHeaderTypeCardbus) { |
| uint32_t busses; |
| busses = pci_read_config32( |
| dev, PciConfPrimaryBus); |
| busmaster_disable_on_bus((busses >> 8) & 0xff); |
| } |
| } |
| } |
| } |
| |
| static void busmaster_disable(void) |
| { |
| busmaster_disable_on_bus(0); |
| } |
| |
| /* |
| * 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. |
| */ |
| static int pch_power_off_full_args(pcidev_t pci_dev, int pmbase_reg, |
| uint16_t bar_mask, uint16_t gpe_en_reg, |
| int num_gpe_regs) |
| { |
| int i; |
| |
| // Make sure this is an Intel chipset with the LPC device hard coded |
| // at 0:1f.0. |
| uint16_t id = pci_read_config16(pci_dev, 0x00); |
| if (id != 0x8086) { |
| printf("Power off is not implemented for this chipset. " |
| "Halting the CPU.\n"); |
| return -1; |
| } |
| |
| // Find the base address of the powermanagement registers. |
| uint16_t pmbase = pci_read_config16(pci_dev, pmbase_reg); |
| pmbase &= bar_mask; |
| |
| // Mask interrupts or system might stay in a coma (not executing |
| // code anymore, but not powered off either. |
| asm volatile ("cli" ::: "memory"); |
| |
| // Turn off all bus master enable bits. |
| busmaster_disable(); |
| |
| // Avoid any GPI waking the system from S5 or the system might stay |
| // in a coma. |
| for (i = 0; i < num_gpe_regs; i++) |
| outl(0x00000000, pmbase + gpe_en_reg + i * sizeof(uint32_t)); |
| |
| // Clear Power Button Status. |
| outw(PWRBTN_STS, pmbase + PM1_STS); |
| |
| // PMBASE + 4, Bit 10-12, Sleeping Type, set to 111 -> S5, soft_off |
| uint32_t reg32 = inl(pmbase + PM1_CNT); |
| |
| // Set Sleeping Type to S5 (poweroff). |
| reg32 &= ~(SLP_EN | SLP_TYP); |
| reg32 |= SLP_TYP_S5; |
| outl(reg32, pmbase + PM1_CNT); |
| |
| // Now set the Sleep Enable bit. |
| reg32 |= SLP_EN; |
| outl(reg32, pmbase + PM1_CNT); |
| |
| halt(); |
| } |
| |
| static int pch_power_off_common(uint16_t bar_mask, uint16_t gpe_en_reg) |
| { |
| return pch_power_off_full_args(PCI_DEV(0, 0x1f, 0), 0x40, |
| bar_mask, gpe_en_reg, 1); |
| } |
| |
| static int pch_power_off(PowerOps *me) |
| { |
| return pch_power_off_common(0xfffe, 0x2c); |
| } |
| |
| static int baytrail_power_off(PowerOps *me) |
| { |
| return pch_power_off_common(0xfff8, 0x28); |
| } |
| |
| static int braswell_power_off(PowerOps *me) |
| { |
| return pch_power_off_common(0xfff8, 0x28); |
| } |
| |
| static int skylake_power_off(PowerOps *me) |
| { |
| // Skylake has 4 GPE en registers and the bar lives within the |
| // PMC device at 0:1f.2. |
| return pch_power_off_full_args(PCI_DEV(0, 0x1f, 2), 0x40, |
| 0xfff0, 0x90, 4); |
| } |
| |
| PowerOps pch_power_ops = { |
| .cold_reboot = &pch_cold_reboot, |
| .power_off = &pch_power_off |
| }; |
| |
| PowerOps baytrail_power_ops = { |
| .cold_reboot = &pch_cold_reboot, |
| .power_off = &baytrail_power_off |
| }; |
| |
| PowerOps braswell_power_ops = { |
| .cold_reboot = &pch_cold_reboot, |
| .power_off = &braswell_power_off |
| }; |
| |
| PowerOps skylake_power_ops = { |
| .cold_reboot = &pch_cold_reboot, |
| .power_off = &skylake_power_off |
| }; |