Commit ef6da24d authored by Marc Jones's avatar Marc Jones Committed by chrome-bot
Browse files

amd/fch: Update power driver for AMD FCH



This first implementation is for the Kern FCH (Stoney Ridge) only,
however it is left in a state that it may be easily extended for
other AMD FCH devices.

BRANCH=none
BUG=b:62457067
TEST=Builds clean.

Change-Id: Ia0085c236de56764ba803ac90a0dba058ac455cb
Signed-off-by: default avatarMarshall Dawson <marshalldawson3rd@gmail.com>
Signed-off-by: default avatarMarc Jones <marc.jones@scarletltd.com>
Reviewed-on: https://chromium-review.googlesource.com/474054


Commit-Ready: Martin Roth <martinroth@chromium.org>
Tested-by: default avatarMartin Roth <martinroth@chromium.org>
Reviewed-by: default avatarMartin Roth <martinroth@chromium.org>
parent 487199e3
......@@ -30,6 +30,13 @@ config DRIVER_POWER_PCH
help
Reboot or power off using registers in the Intel chipset.
config DRIVER_POWER_FCH
bool "AMD chipset power management"
default n
depends on ARCH_X86
help
Reboot or power off using registers in the AMD chipset.
config DRIVER_POWER_TPS65913
bool "TPS65913 PMIC power management"
default n
......
......@@ -24,3 +24,4 @@ depthcharge-$(CONFIG_DRIVER_POWER_ARMADA38X) += armada38x.c
depthcharge-y += power.c
depthcharge-y += sysinfo.c
depthcharge-$(CONFIG_ARCH_ARM_V8) += psci.c
depthcharge-$(CONFIG_DRIVER_POWER_FCH) += fch.c
/*
* Copyright 2012 Google Inc.
* Copyright (C) 2017 Advanced Micro Devices, Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
......@@ -17,9 +18,12 @@
#include <libpayload.h>
#include "drivers/power/pch.h"
#include "drivers/power/fch.h"
#include "drivers/power/power.h"
#define ACPI_MMIO_REGION 0xfed80000
#define PMIO_REGS (phys_to_virt(ACPI_MMIO_REGION) + 0x300)
#define PM1_STS 0x00
#define PWRBTN_STS (1 << 8)
#define PM1_EN 0x02
......@@ -28,9 +32,9 @@
#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 SLP_TYP_S3 (3 << 10)
#define SLP_TYP_S4 (4 << 10)
#define SLP_TYP_S5 (5 << 10)
#define RST_CNT 0xcf9
#define SYS_RST (1 << 1)
......@@ -39,12 +43,11 @@
/*
* 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)
* register is available on all x86 systems.
*
* This function never returns.
*/
static int pch_cold_reboot(PowerOps *me)
static int fch_cold_reboot(PowerOps *me)
{
outb(SYS_RST | RST_CPU | FULL_RST, RST_CNT);
halt();
......@@ -55,15 +58,11 @@ struct power_off_args {
pcidev_t pci_dev;
/* Do we need to keep PCI dev for power-mgmt enabled? */
int en_pci_dev;
/* Register address to read pmbase from. */
uint16_t pmbase_reg;
/* BAR for power-mgmt operations. */
uint16_t pmbase;
uint16_t pmbase_mask;
/* Base address for ACPI registers */
uint16_t pm1base;
/* Registers to disable GPE wakes. */
uint16_t gpe_en_reg;
uint16_t gpe_base;
int num_gpe_regs;
int no_config_space;
};
static void busmaster_disable_on_bus(int bus, struct power_off_args *args)
......@@ -108,45 +107,20 @@ static void busmaster_disable(struct power_off_args *args)
/*
* 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.
* of the chipset.
*
* This function never returns.
*/
static int __pch_power_off_common(struct power_off_args *args)
static int fch_power_off_common(struct power_off_args *args)
{
int i;
uint16_t pmbase;
/*
* Make sure this is an Intel chipset with the LPC device hard coded
* at 0:1f.0.
*/
/* Make sure this is an AMD chipset. */
uint16_t id = pci_read_config16(args->pci_dev, 0x00);
if (!args->no_config_space && id != 0x8086) {
if (id != 0x1022) {
printf("Power off is not implemented for this chipset. "
"Halting the CPU.\n");
return -1;
}
/*
* Find the base address of the power-management registers, if not
* already provided by the caller.
*/
pmbase = args->pmbase;
if (!pmbase) {
if (args->no_config_space) {
printf("pmbase needed when no config space present.\n");
return -1;
}
pmbase = pci_read_config16(args->pci_dev, args->pmbase_reg);
pmbase &= args->pmbase_mask;
}
/*
* Mask interrupts or system might stay in a coma (not executing
* code anymore, but not powered off either.
......@@ -160,116 +134,45 @@ static int __pch_power_off_common(struct power_off_args *args)
* Avoid any GPI waking the system from S5 or the system might stay
* in a coma.
*/
for (i = 0; i < args->num_gpe_regs; i++)
outl(0x00000000, pmbase + args->gpe_en_reg +
i * sizeof(uint32_t));
if (args->num_gpe_regs > 1)
printf("Disabling GPI not currently implemented for multiple"
" GPE registers at this time\n");
outl(0x00000000, args->gpe_base + 1);
/* Clear Power Button Status. */
outw(PWRBTN_STS, pmbase + PM1_STS);
outw(PWRBTN_STS, args->pm1base + PM1_STS);
/* PMBASE + 4, Bit 10-12, Sleeping Type, set to 111 -> S5, soft_off */
uint32_t reg32 = inl(pmbase + PM1_CNT);
uint32_t reg32 = inl(args->pm1base + PM1_CNT);
/* Set Sleeping Type to S5 (poweroff). */
reg32 &= ~(SLP_EN | SLP_TYP);
reg32 |= SLP_TYP_S5;
outl(reg32, pmbase + PM1_CNT);
outl(reg32, args->pm1base + PM1_CNT);
/* Now set the Sleep Enable bit. */
reg32 |= SLP_EN;
outl(reg32, pmbase + PM1_CNT);
outl(reg32, args->pm1base + PM1_CNT);
halt();
}
static int pch_power_off_common(uint16_t bar_mask, uint16_t gpe_en_reg)
static int kern_power_off(PowerOps *me)
{
uint8_t *pmregs = (uint8_t *)PMIO_REGS;
struct power_off_args args;
memset(&args, 0, sizeof(args));
args.pci_dev = PCI_DEV(0, 0x1f, 0);
args.pmbase_reg = 0x40;
args.pmbase_mask = bar_mask;
args.gpe_en_reg = gpe_en_reg;
args.pci_dev = PCI_DEV(0, 0x14, 3);
args.pm1base = (uint16_t)*(uint16_t *)(pmregs + 0x60);
args.gpe_base = (uint16_t)*(uint16_t *)(pmregs + 0x68);
args.num_gpe_regs = 1;
return __pch_power_off_common(&args);
}
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.
*/
struct power_off_args args;
memset(&args, 0, sizeof(args));
args.pci_dev = PCI_DEV(0, 0x1f, 2);
args.pmbase_reg = 0x40;
args.pmbase_mask = 0xfff0;
args.gpe_en_reg = 0x90;
args.num_gpe_regs = 4;
return __pch_power_off_common(&args);
}
static int apollolake_power_off(PowerOps *me)
{
struct power_off_args args;
memset(&args, 0, sizeof(args));
args.pci_dev = PCI_DEV(0, 0xd, 1);
args.en_pci_dev = 1;
/*
* This is hard-coded because reading BAR4 from PMC dev does not work
* correctly. It simply returns 0.
*/
args.pmbase = 0x400;
args.gpe_en_reg = 0x30;
args.num_gpe_regs = 4;
/* Check if the config space is present. */
args.no_config_space = pci_read_config16(args.pci_dev, 0x00) == 0xffff;
return __pch_power_off_common(&args);
return fch_power_off_common(&args);
}
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
};
PowerOps apollolake_power_ops = {
.cold_reboot = &pch_cold_reboot,
.power_off = &apollolake_power_off
PowerOps kern_power_ops = {
.cold_reboot = &fch_cold_reboot,
.power_off = &kern_power_off
};
/*
* Copyright 2012 Google Inc.
* Copyright (C) 2017 Advanced Micro Devices, Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
......@@ -20,10 +21,6 @@
#include "drivers/power/power.h"
PowerOps pch_power_ops;
PowerOps baytrail_power_ops;
PowerOps braswell_power_ops;
PowerOps skylake_power_ops;
PowerOps apollolake_power_ops;
PowerOps kern_power_ops;
#endif /* __DRIVERS_POWER_PCH_H__ */
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment