Duncan Laurie has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/46260 )
Change subject: soc/intel/common: Add PCIe Runtime D3 driver for ACPI ......................................................................
soc/intel/common: Add PCIe Runtime D3 driver for ACPI
This driver is for devices attached to a PCIe root port that support Runtime D3. It creates the necessary PowerResource in the root port to provide _ON/_OFF methods for which will turn off power and clocks to the device when it is in the D3cold state.
The mainboard declares the driver in devicetree and provides the GPIOs that control power/reset for the device attached to the root port and the SRCCLK pin used for the PMC IPC mailbox to enable/disable the clock.
An additional device property is created for storage devices if it matches the PCI storage class which is used to indicate that the storage device should use D3 for power savings.
BUG=b:160996445 TEST=boot on volteer device with this driver enabled in the devicetree and disassemble the SSDT to ensure this code exists.
Signed-off-by: Duncan Laurie dlaurie@google.com Change-Id: I13e59c996b4f5e4c2657694bda9fad869b64ffde --- M src/soc/intel/common/block/pcie/Kconfig M src/soc/intel/common/block/pcie/Makefile.inc A src/soc/intel/common/block/pcie/rtd3/Kconfig A src/soc/intel/common/block/pcie/rtd3/Makefile.inc A src/soc/intel/common/block/pcie/rtd3/chip.h A src/soc/intel/common/block/pcie/rtd3/rtd3.c 6 files changed, 306 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/60/46260/1
diff --git a/src/soc/intel/common/block/pcie/Kconfig b/src/soc/intel/common/block/pcie/Kconfig index 9c42af6..25cde37 100644 --- a/src/soc/intel/common/block/pcie/Kconfig +++ b/src/soc/intel/common/block/pcie/Kconfig @@ -7,6 +7,8 @@
if SOC_INTEL_COMMON_BLOCK_PCIE
+source "src/soc/intel/common/block/pcie/*/Kconfig" + config PCIEXP_CLK_PM default y
diff --git a/src/soc/intel/common/block/pcie/Makefile.inc b/src/soc/intel/common/block/pcie/Makefile.inc index 0cded9d..e2ad685 100644 --- a/src/soc/intel/common/block/pcie/Makefile.inc +++ b/src/soc/intel/common/block/pcie/Makefile.inc @@ -1,2 +1,4 @@ +subdirs-y += ./* + ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_PCIE) += pcie.c ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_PCIE) += pcie_rp.c diff --git a/src/soc/intel/common/block/pcie/rtd3/Kconfig b/src/soc/intel/common/block/pcie/rtd3/Kconfig new file mode 100644 index 0000000..03e13da --- /dev/null +++ b/src/soc/intel/common/block/pcie/rtd3/Kconfig @@ -0,0 +1,9 @@ +config SOC_INTEL_COMMON_BLOCK_PCIE_RTD3 + bool + default n + depends on HAVE_ACPI_TABLES + select PMC_IPC_MAILBOX_ACPI_INTERFACE + help + When enabled, this driver will add support for ACPI controlled + Runtime D3 using GPIOs for power/reset control of the device + attached to a PCIe root port. diff --git a/src/soc/intel/common/block/pcie/rtd3/Makefile.inc b/src/soc/intel/common/block/pcie/rtd3/Makefile.inc new file mode 100644 index 0000000..7edea97 --- /dev/null +++ b/src/soc/intel/common/block/pcie/rtd3/Makefile.inc @@ -0,0 +1 @@ +ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_PCIE_RTD3) += rtd3.c diff --git a/src/soc/intel/common/block/pcie/rtd3/chip.h b/src/soc/intel/common/block/pcie/rtd3/chip.h new file mode 100644 index 0000000..062ce30 --- /dev/null +++ b/src/soc/intel/common/block/pcie/rtd3/chip.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOC_INTEL_COMMON_BLOCK_PCIE_RTD3_CHIP_H__ +#define __SOC_INTEL_COMMON_BLOCK_PCIE_RTD3_CHIP_H__ + +#include <acpi/acpi_device.h> + +struct soc_intel_common_block_pcie_rtd3_config { + struct acpi_gpio power_gpio; /* Power GPIO for this device */ + struct acpi_gpio reset_gpio; /* Reset GPIO for this device (optional) */ + unsigned int clock_pin; /* SRCCLK assigned to this root port */ +}; + +#endif /* __SOC_INTEL_COMMON_BLOCK_PCIE_RTD3_CHIP_H__ */ diff --git a/src/soc/intel/common/block/pcie/rtd3/rtd3.c b/src/soc/intel/common/block/pcie/rtd3/rtd3.c new file mode 100644 index 0000000..0331e95 --- /dev/null +++ b/src/soc/intel/common/block/pcie/rtd3/rtd3.c @@ -0,0 +1,278 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <acpi/acpigen.h> +#include <acpi/acpi_device.h> +#include <console/console.h> +#include <device/device.h> +#include <device/pci_ids.h> +#include <device/pci_ops.h> +#include <device/pci.h> +#include <intelblocks/pmc.h> + +#include "chip.h" + +/* + * This UUID and the resulting ACPI Device Property is defined by the + * Power Management for Storage Hardware Devices: + * + * https://docs.microsoft.com/en-us/windows-hardware/design/component-guideline... + */ +#define PCIE_RTD3_STORAGE_UUID "5025030F-842F-4AB4-A561-99A5189762D0" + +/* Address for start of PCIe port control and status registers. */ +#define PCIE_PXCS_ADDRESS (CONFIG_MMCONF_BASE_ADDRESS + 0xe8000) + +#define PCH_PCIE_PXCS_CFG_LSTS 0x52 /* Link Status Register */ +#define PCH_PCIE_PXCS_CFG_SPR 0xe0 /* Scratchpad */ +#define PCH_PCIE_PXCS_CFG_RPPGEN 0xe2 /* Root Port Power Gating Enable */ +#define PCH_PCIE_CFG_LCAP_PN 0x4f /* Root Port Number */ + +/* + * Delay up to wait_ms until provided register matches expected value. + * + * Local7 = segments + * While (Local7 > 0) + * { + * If (name == val) { + * Break + * } + * Sleep (wait_ms_segment) + * Local7++ + * } + */ +static void pcie_rtd3_delay(uint32_t wait_ms, const char *name, uint64_t val) +{ + uint32_t wait_ms_segment = 1; + uint32_t segments = wait_ms; + + if (wait_ms > 32) { + wait_ms_segment = 16; + segments = wait_ms / 16; + } + + acpigen_write_store_int_to_op(segments, LOCAL7_OP); + acpigen_emit_byte(WHILE_OP); + acpigen_write_len_f(); + acpigen_emit_byte(LGREATER_OP); + acpigen_emit_byte(LOCAL7_OP); + acpigen_emit_byte(ZERO_OP); + if (name) { + acpigen_write_if_lequal_namestr_int(name, val); + acpigen_emit_byte(BREAK_OP); + acpigen_pop_len(); /* If */ + } + acpigen_write_sleep(wait_ms_segment); + acpigen_emit_byte(DECREMENT_OP); + acpigen_emit_byte(LOCAL7_OP); + acpigen_pop_len(); /* While */ +} + +/* Called from _ON to get PCIe link back to active state. */ +static void pcie_rtd3_l23_exit(void) +{ + /* Skip if port is not in L2/L3. */ + acpigen_write_if_lequal_namestr_int("NCB7", 0); + acpigen_write_return_op(ZERO_OP); + acpigen_pop_len(); + + /* Initiate L2/L3 Ready To Detect transition. */ + acpigen_write_store_int_to_namestr(1, "L23R"); + + /* Wait for transition to detect. */ + pcie_rtd3_delay(320, "L23R", 0); + + /* Once in detect, wait for link active. */ + pcie_rtd3_delay(128, "LASX", 1); + + /* Track state of port if link is active. */ + acpigen_write_if_lequal_namestr_int("LASX", 1); + acpigen_write_store_int_to_namestr(0, "NCB7"); + acpigen_pop_len(); +} + +/* Called from _OFF to put PCIe link into L2/L3 state. */ +static void pcie_rtd3_l23_entry(void) +{ + /* Initiate L2/L3 Entry request. */ + acpigen_write_store_int_to_namestr(1, "L23E"); + + /* Wait for L2/L3 Entry request to clear. */ + pcie_rtd3_delay(128, "L23E", 0); + + /* Track state of port if L2/L3 entry request is clear. */ + acpigen_write_if_lequal_namestr_int("L23E", 0); + acpigen_write_store_int_to_namestr(1, "NCB7"); + acpigen_pop_len(); +} + +static void pcie_rtd3_method_on(unsigned int pcie_rp, unsigned int clock_pin, + const struct acpi_gpio *power_gpio, + const struct acpi_gpio *reset_gpio) +{ + acpigen_write_method_serialized("_ON", 0); + + /* Assert power gpio. */ + acpigen_enable_tx_gpio(power_gpio); + + /* Enable CLKSRC for root port. */ + pmc_acpi_set_pci_clock(pcie_rp, clock_pin, true); + + /* De-assert reset gpio. */ + if (reset_gpio->pin_count) + acpigen_disable_tx_gpio(reset_gpio); + + /* Trigger L3/S3 ready exit flow. */ + pcie_rtd3_l23_exit(); + + acpigen_pop_len(); +} + +static void pcie_rtd3_method_off(int pcie_rp, int clock_pin, + const struct acpi_gpio *power_gpio, + const struct acpi_gpio *reset_gpio) +{ + acpigen_write_method_serialized("_OFF", 0); + + /* Trigger L2/L3 ready entry flow. */ + pcie_rtd3_l23_entry(); + + /* Assert reset gpio. */ + if (reset_gpio->pin_count) + acpigen_enable_tx_gpio(reset_gpio); + + /* Disable CLKSRC for this root port. */ + pmc_acpi_set_pci_clock(pcie_rp, clock_pin, false); + + /* De-assert power gpio. */ + acpigen_disable_tx_gpio(power_gpio); + + acpigen_pop_len(); +} + +static void pcie_rtd3_method_status(const struct acpi_gpio *power_gpio) +{ + acpigen_write_method("_STA", 0); + + /* Read power gpio value into Local0. */ + acpigen_get_tx_gpio(power_gpio); + + acpigen_write_if_lequal_op_op(LOCAL0_OP, ZERO_OP); + acpigen_write_return_op(ZERO_OP); + acpigen_pop_len(); + acpigen_write_else(); + acpigen_write_return_op(ONE_OP); + acpigen_pop_len(); + + acpigen_pop_len(); +} + +static void pcie_rtd3_fill_ssdt(const struct device *dev) +{ + const struct soc_intel_common_block_pcie_rtd3_config *config = config_of(dev); + static const char * const power_res_states[] = { "_PR0", "_PR3" }; + const char *scope = acpi_device_scope(dev); + const struct opregion opregion = OPREGION("PXCS", SYSTEMMEMORY, PCIE_PXCS_ADDRESS, 0xff); + const struct fieldlist fieldlist[] = { + FIELDLIST_OFFSET(PCH_PCIE_PXCS_CFG_LSTS), + FIELDLIST_RESERVED(13), + FIELDLIST_NAMESTR("LASX", 1), /* Link Active Status */ + FIELDLIST_OFFSET(PCH_PCIE_PXCS_CFG_SPR), + FIELDLIST_RESERVED(7), + FIELDLIST_NAMESTR("NCB7", 1), + FIELDLIST_OFFSET(PCH_PCIE_PXCS_CFG_RPPGEN), + FIELDLIST_RESERVED(2), + FIELDLIST_NAMESTR("L23E", 1), /* L23_Rdy Entry Request */ + FIELDLIST_NAMESTR("L23R", 1), /* L23_Rdy Detect Transition */ + }; + uint8_t pcie_rp; + + if (!scope) { + printk(BIOS_ERR, "%s: scope not found: %s\n", __func__, dev_path(dev)); + return; + } + if (!config->power_gpio.pin_count) { + printk(BIOS_ERR, "%s: power gpio not provided for %s.\n", __func__, scope); + return; + } + if (config->clock_pin > CONFIG_MAX_PCIE_CLOCKS) { + printk(BIOS_ERR, "%s: Invalid clock pin %u for %s.\n", __func__, + config->clock_pin, scope); + return; + } + + /* Read port number of root port that this device is attached to. */ + pcie_rp = pci_read_config8(dev->bus->dev, PCH_PCIE_CFG_LCAP_PN); + if (pcie_rp == 0 || pcie_rp > CONFIG_MAX_ROOT_PORTS) { + printk(BIOS_ERR, "%s: Invalid root port number: %u\n", __func__, pcie_rp); + return; + } + /* Port number is 1-based, PMC IPC method expects 0-based. */ + pcie_rp--; + + printk(BIOS_INFO, "%s: Enable RTD3 for %s\n", scope, dev_path(dev->bus->dev)); + + /* The RTD3 power resource is added to the root port, not the device. */ + acpigen_write_scope(scope); + + acpigen_write_opregion(&opregion); + acpigen_write_field("PXCS", fieldlist, ARRAY_SIZE(fieldlist), + FIELD_ANYACC | FIELD_NOLOCK | FIELD_PRESERVE); + + /* Power resources */ + acpigen_write_power_res("PRIC", 0, 0, power_res_states, ARRAY_SIZE(power_res_states)); + + pcie_rtd3_method_status(&config->power_gpio); + pcie_rtd3_method_on(pcie_rp, config->clock_pin, &config->power_gpio, + &config->reset_gpio); + pcie_rtd3_method_off(pcie_rp, config->clock_pin, &config->power_gpio, + &config->reset_gpio); + + acpigen_pop_len(); /* PowerResource */ + + /* Add property for OS to enable storage D3. */ + if ((dev->class >> 16) == PCI_BASE_CLASS_STORAGE) { + struct acpi_dp *dsd, *pkg; + + acpigen_write_device(acpi_device_name(dev)); + acpigen_write_ADR(0); + acpigen_write_STA(ACPI_STATUS_DEVICE_ALL_ON); + acpigen_write_name_integer("_S0W", 4); + + dsd = acpi_dp_new_table("_DSD"); + pkg = acpi_dp_new_table(PCIE_RTD3_STORAGE_UUID); + acpi_dp_add_integer(pkg, "StorageD3Enable", 1); + acpi_dp_add_package(dsd, pkg); + acpi_dp_write(dsd); + + acpigen_pop_len(); /* Device */ + + printk(BIOS_INFO, "%s: Added StorageD3Enable property\n", + acpi_device_path(dev)); + } + + acpigen_pop_len(); /* Scope */ +} + +static const char *pcie_rtd3_acpi_name(const struct device *dev) +{ + return "RTD3"; +} + +static struct device_operations pcie_rtd3_ops = { + .read_resources = pci_dev_read_resources, + .set_resources = pci_dev_set_resources, + .enable_resources = pci_dev_enable_resources, + .acpi_fill_ssdt = pcie_rtd3_fill_ssdt, + .acpi_name = pcie_rtd3_acpi_name, + .ops_pci = &pci_dev_ops_pci, +}; + +static void pcie_rtd3_enable(struct device *dev) +{ + dev->ops = &pcie_rtd3_ops; +} + +struct chip_operations soc_intel_common_block_pcie_rtd3_ops = { + CHIP_NAME("Intel PCIe Runtime D3") + .enable_dev = pcie_rtd3_enable +};