summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDuncan Laurie <dlaurie@google.com>2020-10-10 00:15:28 +0000
committerDuncan Laurie <dlaurie@chromium.org>2020-11-20 00:24:11 +0000
commit64bc26ad1553eec6bbbd6deac21a3e79ca7ce455 (patch)
tree3b8b66a3b3abfbe6e0f26f8d37ea85c44943b24f
parent05c732b9e4c6cac921416d26e9e4febdc63d5772 (diff)
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 Reviewed-on: https://review.coreboot.org/c/coreboot/+/46260 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Furquan Shaikh <furquan@google.com>
-rw-r--r--src/soc/intel/common/block/pcie/Kconfig2
-rw-r--r--src/soc/intel/common/block/pcie/Makefile.inc2
-rw-r--r--src/soc/intel/common/block/pcie/rtd3/Kconfig9
-rw-r--r--src/soc/intel/common/block/pcie/rtd3/Makefile.inc1
-rw-r--r--src/soc/intel/common/block/pcie/rtd3/chip.h50
-rw-r--r--src/soc/intel/common/block/pcie/rtd3/rtd3.c299
6 files changed, 363 insertions, 0 deletions
diff --git a/src/soc/intel/common/block/pcie/Kconfig b/src/soc/intel/common/block/pcie/Kconfig
index 9c42af6c25..25cde37d1a 100644
--- a/src/soc/intel/common/block/pcie/Kconfig
+++ b/src/soc/intel/common/block/pcie/Kconfig
@@ -7,6 +7,8 @@ config SOC_INTEL_COMMON_BLOCK_PCIE
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 0cded9dce7..e2ad685bc3 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 0000000000..7c21ce8ced
--- /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_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 0000000000..7edea97bd4
--- /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 0000000000..15b8f64891
--- /dev/null
+++ b/src/soc/intel/common/block/pcie/rtd3/chip.h
@@ -0,0 +1,50 @@
+/* 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>
+
+/* Device support at least one of enable/reset GPIO. */
+struct soc_intel_common_block_pcie_rtd3_config {
+ const char *desc;
+
+ /* GPIO used to enable device. */
+ struct acpi_gpio enable_gpio;
+ /* Delay to be inserted after device is enabled. */
+ unsigned int enable_delay_ms;
+ /* Delay to be inserted after device is disabled. */
+ unsigned int enable_off_delay_ms;
+
+ /* GPIO used to take device out of reset or to put it into reset. */
+ struct acpi_gpio reset_gpio;
+ /* Delay to be inserted after device is taken out of reset. */
+ unsigned int reset_delay_ms;
+ /* Delay to be inserted after device is put into reset. */
+ unsigned int reset_off_delay_ms;
+
+ /*
+ * SRCCLK assigned to this root port which will be turned off via PMC IPC.
+ * If set to -1 then the clock will not be disabled in D3.
+ */
+ int srcclk_pin;
+
+ /*
+ * Add device property indicating the device provides an external PCI port
+ * for the OS to apply security restrictions.
+ */
+ bool is_external;
+
+ /*
+ * Allow a device to add the RuntimeD3Storage property even if the detected
+ * PCI device does not identify as storage class.
+ */
+ bool is_storage;
+
+ /*
+ * Disable the ACPI-driven L23 Ready-to-Detect transition for the root port.
+ */
+ bool disable_l23;
+};
+
+#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 0000000000..be412e7967
--- /dev/null
+++ b/src/soc/intel/common/block/pcie/rtd3/rtd3.c
@@ -0,0 +1,299 @@
+/* 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 <intelblocks/pmc_ipc.h>
+#include "chip.h"
+
+/*
+ * The "ExternalFacingPort" and "HotPlugSupportInD3" properties are defined at
+ * https://docs.microsoft.com/en-us/windows-hardware/drivers/pci/dsd-for-pcie-root-ports
+ */
+#define PCIE_EXTERNAL_PORT_UUID "EFCC06CC-73AC-4BC3-BFF0-76143807C389"
+#define PCIE_EXTERNAL_PORT_PROPERTY "ExternalFacingPort"
+
+#define PCIE_HOTPLUG_IN_D3_UUID "6211E2C0-58A3-4AF3-90E1-927A4E0C55A4"
+#define PCIE_HOTPLUG_IN_D3_PROPERTY "HotPlugSupportInD3"
+
+/*
+ * 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-guidelines/power-management-for-storage-hardware-devices-intro
+ */
+#define PCIE_RTD3_STORAGE_UUID "5025030F-842F-4AB4-A561-99A5189762D0"
+#define PCIE_RTD3_STORAGE_PROPERTY "StorageD3Enable"
+
+/* PCIe Root Port registers for link status and L23 control. */
+#define PCH_PCIE_CFG_LSTS 0x52 /* Link Status Register */
+#define PCH_PCIE_CFG_SPR 0xe0 /* Scratchpad */
+#define PCH_PCIE_CFG_RPPGEN 0xe2 /* Root Port Power Gating Enable */
+#define PCH_PCIE_CFG_LCAP_PN 0x4f /* Root Port Number */
+
+/* ACPI register names corresponding to PCIe root port registers. */
+#define ACPI_REG_PCI_LINK_ACTIVE "LASX" /* Link active status */
+#define ACPI_REG_PCI_L23_RDY_ENTRY "L23E" /* L23_Rdy Entry Request */
+#define ACPI_REG_PCI_L23_RDY_DETECT "L23R" /* L23_Rdy Detect Transition */
+#define ACPI_REG_PCI_L23_SAVE_STATE "NCB7" /* Scratch bit to save L23 state */
+
+/* Called from _ON to get PCIe link back to active state. */
+static void pcie_rtd3_acpi_l23_exit(void)
+{
+ /* Skip if port is not in L2/L3. */
+ acpigen_write_if_lequal_namestr_int(ACPI_REG_PCI_L23_SAVE_STATE, 1);
+
+ /* Initiate L2/L3 Ready To Detect transition. */
+ acpigen_write_store_int_to_namestr(1, ACPI_REG_PCI_L23_RDY_DETECT);
+
+ /* Wait for transition to detect. */
+ acpigen_write_delay_until_namestr_int(320, ACPI_REG_PCI_L23_RDY_DETECT, 0);
+
+ acpigen_write_store_int_to_namestr(0, ACPI_REG_PCI_L23_SAVE_STATE);
+
+ /* Once in detect, wait for link active. */
+ acpigen_write_delay_until_namestr_int(128, ACPI_REG_PCI_LINK_ACTIVE, 1);
+
+ acpigen_pop_len(); /* If */
+}
+
+/* Called from _OFF to put PCIe link into L2/L3 state. */
+static void pcie_rtd3_acpi_l23_entry(void)
+{
+ /* Initiate L2/L3 Entry request. */
+ acpigen_write_store_int_to_namestr(1, ACPI_REG_PCI_L23_RDY_ENTRY);
+
+ /* Wait for L2/L3 Entry request to clear. */
+ acpigen_write_delay_until_namestr_int(128, ACPI_REG_PCI_L23_RDY_ENTRY, 0);
+
+ acpigen_write_store_int_to_namestr(1, ACPI_REG_PCI_L23_SAVE_STATE);
+}
+
+static void
+pcie_rtd3_acpi_method_on(unsigned int pcie_rp,
+ const struct soc_intel_common_block_pcie_rtd3_config *config)
+{
+ acpigen_write_method_serialized("_ON", 0);
+
+ /* Assert enable GPIO to turn on device power. */
+ if (config->enable_gpio.pin_count) {
+ acpigen_enable_tx_gpio(&config->enable_gpio);
+ if (config->enable_delay_ms)
+ acpigen_write_sleep(config->enable_delay_ms);
+ }
+
+ /* Enable SRCCLK for root port if pin is defined. */
+ if (config->srcclk_pin >= 0)
+ pmc_ipc_acpi_set_pci_clock(pcie_rp, config->srcclk_pin, true);
+
+ /* De-assert reset GPIO to bring device out of reset. */
+ if (config->reset_gpio.pin_count) {
+ acpigen_disable_tx_gpio(&config->reset_gpio);
+ if (config->reset_delay_ms)
+ acpigen_write_sleep(config->reset_delay_ms);
+ }
+
+ /* Trigger L23 ready exit flow unless disabld by config. */
+ if (!config->disable_l23)
+ pcie_rtd3_acpi_l23_exit();
+
+ acpigen_pop_len(); /* Method */
+}
+
+static void
+pcie_rtd3_acpi_method_off(int pcie_rp,
+ const struct soc_intel_common_block_pcie_rtd3_config *config)
+{
+ acpigen_write_method_serialized("_OFF", 0);
+
+ /* Trigger L23 ready entry flow unless disabled by config. */
+ if (!config->disable_l23)
+ pcie_rtd3_acpi_l23_entry();
+
+ /* Assert reset GPIO to place device into reset. */
+ if (config->reset_gpio.pin_count) {
+ acpigen_enable_tx_gpio(&config->reset_gpio);
+ if (config->reset_off_delay_ms)
+ acpigen_write_sleep(config->reset_off_delay_ms);
+ }
+
+ /* Disable SRCCLK for this root port if pin is defined. */
+ if (config->srcclk_pin >= 0)
+ pmc_ipc_acpi_set_pci_clock(pcie_rp, config->srcclk_pin, false);
+
+ /* De-assert enable GPIO to turn off device power. */
+ if (config->enable_gpio.pin_count) {
+ acpigen_disable_tx_gpio(&config->enable_gpio);
+ if (config->enable_off_delay_ms)
+ acpigen_write_sleep(config->enable_off_delay_ms);
+ }
+
+ acpigen_pop_len(); /* Method */
+}
+
+static void
+pcie_rtd3_acpi_method_status(int pcie_rp,
+ const struct soc_intel_common_block_pcie_rtd3_config *config)
+{
+ const struct acpi_gpio *gpio;
+
+ acpigen_write_method("_STA", 0);
+
+ /* Use enable GPIO for status if provided, otherwise use reset GPIO. */
+ if (config->enable_gpio.pin_count)
+ gpio = &config->enable_gpio;
+ else
+ gpio = &config->reset_gpio;
+
+ /* Read current GPIO value into Local0. */
+ acpigen_get_tx_gpio(gpio);
+
+ /* Ensure check works for both active low and active high GPIOs. */
+ acpigen_write_store_int_to_op(gpio->active_low, LOCAL1_OP);
+
+ acpigen_write_if_lequal_op_op(LOCAL0_OP, LOCAL1_OP);
+ acpigen_write_return_op(ZERO_OP);
+ acpigen_pop_len(); /* If */
+ acpigen_write_else();
+ acpigen_write_return_op(ONE_OP);
+ acpigen_pop_len(); /* Else */
+
+ acpigen_pop_len(); /* Method */
+}
+
+static void pcie_rtd3_acpi_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"};
+ const struct device *parent = dev->bus->dev;
+ const char *scope = acpi_device_path(parent);
+ const struct opregion opregion = OPREGION("PXCS", PCI_CONFIG, 0, 0xff);
+ const struct fieldlist fieldlist[] = {
+ FIELDLIST_OFFSET(PCH_PCIE_CFG_LSTS),
+ FIELDLIST_RESERVED(13),
+ FIELDLIST_NAMESTR(ACPI_REG_PCI_LINK_ACTIVE, 1),
+ FIELDLIST_OFFSET(PCH_PCIE_CFG_SPR),
+ FIELDLIST_RESERVED(7),
+ FIELDLIST_NAMESTR(ACPI_REG_PCI_L23_SAVE_STATE, 1),
+ FIELDLIST_OFFSET(PCH_PCIE_CFG_RPPGEN),
+ FIELDLIST_RESERVED(2),
+ FIELDLIST_NAMESTR(ACPI_REG_PCI_L23_RDY_ENTRY, 1),
+ FIELDLIST_NAMESTR(ACPI_REG_PCI_L23_RDY_DETECT, 1),
+ };
+ uint8_t pcie_rp;
+ struct acpi_dp *dsd, *pkg;
+
+ if (!is_dev_enabled(parent)) {
+ printk(BIOS_ERR, "%s: root port not enabled\n", __func__);
+ return;
+ }
+ if (!scope) {
+ printk(BIOS_ERR, "%s: root port scope not found\n", __func__);
+ return;
+ }
+ if (!config->enable_gpio.pin_count && !config->reset_gpio.pin_count) {
+ printk(BIOS_ERR, "%s: Enable and/or Reset GPIO required for %s.\n",
+ __func__, scope);
+ return;
+ }
+ if (config->srcclk_pin > CONFIG_MAX_PCIE_CLOCKS) {
+ printk(BIOS_ERR, "%s: Invalid clock pin %u for %s.\n", __func__,
+ config->srcclk_pin, scope);
+ return;
+ }
+
+ /* Read port number of root port that this device is attached to. */
+ pcie_rp = pci_read_config8(parent, 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 (%s)\n", scope, dev_path(parent),
+ config->desc ?: dev->chip_ops->name);
+
+ /* The RTD3 power resource is added to the root port, not the device. */
+ acpigen_write_scope(scope);
+
+ if (config->desc)
+ acpigen_write_name_string("_DDN", config->desc);
+
+ acpigen_write_opregion(&opregion);
+ acpigen_write_field("PXCS", fieldlist, ARRAY_SIZE(fieldlist),
+ FIELD_ANYACC | FIELD_NOLOCK | FIELD_PRESERVE);
+
+ /* ACPI Power Resource for controlling the attached device power. */
+ acpigen_write_power_res("RTD3", 0, 0, power_res_states, ARRAY_SIZE(power_res_states));
+ pcie_rtd3_acpi_method_status(pcie_rp, config);
+ pcie_rtd3_acpi_method_on(pcie_rp, config);
+ pcie_rtd3_acpi_method_off(pcie_rp, config);
+ acpigen_pop_len(); /* PowerResource */
+
+ /* Indicate to the OS that device supports hotplug in D3. */
+ dsd = acpi_dp_new_table("_DSD");
+ pkg = acpi_dp_new_table(PCIE_HOTPLUG_IN_D3_UUID);
+ acpi_dp_add_integer(pkg, PCIE_HOTPLUG_IN_D3_PROPERTY, 1);
+ acpi_dp_add_package(dsd, pkg);
+
+ /* Indicate to the OS if the device provides an External facing port. */
+ if (config->is_external) {
+ pkg = acpi_dp_new_table(PCIE_EXTERNAL_PORT_UUID);
+ acpi_dp_add_integer(pkg, PCIE_EXTERNAL_PORT_PROPERTY, 1);
+ acpi_dp_add_package(dsd, pkg);
+ }
+ acpi_dp_write(dsd);
+
+ /*
+ * Check the sibling device on the root port to see if it is storage class and add the
+ * property for the OS to enable storage D3, or allow it to be enabled by config.
+ */
+ if (config->is_storage
+ || (dev->sibling && (dev->sibling->class >> 16) == PCI_BASE_CLASS_STORAGE)) {
+ 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, PCIE_RTD3_STORAGE_PROPERTY, 1);
+ acpi_dp_add_package(dsd, pkg);
+ acpi_dp_write(dsd);
+
+ acpigen_pop_len(); /* Device */
+
+ printk(BIOS_INFO, "%s: Added StorageD3Enable property\n", scope);
+ }
+
+ acpigen_pop_len(); /* Scope */
+}
+
+static const char *pcie_rtd3_acpi_name(const struct device *dev)
+{
+ /* Attached device name must be "PXSX" for the Linux Kernel to recognize it. */
+ return "PXSX";
+}
+
+static struct device_operations pcie_rtd3_ops = {
+ .read_resources = noop_read_resources,
+ .set_resources = noop_set_resources,
+ .acpi_fill_ssdt = pcie_rtd3_acpi_fill_ssdt,
+ .acpi_name = pcie_rtd3_acpi_name,
+};
+
+static void pcie_rtd3_acpi_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_acpi_enable
+};