Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions drivers/pci/of.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define pr_fmt(fmt) "PCI: OF: " fmt

#include <linux/cleanup.h>
#include <linux/gpio/consumer.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/pci.h>
Expand All @@ -15,6 +16,7 @@
#include <linux/of_address.h>
#include <linux/of_pci.h>
#include <linux/platform_device.h>
#include <linux/pm_wakeirq.h>
#include "pci.h"

#ifdef CONFIG_PCI
Expand Down Expand Up @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions drivers/pci/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <linux/init.h>
#include <linux/msi.h>
#include <linux/of.h>
#include <linux/of_pci.h>
#include <linux/pci.h>
#include <linux/pm.h>
#include <linux/slab.h>
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions drivers/pci/pci.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
2 changes: 2 additions & 0 deletions drivers/pci/probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions drivers/pci/remove.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
6 changes: 6 additions & 0 deletions include/linux/of_pci.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions include/linux/pci.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down