blob: 03533a3c06fe53b3113edfc1220fbb1b4fafac16 [file] [log] [blame]
/*
* 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
};