* [RFC PATCH v9 5/7] PCI: Make pci_platform_pm_ops's callbacks optional
2017-10-27 7:17 [RFC PATCH v9 0/7] PCI: rockchip: Move PCIe WAKE# handling into pci core Jeffy Chen
2017-10-27 7:17 ` [RFC PATCH v9 1/7] dt-bindings: PCI: Add definition of PCIe WAKE# irq and PCI irq Jeffy Chen
@ 2017-10-27 7:17 ` Jeffy Chen
2017-10-27 7:17 ` [RFC PATCH v9 6/7] PCI / PM: Move acpi wakeup code to pci core Jeffy Chen
2017-10-27 7:17 ` [RFC PATCH v9 7/7] PCI / PM: Add support for the PCIe WAKE# signal for OF Jeffy Chen
3 siblings, 0 replies; 5+ messages in thread
From: Jeffy Chen @ 2017-10-27 7:17 UTC (permalink / raw)
To: linux-kernel, bhelgaas
Cc: linux-pm, tony, shawn.lin, briannorris, rjw, dianders, Jeffy Chen,
linux-pci
Allow platforms not to provide some of the pci_platform_pm_ops's
callbacks.
Also change the return value from -ENOSYS to -ENODEV for:
warning: drivers/pci/pci.c,594: ENOSYS means 'invalid syscall nr' and nothing else
Signed-off-by: Jeffy Chen <jeffy.chen@rock-chips.com>
---
Changes in v9: None
Changes in v8: None
Changes in v7: None
Changes in v6: None
Changes in v5: None
Changes in v3: None
Changes in v2: None
drivers/pci/pci.c | 38 +++++++++++++++++++++++++-------------
drivers/pci/pci.h | 5 +----
2 files changed, 26 insertions(+), 17 deletions(-)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index f0d68066c726..e120b00a9017 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -572,46 +572,58 @@ static void pci_restore_bars(struct pci_dev *dev)
static const struct pci_platform_pm_ops *pci_platform_pm;
-int pci_set_platform_pm(const struct pci_platform_pm_ops *ops)
+void pci_set_platform_pm(const struct pci_platform_pm_ops *ops)
{
- if (!ops->is_manageable || !ops->set_state || !ops->get_state ||
- !ops->choose_state || !ops->set_wakeup || !ops->need_resume)
- return -EINVAL;
pci_platform_pm = ops;
- return 0;
}
static inline bool platform_pci_power_manageable(struct pci_dev *dev)
{
- return pci_platform_pm ? pci_platform_pm->is_manageable(dev) : false;
+ if (pci_platform_pm && pci_platform_pm->is_manageable)
+ return pci_platform_pm->is_manageable(dev);
+
+ return false;
}
static inline int platform_pci_set_power_state(struct pci_dev *dev,
pci_power_t t)
{
- return pci_platform_pm ? pci_platform_pm->set_state(dev, t) : -ENOSYS;
+ if (pci_platform_pm && pci_platform_pm->set_state)
+ return pci_platform_pm->set_state(dev, t);
+
+ return -ENODEV;
}
static inline pci_power_t platform_pci_get_power_state(struct pci_dev *dev)
{
- return pci_platform_pm ? pci_platform_pm->get_state(dev) : PCI_UNKNOWN;
+ if (pci_platform_pm && pci_platform_pm->get_state)
+ return pci_platform_pm->get_state(dev);
+
+ return PCI_UNKNOWN;
}
static inline pci_power_t platform_pci_choose_state(struct pci_dev *dev)
{
- return pci_platform_pm ?
- pci_platform_pm->choose_state(dev) : PCI_POWER_ERROR;
+ if (pci_platform_pm && pci_platform_pm->choose_state)
+ return pci_platform_pm->choose_state(dev);
+
+ return PCI_POWER_ERROR;
}
static inline int platform_pci_set_wakeup(struct pci_dev *dev, bool enable)
{
- return pci_platform_pm ?
- pci_platform_pm->set_wakeup(dev, enable) : -ENODEV;
+ if (pci_platform_pm && pci_platform_pm->set_wakeup)
+ return pci_platform_pm->set_wakeup(dev, enable);
+
+ return -ENODEV;
}
static inline bool platform_pci_need_resume(struct pci_dev *dev)
{
- return pci_platform_pm ? pci_platform_pm->need_resume(dev) : false;
+ if (pci_platform_pm && pci_platform_pm->need_resume)
+ return pci_platform_pm->need_resume(dev);
+
+ return false;
}
/**
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index e816a13e6259..048668271014 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -52,9 +52,6 @@ int pci_probe_reset_function(struct pci_dev *dev);
* @need_resume: returns 'true' if the given device (which is currently
* suspended) needs to be resumed to be configured for system
* wakeup.
- *
- * If given platform is generally capable of power managing PCI devices, all of
- * these callbacks are mandatory.
*/
struct pci_platform_pm_ops {
bool (*is_manageable)(struct pci_dev *dev);
@@ -65,7 +62,7 @@ struct pci_platform_pm_ops {
bool (*need_resume)(struct pci_dev *dev);
};
-int pci_set_platform_pm(const struct pci_platform_pm_ops *ops);
+void pci_set_platform_pm(const struct pci_platform_pm_ops *ops);
void pci_update_current_state(struct pci_dev *dev, pci_power_t state);
void pci_power_up(struct pci_dev *dev);
void pci_disable_enabled_device(struct pci_dev *dev);
--
2.11.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [RFC PATCH v9 6/7] PCI / PM: Move acpi wakeup code to pci core
2017-10-27 7:17 [RFC PATCH v9 0/7] PCI: rockchip: Move PCIe WAKE# handling into pci core Jeffy Chen
2017-10-27 7:17 ` [RFC PATCH v9 1/7] dt-bindings: PCI: Add definition of PCIe WAKE# irq and PCI irq Jeffy Chen
2017-10-27 7:17 ` [RFC PATCH v9 5/7] PCI: Make pci_platform_pm_ops's callbacks optional Jeffy Chen
@ 2017-10-27 7:17 ` Jeffy Chen
2017-10-27 7:17 ` [RFC PATCH v9 7/7] PCI / PM: Add support for the PCIe WAKE# signal for OF Jeffy Chen
3 siblings, 0 replies; 5+ messages in thread
From: Jeffy Chen @ 2017-10-27 7:17 UTC (permalink / raw)
To: linux-kernel, bhelgaas
Cc: linux-pm, tony, shawn.lin, briannorris, rjw, dianders, Jeffy Chen,
linux-pci, linux-acpi, Len Brown
Move acpi wakeup code to pci core as pci_set_wakeup(), so that other
platforms could reuse it.
Also add .setup_dev() / .setup_host_bridge() / .cleanup() platform pm
ops's callbacks to setup and cleanup pci devices and host bridge for
wakeup.
Signed-off-by: Jeffy Chen <jeffy.chen@rock-chips.com>
---
Changes in v9: None
Changes in v8: None
Changes in v7: None
Changes in v6: None
Changes in v5: None
Changes in v3: None
Changes in v2: None
drivers/pci/pci-acpi.c | 121 +++++++++++++++++++++++------------------------
drivers/pci/pci-driver.c | 9 ++++
drivers/pci/pci.c | 84 ++++++++++++++++++++++++++++----
drivers/pci/pci.h | 28 +++++++++--
drivers/pci/probe.c | 12 ++++-
drivers/pci/remove.c | 2 +
include/linux/pci.h | 2 +
7 files changed, 180 insertions(+), 78 deletions(-)
diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c
index 4708eb9df71b..ee96e7afe1ac 100644
--- a/drivers/pci/pci-acpi.c
+++ b/drivers/pci/pci-acpi.c
@@ -569,31 +569,6 @@ static pci_power_t acpi_pci_get_power_state(struct pci_dev *dev)
return state_conv[state];
}
-static int acpi_pci_propagate_wakeup(struct pci_bus *bus, bool enable)
-{
- while (bus->parent) {
- if (acpi_pm_device_can_wakeup(&bus->self->dev))
- return acpi_pm_set_bridge_wakeup(&bus->self->dev, enable);
-
- bus = bus->parent;
- }
-
- /* We have reached the root bus. */
- if (bus->bridge) {
- if (acpi_pm_device_can_wakeup(bus->bridge))
- return acpi_pm_set_bridge_wakeup(bus->bridge, enable);
- }
- return 0;
-}
-
-static int acpi_pci_wakeup(struct pci_dev *dev, bool enable)
-{
- if (acpi_pm_device_can_wakeup(&dev->dev))
- return acpi_pm_set_device_wakeup(&dev->dev, enable);
-
- return acpi_pci_propagate_wakeup(dev->bus, enable);
-}
-
static bool acpi_pci_need_resume(struct pci_dev *dev)
{
struct acpi_device *adev = ACPI_COMPANION(&dev->dev);
@@ -610,14 +585,29 @@ static bool acpi_pci_need_resume(struct pci_dev *dev)
return !!adev->power.flags.dsw_present;
}
-static const struct pci_platform_pm_ops acpi_pci_platform_pm = {
- .is_manageable = acpi_pci_power_manageable,
- .set_state = acpi_pci_set_power_state,
- .get_state = acpi_pci_get_power_state,
- .choose_state = acpi_pci_choose_state,
- .set_wakeup = acpi_pci_wakeup,
- .need_resume = acpi_pci_need_resume,
-};
+static bool acpi_pci_can_wakeup(void *pmdata)
+{
+ struct device *dev = pmdata;
+
+ if (!dev)
+ return false;
+
+ return acpi_pm_device_can_wakeup(dev);
+}
+
+static int acpi_pci_dev_wakeup(void *pmdata, bool enable)
+{
+ struct device *dev = pmdata;
+
+ return acpi_pm_set_device_wakeup(dev, enable);
+}
+
+static int acpi_pci_bridge_wakeup(void *pmdata, bool enable)
+{
+ struct device *dev = pmdata;
+
+ return acpi_pm_set_bridge_wakeup(dev, enable);
+}
void acpi_pci_add_bus(struct pci_bus *bus)
{
@@ -658,20 +648,6 @@ void acpi_pci_remove_bus(struct pci_bus *bus)
acpi_pci_slot_remove(bus);
}
-/* ACPI bus type */
-static struct acpi_device *acpi_pci_find_companion(struct device *dev)
-{
- struct pci_dev *pci_dev = to_pci_dev(dev);
- bool check_children;
- u64 addr;
-
- check_children = pci_is_bridge(pci_dev);
- /* Please ref to ACPI spec for the syntax of _ADR */
- addr = (PCI_SLOT(pci_dev->devfn) << 16) | PCI_FUNC(pci_dev->devfn);
- return acpi_find_child_device(ACPI_COMPANION(dev->parent), addr,
- check_children);
-}
-
/**
* pci_acpi_optimize_delay - optimize PCI D3 and D3cold delay from ACPI
* @pdev: the PCI device whose delay is to be updated
@@ -723,34 +699,55 @@ static void pci_acpi_optimize_delay(struct pci_dev *pdev,
ACPI_FREE(obj);
}
-static void pci_acpi_setup(struct device *dev)
+static void *acpi_pci_setup_dev(struct pci_dev *pci_dev)
{
- struct pci_dev *pci_dev = to_pci_dev(dev);
- struct acpi_device *adev = ACPI_COMPANION(dev);
+ struct acpi_device *adev = ACPI_COMPANION(&pci_dev->dev);
if (!adev)
- return;
+ return NULL;
pci_acpi_optimize_delay(pci_dev, adev->handle);
pci_acpi_add_pm_notifier(adev, pci_dev);
if (!adev->wakeup.flags.valid)
- return;
+ return NULL;
+
+ device_set_wakeup_capable(&pci_dev->dev, true);
+ acpi_pm_set_device_wakeup(&pci_dev->dev, false);
- device_set_wakeup_capable(dev, true);
- acpi_pci_wakeup(pci_dev, false);
+ return &pci_dev->dev;
}
-static void pci_acpi_cleanup(struct device *dev)
+static void *acpi_pci_setup_host_bridge(struct pci_host_bridge *bridge)
{
- struct acpi_device *adev = ACPI_COMPANION(dev);
+ return &bridge->dev;
+}
- if (!adev)
- return;
+static const struct pci_platform_pm_ops acpi_pci_platform_pm = {
+ .is_manageable = acpi_pci_power_manageable,
+ .set_state = acpi_pci_set_power_state,
+ .get_state = acpi_pci_get_power_state,
+ .choose_state = acpi_pci_choose_state,
+ .need_resume = acpi_pci_need_resume,
+ .setup_dev = acpi_pci_setup_dev,
+ .setup_host_bridge = acpi_pci_setup_host_bridge,
+ .can_wakeup = acpi_pci_can_wakeup,
+ .dev_wakeup = acpi_pci_dev_wakeup,
+ .bridge_wakeup = acpi_pci_bridge_wakeup,
+};
+
+/* ACPI bus type */
+static struct acpi_device *acpi_pci_find_companion(struct device *dev)
+{
+ struct pci_dev *pci_dev = to_pci_dev(dev);
+ bool check_children;
+ u64 addr;
- pci_acpi_remove_pm_notifier(adev);
- if (adev->wakeup.flags.valid)
- device_set_wakeup_capable(dev, false);
+ check_children = pci_is_bridge(pci_dev);
+ /* Please ref to ACPI spec for the syntax of _ADR */
+ addr = (PCI_SLOT(pci_dev->devfn) << 16) | PCI_FUNC(pci_dev->devfn);
+ return acpi_find_child_device(ACPI_COMPANION(dev->parent), addr,
+ check_children);
}
static bool pci_acpi_bus_match(struct device *dev)
@@ -762,8 +759,6 @@ static struct acpi_bus_type acpi_pci_bus = {
.name = "PCI",
.match = pci_acpi_bus_match,
.find_companion = acpi_pci_find_companion,
- .setup = pci_acpi_setup,
- .cleanup = pci_acpi_cleanup,
};
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index 9be563067c0c..abb7caa8a6e5 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -421,10 +421,17 @@ static int pci_device_probe(struct device *dev)
if (error < 0)
return error;
+ pci_dev->pmdata = platform_pci_setup_dev(pci_dev);
+ if (IS_ERR(pci_dev->pmdata)) {
+ pcibios_free_irq(pci_dev);
+ return PTR_ERR(pci_dev->pmdata);
+ }
+
pci_dev_get(pci_dev);
if (pci_device_can_probe(pci_dev)) {
error = __pci_device_probe(drv, pci_dev);
if (error) {
+ platform_pci_cleanup(pci_dev->pmdata);
pcibios_free_irq(pci_dev);
pci_dev_put(pci_dev);
}
@@ -438,6 +445,8 @@ static int pci_device_remove(struct device *dev)
struct pci_dev *pci_dev = to_pci_dev(dev);
struct pci_driver *drv = pci_dev->driver;
+ platform_pci_cleanup(pci_dev->pmdata);
+
if (drv) {
if (drv->remove) {
pm_runtime_get_sync(dev);
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index e120b00a9017..6add5d3209bf 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -585,6 +585,52 @@ static inline bool platform_pci_power_manageable(struct pci_dev *dev)
return false;
}
+void *platform_pci_setup_dev(struct pci_dev *dev)
+{
+ if (pci_platform_pm && pci_platform_pm->setup_dev)
+ return pci_platform_pm->setup_dev(dev);
+
+ return NULL;
+}
+
+void *platform_pci_setup_host_bridge(struct pci_host_bridge *bridge)
+{
+ if (pci_platform_pm && pci_platform_pm->setup_host_bridge)
+ return pci_platform_pm->setup_host_bridge(bridge);
+
+ return NULL;
+}
+
+void platform_pci_cleanup(void *pmdata)
+{
+ if (pci_platform_pm && pci_platform_pm->cleanup)
+ pci_platform_pm->cleanup(pmdata);
+}
+
+static inline bool platform_pci_can_wakeup(void *pmdata)
+{
+ if (pci_platform_pm && pci_platform_pm->can_wakeup)
+ return pci_platform_pm->can_wakeup(pmdata);
+
+ return false;
+}
+
+static inline int platform_pci_dev_wakeup(void *pmdata, bool enable)
+{
+ if (pci_platform_pm && pci_platform_pm->dev_wakeup)
+ return pci_platform_pm->dev_wakeup(pmdata, enable);
+
+ return -ENODEV;
+}
+
+static inline int platform_pci_bridge_wakeup(void *pmdata, bool enable)
+{
+ if (pci_platform_pm && pci_platform_pm->bridge_wakeup)
+ return pci_platform_pm->bridge_wakeup(pmdata, enable);
+
+ return -ENODEV;
+}
+
static inline int platform_pci_set_power_state(struct pci_dev *dev,
pci_power_t t)
{
@@ -610,14 +656,6 @@ static inline pci_power_t platform_pci_choose_state(struct pci_dev *dev)
return PCI_POWER_ERROR;
}
-static inline int platform_pci_set_wakeup(struct pci_dev *dev, bool enable)
-{
- if (pci_platform_pm && pci_platform_pm->set_wakeup)
- return pci_platform_pm->set_wakeup(dev, enable);
-
- return -ENODEV;
-}
-
static inline bool platform_pci_need_resume(struct pci_dev *dev)
{
if (pci_platform_pm && pci_platform_pm->need_resume)
@@ -1903,6 +1941,32 @@ void pci_pme_active(struct pci_dev *dev, bool enable)
}
EXPORT_SYMBOL(pci_pme_active);
+static int pci_set_wakeup(struct pci_dev *dev, bool enable)
+{
+ struct pci_bus *bus = dev->bus;
+ struct pci_host_bridge *bridge;
+
+ /* Handle per device wakeup */
+ if (platform_pci_can_wakeup(dev->pmdata))
+ return platform_pci_dev_wakeup(dev->pmdata, enable);
+
+ /* Find a parent which can handle wakeup */
+ while (bus->parent) {
+ if (platform_pci_can_wakeup(bus->self->pmdata))
+ return platform_pci_bridge_wakeup(bus->self->pmdata,
+ enable);
+
+ bus = bus->parent;
+ }
+
+ /* We have reached the root bus. */
+ bridge = to_pci_host_bridge(bus->bridge);
+ if (platform_pci_can_wakeup(bridge->pmdata))
+ return platform_pci_bridge_wakeup(bridge->pmdata, enable);
+
+ return 0;
+}
+
/**
* pci_enable_wake - enable PCI device as wakeup event source
* @dev: PCI device affected
@@ -1950,13 +2014,13 @@ int pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable)
pci_pme_active(dev, true);
else
ret = 1;
- error = platform_pci_set_wakeup(dev, true);
+ error = pci_set_wakeup(dev, true);
if (ret)
ret = error;
if (!ret)
dev->wakeup_prepared = true;
} else {
- platform_pci_set_wakeup(dev, false);
+ pci_set_wakeup(dev, false);
pci_pme_active(dev, false);
dev->wakeup_prepared = false;
}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 048668271014..dcefb9e759d7 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -47,21 +47,43 @@ int pci_probe_reset_function(struct pci_dev *dev);
* platform; to be used during system-wide transitions from a
* sleeping state to the working state and vice versa
*
- * @set_wakeup: enables/disables wakeup capability for the device
- *
* @need_resume: returns 'true' if the given device (which is currently
* suspended) needs to be resumed to be configured for system
* wakeup.
+ *
+ * @setup_dev: setup device's wakeup ability, alloc and return device's private
+ * pm data.
+ *
+ * @setup_host_bridge: setup host bridge's wakeup ability, alloc and return host
+ * bridge's private pm data.
+ *
+ * @cleanup: cleanup the private pm data and deinit wakeup ability.
+ *
+ * @can_wakeup: returns 'true' if given device is capable of waking up the
+ * system from a sleeping state.
+ *
+ * @dev_wakeup: enables/disables wakeup capability for the device.
+ *
+ * @bridge_wakeup: enables/disables wakeup capability for the bridge.
*/
struct pci_platform_pm_ops {
bool (*is_manageable)(struct pci_dev *dev);
int (*set_state)(struct pci_dev *dev, pci_power_t state);
pci_power_t (*get_state)(struct pci_dev *dev);
pci_power_t (*choose_state)(struct pci_dev *dev);
- int (*set_wakeup)(struct pci_dev *dev, bool enable);
bool (*need_resume)(struct pci_dev *dev);
+ void *(*setup_dev)(struct pci_dev *dev);
+ void *(*setup_host_bridge)(struct pci_host_bridge *bridge);
+ void (*cleanup)(void *pmdata);
+ bool (*can_wakeup)(void *pmdata);
+ int (*dev_wakeup)(void *pmdata, bool enable);
+ int (*bridge_wakeup)(void *pmdata, bool enable);
};
+void *platform_pci_setup_dev(struct pci_dev *dev);
+void *platform_pci_setup_host_bridge(struct pci_host_bridge *bridge);
+void platform_pci_cleanup(void *pmdata);
+
void pci_set_platform_pm(const struct pci_platform_pm_ops *ops);
void pci_update_current_state(struct pci_dev *dev, pci_power_t state);
void pci_power_up(struct pci_dev *dev);
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index cdc2f83c11c5..b12c552a5522 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -809,7 +809,13 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
err = device_register(&bus->dev);
if (err)
- goto unregister;
+ goto unregister_bridge;
+
+ bridge->pmdata = platform_pci_setup_host_bridge(bridge);
+ if (IS_ERR(bridge->pmdata)) {
+ err = PTR_ERR(bridge->pmdata);
+ goto unregister_bus;
+ }
pcibios_add_bus(bus);
@@ -853,7 +859,9 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
return 0;
-unregister:
+unregister_bus:
+ device_unregister(&bus->dev);
+unregister_bridge:
put_device(&bridge->dev);
device_unregister(&bridge->dev);
diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c
index 73a03d382590..d7a8cf1dc69f 100644
--- a/drivers/pci/remove.c
+++ b/drivers/pci/remove.c
@@ -153,6 +153,8 @@ void pci_remove_root_bus(struct pci_bus *bus)
if (!pci_is_root_bus(bus))
return;
+ platform_pci_cleanup(host_bridge->pmdata);
+
host_bridge = to_pci_host_bridge(bus->bridge);
list_for_each_entry_safe(child, tmp,
&bus->devices, bus_list)
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 80eaa2dbe3e9..628faa58c790 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -293,6 +293,7 @@ struct pci_dev {
struct pci_bus *subordinate; /* bus this device bridges to */
void *sysdata; /* hook for sys-specific extension */
+ void *pmdata; /* data for platform pm */
struct proc_dir_entry *procent; /* device entry in /proc/bus/pci */
struct pci_slot *slot; /* Physical slot this device is in */
@@ -467,6 +468,7 @@ struct pci_host_bridge {
struct pci_bus *bus; /* root bus */
struct pci_ops *ops;
void *sysdata;
+ void *pmdata; /* data for platform pm */
int busnr;
struct list_head windows; /* resource_entry */
u8 (*swizzle_irq)(struct pci_dev *, u8 *); /* platform IRQ swizzler */
--
2.11.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [RFC PATCH v9 7/7] PCI / PM: Add support for the PCIe WAKE# signal for OF
2017-10-27 7:17 [RFC PATCH v9 0/7] PCI: rockchip: Move PCIe WAKE# handling into pci core Jeffy Chen
` (2 preceding siblings ...)
2017-10-27 7:17 ` [RFC PATCH v9 6/7] PCI / PM: Move acpi wakeup code to pci core Jeffy Chen
@ 2017-10-27 7:17 ` Jeffy Chen
3 siblings, 0 replies; 5+ messages in thread
From: Jeffy Chen @ 2017-10-27 7:17 UTC (permalink / raw)
To: linux-kernel, bhelgaas
Cc: linux-pm, tony, shawn.lin, briannorris, rjw, dianders, Jeffy Chen,
linux-pci
Add pci-of.c to handle the PCIe WAKE# interrupt.
Also use the dedicated wakeirq infrastructure to simplify it.
Signed-off-by: Jeffy Chen <jeffy.chen@rock-chips.com>
---
Changes in v9:
Fix check error in .cleanup().
Move dedicated wakeirq setup to setup() callback and use
device_set_wakeup_enable() to enable/disable.
Changes in v8:
Add pci-of.c and use platform_pm_ops to handle the PCIe WAKE# signal.
Changes in v7:
Move PCIE_WAKE handling into pci core.
Changes in v6:
Fix device_init_wake error handling, and add some comments.
Changes in v5:
Rebase.
Changes in v3:
Fix error handling.
Changes in v2:
Use dev_pm_set_dedicated_wake_irq.
drivers/pci/Makefile | 2 +-
drivers/pci/pci-of.c | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 127 insertions(+), 1 deletion(-)
create mode 100644 drivers/pci/pci-of.c
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 66a21acad952..4f76dbdb024c 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -49,7 +49,7 @@ obj-$(CONFIG_PCI_ECAM) += ecam.o
obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
-obj-$(CONFIG_OF) += of.o
+obj-$(CONFIG_OF) += of.o pci-of.o
ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG
diff --git a/drivers/pci/pci-of.c b/drivers/pci/pci-of.c
new file mode 100644
index 000000000000..cc8ab979af2c
--- /dev/null
+++ b/drivers/pci/pci-of.c
@@ -0,0 +1,126 @@
+/*
+ * OF PCI PM support
+ *
+ * Copyright (c) 2017 Rockchip, Inc.
+ *
+ * Author: Jeffy Chen <jeffy.chen@rock-chips.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/of_irq.h>
+#include <linux/pci.h>
+#include <linux/pm_wakeirq.h>
+#include "pci.h"
+
+struct of_pci_pm_data {
+ struct device *dev;
+ unsigned int wakeup_irq;
+ atomic_t wakeup_cnt;
+};
+
+static void *of_pci_setup(struct device *dev)
+{
+ struct of_pci_pm_data *data;
+ int irq, ret;
+
+ if (!dev->of_node)
+ return NULL;
+
+ data = devm_kzalloc(dev, sizeof(struct of_pci_pm_data), GFP_KERNEL);
+ if (!data)
+ return ERR_PTR(-ENOMEM);
+
+ irq = of_irq_get_byname(dev->of_node, "wakeup");
+ if (irq < 0) {
+ if (irq == -EPROBE_DEFER)
+ return ERR_PTR(irq);
+
+ return NULL;
+ }
+
+ data->wakeup_irq = irq;
+ data->dev = dev;
+
+ device_init_wakeup(dev, true);
+ ret = dev_pm_set_dedicated_wake_irq(dev, irq);
+ if (ret < 0) {
+ device_init_wakeup(dev, false);
+ return NULL;
+ }
+ device_set_wakeup_enable(dev, false);
+
+ dev_info(dev, "Wakeup IRQ %d\n", irq);
+ return data;
+}
+
+static void *of_pci_setup_dev(struct pci_dev *pci_dev)
+{
+ return of_pci_setup(&pci_dev->dev);
+}
+
+static void *of_pci_setup_host_bridge(struct pci_host_bridge *bridge)
+{
+ return of_pci_setup(bridge->dev.parent);
+}
+
+static void of_pci_cleanup(void *pmdata)
+{
+ struct of_pci_pm_data *data = pmdata;
+
+ if (!IS_ERR_OR_NULL(data)) {
+ device_init_wakeup(data->dev, false);
+ dev_pm_clear_wake_irq(data->dev);
+ }
+}
+
+static bool of_pci_can_wakeup(void *pmdata)
+{
+ struct of_pci_pm_data *data = pmdata;
+
+ if (IS_ERR_OR_NULL(data))
+ return false;
+
+ return data->wakeup_irq > 0;
+}
+
+static int of_pci_dev_wakeup(void *pmdata, bool enable)
+{
+ struct of_pci_pm_data *data = pmdata;
+ struct device *dev = data->dev;
+
+ return device_set_wakeup_enable(dev, enable);
+}
+
+static int of_pci_bridge_wakeup(void *pmdata, bool enable)
+{
+ struct of_pci_pm_data *data = pmdata;
+
+ if (enable && atomic_inc_return(&data->wakeup_cnt) != 1)
+ return 0;
+
+ if (!enable && atomic_dec_return(&data->wakeup_cnt) != 0)
+ return 0;
+
+ return of_pci_dev_wakeup(pmdata, enable);
+}
+
+static const struct pci_platform_pm_ops of_pci_platform_pm = {
+ .setup_dev = of_pci_setup_dev,
+ .setup_host_bridge = of_pci_setup_host_bridge,
+ .cleanup = of_pci_cleanup,
+ .can_wakeup = of_pci_can_wakeup,
+ .dev_wakeup = of_pci_dev_wakeup,
+ .bridge_wakeup = of_pci_bridge_wakeup,
+};
+
+static int __init of_pci_init(void)
+{
+ pci_set_platform_pm(&of_pci_platform_pm);
+ return 0;
+}
+arch_initcall(of_pci_init);
--
2.11.0
^ permalink raw reply related [flat|nested] 5+ messages in thread