drivers/pcie/rtd3/device: Add PCIe RTD3 driver
This driver was inspired from soc/intel/common/block/pci/rtd3. I decided
to copy and modify it because the Intel driver has a lot of Intel
specific code.
This driver has been stripped down to only provide a power resource and
set the StorageD3Enable property. This driver is SoC agnostic and does
not handle suspending the actual PCIe root port. That should be
implemented by an SoC specific driver.
This is required for Guybrush to suspend/resume properly because the
NVMe power is tied to the S0 power rails, so the kernel needs to place
the device into D3.
BUG=b:184617186
TEST=Guybrush is able to suspend/resume properly. Also see power
resource get enabled / disabled.
[ 56.075559] power-0416 __acpi_power_off : Power resource [RTD3] turned off
[ 56.075562] device_pm-0279 device_set_power : Device [PXSX] transitioned to D3cold
[ 56.075567] pci_pm_suspend_noirq: nvme 0000:02:00.0: PCI PM: Suspend power state: D3cold
[ 56.075569] nvme 0000:02:00.0: pci_pm_suspend_noirq+0x0/0x413 returned 0 after 15978 usecs
[ 123.464874] nvme 0000:02:00.0: calling pci_pm_resume_noirq+0x0/0x11d @ 7, parent: 0000:00:02.4
[ 123.464891] acpi_device_set_power: ACPI: \_SB_.PCI0.GP14.PXSX: Power state change: D3cold -> D0
[ 123.464982] power-0360 __acpi_power_on : Power resource [RTD3] turned on
[ 123.464984] device_pm-0279 device_set_power : Device [PXSX] transitioned to D0
[ 123.465039] nvme 0000:02:00.0: pci_pm_resume_noirq+0x0/0x11d returned 0 after 158 usecs
Signed-off-by: Raul E Rangel <rrangel@chromium.org>
Change-Id: I2adfc925183ff7a19ab97e89212bc87c40d552d0
Reviewed-on: https://review.coreboot.org/c/coreboot/+/54966
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Tim Wawrzynczak <twawrzynczak@chromium.org>
diff --git a/src/drivers/pcie/rtd3/device/Kconfig b/src/drivers/pcie/rtd3/device/Kconfig
new file mode 100644
index 0000000..7fecd57
--- /dev/null
+++ b/src/drivers/pcie/rtd3/device/Kconfig
@@ -0,0 +1,6 @@
+config DRIVERS_PCIE_RTD3_DEVICE
+ bool
+ depends on HAVE_ACPI_TABLES
+ help
+ When enabled, this driver will add support for ACPI controlled
+ Runtime D3 using GPIOs for power/reset control of the PCIe device.
diff --git a/src/drivers/pcie/rtd3/device/Makefile.inc b/src/drivers/pcie/rtd3/device/Makefile.inc
new file mode 100644
index 0000000..df4aaf6
--- /dev/null
+++ b/src/drivers/pcie/rtd3/device/Makefile.inc
@@ -0,0 +1 @@
+ramstage-$(CONFIG_DRIVERS_PCIE_RTD3_DEVICE) += chip.c
diff --git a/src/drivers/pcie/rtd3/device/chip.c b/src/drivers/pcie/rtd3/device/chip.c
new file mode 100644
index 0000000..349dffd
--- /dev/null
+++ b/src/drivers/pcie/rtd3/device/chip.c
@@ -0,0 +1,169 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <acpi/acpi_device.h>
+#include <acpi/acpigen.h>
+#include <acpi/acpigen_pci.h>
+#include <assert.h>
+#include <console/console.h>
+#include <device/device.h>
+#include <device/pci.h>
+#include <device/pci_ids.h>
+#include <device/pci_ops.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-guidelines/power-management-for-storage-hardware-devices-intro
+ */
+#define PCIE_RTD3_STORAGE_UUID "5025030F-842F-4AB4-A561-99A5189762D0"
+#define PCIE_RTD3_STORAGE_PROPERTY "StorageD3Enable"
+
+/*
+ * Writes the ACPI power resources for a PCI device so it can enter D3Cold.
+ *
+ * If the device is a storage class, then the StorageD3Enable _DSD will
+ * also be added.
+ *
+ * e.g.,
+ *
+ * Scope (\_SB.PCI0.GP14)
+ * {
+ * Device (NVME)
+ * {
+ * Name (_PR0, Package (0x01) // _PR0: Power Resources for D0
+ * {
+ * PRIC
+ * })
+ * Name (_PR3, Package (0x01) // _PR3: Power Resources for D3hot
+ * {
+ * PRIC
+ * })
+ * PowerResource (PRIC, 0x00, 0x0000)
+ * {
+ * Method (_STA, 0, NotSerialized) // _STA: Status
+ * {
+ * ...
+ * }
+ *
+ * Method (_ON, 0, Serialized) // _ON_: Power On
+ * {
+ * ...
+ * }
+ *
+ * Method (_OFF, 0, Serialized) // _OFF: Power Off
+ * {
+ * ...
+ * }
+ * }
+ *
+ * Name (_ADR, 0x0000000000000000) // _ADR: Address
+ * Method (_STA, 0, NotSerialized) // _STA: Status
+ * {
+ * Return (0x0F)
+ * }
+ *
+ * Name (_DSD, Package (0x02) // _DSD: Device-Specific Data
+ * {
+ * ToUUID ("5025030f-842f-4ab4-a561-99a5189762d0"),
+ * Package (0x01)
+ * {
+ * Package (0x02)
+ * {
+ * "StorageD3Enable",
+ * One
+ * }
+ * }
+ * })
+ * }
+ * }
+ */
+static void pcie_rtd3_device_acpi_fill_ssdt(const struct device *dev)
+{
+ struct acpi_dp *dsd, *pkg;
+ const struct drivers_pcie_rtd3_device_config *config = config_of(dev);
+ /* Copy the GPIOs to avoid discards 'const' qualifier error */
+ struct acpi_gpio reset_gpio = config->reset_gpio;
+ struct acpi_gpio enable_gpio = config->enable_gpio;
+ const struct acpi_power_res_params power_res_params = {
+ .reset_gpio = &reset_gpio,
+ .reset_delay_ms = config->reset_delay_ms,
+ .reset_off_delay_ms = config->reset_off_delay_ms,
+ .enable_gpio = &enable_gpio,
+ .enable_delay_ms = config->enable_delay_ms,
+ .enable_off_delay_ms = config->enable_off_delay_ms,
+ .use_gpio_for_status = true,
+ };
+ const char *scope = acpi_device_scope(dev);
+ const char *name = acpi_device_name(dev);
+
+ assert(name);
+ assert(scope);
+
+ printk(BIOS_INFO, "%s.%s: Enable RTD3 for %s (%s)\n", scope, name, dev_path(dev),
+ dev->chip_ops->name);
+
+ acpigen_write_scope(scope);
+ acpigen_write_device(acpi_device_name(dev));
+
+ acpi_device_add_power_res(&power_res_params);
+
+ acpigen_write_ADR_pci_device(dev);
+ acpigen_write_STA(acpi_device_status(dev));
+
+ /* Storage devices won't enter D3 without this property */
+ if ((dev->class >> 16) == PCI_BASE_CLASS_STORAGE) {
+ 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);
+
+ printk(BIOS_INFO, "%s.%s: Added StorageD3Enable property\n", scope, name);
+ }
+
+ acpigen_write_device_end();
+ acpigen_write_scope_end();
+
+ /* Call the default PCI acpi_fill_ssdt */
+ pci_rom_ssdt(dev);
+}
+
+static const char *pcie_rtd3_device_acpi_name(const struct device *dev)
+{
+ const struct drivers_pcie_rtd3_device_config *config = config_of(dev);
+
+ return config->name;
+}
+
+static struct device_operations pcie_rtd3_device_ops = {
+ .read_resources = pci_dev_read_resources,
+ .set_resources = pci_dev_set_resources,
+ .enable_resources = pci_dev_enable_resources,
+ .init = pci_dev_init,
+ .ops_pci = &pci_dev_ops_pci,
+ .write_acpi_tables = pci_rom_write_acpi_tables,
+ .acpi_fill_ssdt = pcie_rtd3_device_acpi_fill_ssdt,
+ .acpi_name = pcie_rtd3_device_acpi_name,
+};
+
+static void pcie_rtd3_device_enable(struct device *dev)
+{
+ struct drivers_pcie_rtd3_device_config *config = dev ? dev->chip_info : NULL;
+
+ if (!config)
+ return;
+
+ if (dev->path.type != DEVICE_PATH_PCI) {
+ printk(BIOS_ERR, "%s: Invalid device type\n", dev_path(dev));
+ return;
+ }
+
+ dev->ops = &pcie_rtd3_device_ops;
+}
+
+struct chip_operations drivers_pcie_rtd3_device_ops = {
+ CHIP_NAME("PCIe Device w/ Runtime D3")
+ .enable_dev = pcie_rtd3_device_enable
+};
diff --git a/src/drivers/pcie/rtd3/device/chip.h b/src/drivers/pcie/rtd3/device/chip.h
new file mode 100644
index 0000000..e1a8aa7
--- /dev/null
+++ b/src/drivers/pcie/rtd3/device/chip.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __DRIVERS_PCIE_RTD3_DEVICE_CHIP_H__
+#define __DRIVERS_PCIE_RTD3_DEVICE_CHIP_H__
+
+#include <acpi/acpi_device.h>
+
+/* Adds a GPIO controlled ACPI PowerResource for a PCIe device. */
+struct drivers_pcie_rtd3_device_config {
+ /* Name of ACPI node for the device */
+ const char *name;
+
+ /* 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;
+};
+
+#endif /* __DRIVERS_PCIE_RTD3_DEVICE_CHIP_H__ */