diff --git a/drivers/pci/of.c b/drivers/pci/of.c index 9bb5f258759be..d748f5654df20 100644 --- a/drivers/pci/of.c +++ b/drivers/pci/of.c @@ -7,6 +7,7 @@ #define pr_fmt(fmt) "PCI: OF: " fmt #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include "pci.h" #ifdef CONFIG_PCI @@ -586,6 +588,79 @@ int of_irq_parse_and_map_pci(const struct pci_dev *dev, u8 slot, u8 pin) return irq_create_of_mapping(&oirq); } EXPORT_SYMBOL_GPL(of_irq_parse_and_map_pci); +static void pci_configure_wake_irq(struct pci_dev *pdev, struct gpio_desc *wake) +{ + int ret, wake_irq; + + wake_irq = gpiod_to_irq(wake); + if (wake_irq < 0) { + pci_err(pdev, "Failed to get wake irq: %d\n", wake_irq); + return; + } + + /* + * dev_pm_set_dedicated_wake_irq() associates a wakeup IRQ with the + * device and requests it, but the PM core keeps it disabled by default. + * The IRQ is enabled only when the device is allowed to wake the system + * (during system suspend and after runtime suspend), and only if device + * wakeup is enabled. + * + * When the wake IRQ fires, the wakeirq handler invokes pm_runtime_resume() + * to bring the device back to an active power state (e.g. from D3cold to D0). + * Once the device is active and the link is usable, the endpoint may signal + * a PME, which is then handled by the PCI core (either via PME polling or the + * PCIe PME service driver) to wakeup particular endpoint. + */ + ret = dev_pm_set_dedicated_wake_irq(&pdev->dev, wake_irq); + if (ret < 0) { + pci_err(pdev, "Failed to set WAKE# IRQ: %d\n", ret); + return; + } + + ret = irq_set_irq_type(wake_irq, IRQ_TYPE_LEVEL_LOW); + if (ret < 0) { + dev_pm_clear_wake_irq(&pdev->dev); + pci_err(pdev, "Failed to set irq_type: %d\n", ret); + return; + } + + device_init_wakeup(&pdev->dev, true); +} + +void pci_configure_of_wake_gpio(struct pci_dev *dev) +{ + struct device_node *dn = pci_device_to_OF_node(dev); + struct gpio_desc *gpio; + + if (!dn) + return; + /* + * fwnode_gpiod_get() may fail with -EBUSY (e.g. shared WAKE#), but the + * actual WAKE# trigger from the device would still work and the host + * controller driver will enable power to the topology. + * + * -EPROBE_DEFER cannot be propagated here since pci_device_add() has no + * retry mechanism. + */ + gpio = fwnode_gpiod_get(of_fwnode_handle(dn), "wake", GPIOD_IN, NULL); + if (!IS_ERR(gpio)) { + dev->wake = gpio; + pci_configure_wake_irq(dev, gpio); + } +} + +void pci_remove_of_wake_gpio(struct pci_dev *dev) +{ + struct device_node *dn = pci_device_to_OF_node(dev); + + if (!dn) + return; + + device_init_wakeup(&dev->dev, false); + dev_pm_clear_wake_irq(&dev->dev); + gpiod_put(dev->wake); + dev->wake = NULL; +} #endif /* CONFIG_OF_IRQ */ static int pci_parse_request_of_pci_ranges(struct device *dev, diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index a62ed5560c68f..681b2c99e7842 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1128,6 +1129,16 @@ static inline bool platform_pci_bridge_d3(struct pci_dev *dev) return acpi_pci_bridge_d3(dev); } +void platform_pci_configure_wake(struct pci_dev *dev) +{ + pci_configure_of_wake_gpio(dev); +} + +void platform_pci_remove_wake(struct pci_dev *dev) +{ + pci_remove_of_wake_gpio(dev); +} + /** * pci_update_current_state - Read power state of given device and cache it * @dev: PCI device to handle. diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 5510c103be2d3..9ec55f3f58899 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -265,6 +265,8 @@ void pci_msix_init(struct pci_dev *dev); bool pci_bridge_d3_possible(struct pci_dev *dev); void pci_bridge_d3_update(struct pci_dev *dev); int pci_bridge_wait_for_secondary_bus(struct pci_dev *dev, char *reset_type); +void platform_pci_configure_wake(struct pci_dev *dev); +void platform_pci_remove_wake(struct pci_dev *dev); static inline bool pci_bus_rrs_vendor_id(u32 l) { diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index fb0eb6b993ce1..5af9dfe8206d6 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2741,6 +2741,8 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus) ret = device_add(&dev->dev); WARN_ON(ret < 0); + platform_pci_configure_wake(dev); + pci_npem_create(dev); pci_doe_sysfs_init(dev); diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index 2a22595f38af6..6bc88765d7ac6 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -35,6 +35,7 @@ static void pci_destroy_dev(struct pci_dev *dev) if (pci_dev_test_and_set_removed(dev)) return; + platform_pci_remove_wake(dev); pci_doe_sysfs_teardown(dev); pci_npem_remove(dev); diff --git a/include/linux/of_pci.h b/include/linux/of_pci.h index 29658c0ee71ff..649fe8eafcfa4 100644 --- a/include/linux/of_pci.h +++ b/include/linux/of_pci.h @@ -30,12 +30,18 @@ static inline void of_pci_check_probe_only(void) { } #if IS_ENABLED(CONFIG_OF_IRQ) int of_irq_parse_and_map_pci(const struct pci_dev *dev, u8 slot, u8 pin); +void pci_configure_of_wake_gpio(struct pci_dev *dev); +void pci_remove_of_wake_gpio(struct pci_dev *dev); #else static inline int of_irq_parse_and_map_pci(const struct pci_dev *dev, u8 slot, u8 pin) { return 0; } + +static inline void pci_configure_of_wake_gpio(struct pci_dev *dev) { } + +static inline void pci_remove_of_wake_gpio(struct pci_dev *dev) { } #endif #endif diff --git a/include/linux/pci.h b/include/linux/pci.h index 05aeee8c8844a..06ae64ab39acf 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -558,6 +558,8 @@ struct pci_dev { /* These methods index pci_reset_fn_methods[] */ u8 reset_methods[PCI_NUM_RESET_METHODS]; /* In priority order */ + struct gpio_desc *wake; /* Holds WAKE# gpio */ + #ifdef CONFIG_PCIE_TPH u16 tph_cap; /* TPH capability offset */ u8 tph_mode; /* TPH mode */