Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 28ad4b4e authored by Rafael J. Wysocki's avatar Rafael J. Wysocki
Browse files

Merge back PCI power management material for v5.3.

parents 471a739a b51033e0
Loading
Loading
Loading
Loading
+135 −0
Original line number Diff line number Diff line
@@ -42,6 +42,11 @@ ACPI_MODULE_NAME("power");
#define ACPI_POWER_RESOURCE_STATE_ON	0x01
#define ACPI_POWER_RESOURCE_STATE_UNKNOWN 0xFF

struct acpi_power_dependent_device {
	struct device *dev;
	struct list_head node;
};

struct acpi_power_resource {
	struct acpi_device device;
	struct list_head list_node;
@@ -51,6 +56,7 @@ struct acpi_power_resource {
	unsigned int ref_count;
	bool wakeup_enabled;
	struct mutex resource_lock;
	struct list_head dependents;
};

struct acpi_power_resource_entry {
@@ -232,8 +238,121 @@ static int acpi_power_get_list_state(struct list_head *list, int *state)
	return 0;
}

static int
acpi_power_resource_add_dependent(struct acpi_power_resource *resource,
				  struct device *dev)
{
	struct acpi_power_dependent_device *dep;
	int ret = 0;

	mutex_lock(&resource->resource_lock);
	list_for_each_entry(dep, &resource->dependents, node) {
		/* Only add it once */
		if (dep->dev == dev)
			goto unlock;
	}

	dep = kzalloc(sizeof(*dep), GFP_KERNEL);
	if (!dep) {
		ret = -ENOMEM;
		goto unlock;
	}

	dep->dev = dev;
	list_add_tail(&dep->node, &resource->dependents);
	dev_dbg(dev, "added power dependency to [%s]\n", resource->name);

unlock:
	mutex_unlock(&resource->resource_lock);
	return ret;
}

static void
acpi_power_resource_remove_dependent(struct acpi_power_resource *resource,
				     struct device *dev)
{
	struct acpi_power_dependent_device *dep;

	mutex_lock(&resource->resource_lock);
	list_for_each_entry(dep, &resource->dependents, node) {
		if (dep->dev == dev) {
			list_del(&dep->node);
			kfree(dep);
			dev_dbg(dev, "removed power dependency to [%s]\n",
				resource->name);
			break;
		}
	}
	mutex_unlock(&resource->resource_lock);
}

/**
 * acpi_device_power_add_dependent - Add dependent device of this ACPI device
 * @adev: ACPI device pointer
 * @dev: Dependent device
 *
 * If @adev has non-empty _PR0 the @dev is added as dependent device to all
 * power resources returned by it. This means that whenever these power
 * resources are turned _ON the dependent devices get runtime resumed. This
 * is needed for devices such as PCI to allow its driver to re-initialize
 * it after it went to D0uninitialized.
 *
 * If @adev does not have _PR0 this does nothing.
 *
 * Returns %0 in case of success and negative errno otherwise.
 */
int acpi_device_power_add_dependent(struct acpi_device *adev,
				    struct device *dev)
{
	struct acpi_power_resource_entry *entry;
	struct list_head *resources;
	int ret;

	if (!adev->flags.power_manageable)
		return 0;

	resources = &adev->power.states[ACPI_STATE_D0].resources;
	list_for_each_entry(entry, resources, node) {
		ret = acpi_power_resource_add_dependent(entry->resource, dev);
		if (ret)
			goto err;
	}

	return 0;

err:
	list_for_each_entry(entry, resources, node)
		acpi_power_resource_remove_dependent(entry->resource, dev);

	return ret;
}

/**
 * acpi_device_power_remove_dependent - Remove dependent device
 * @adev: ACPI device pointer
 * @dev: Dependent device
 *
 * Does the opposite of acpi_device_power_add_dependent() and removes the
 * dependent device if it is found. Can be called to @adev that does not
 * have _PR0 as well.
 */
void acpi_device_power_remove_dependent(struct acpi_device *adev,
					struct device *dev)
{
	struct acpi_power_resource_entry *entry;
	struct list_head *resources;

	if (!adev->flags.power_manageable)
		return;

	resources = &adev->power.states[ACPI_STATE_D0].resources;
	list_for_each_entry_reverse(entry, resources, node)
		acpi_power_resource_remove_dependent(entry->resource, dev);
}

static int __acpi_power_on(struct acpi_power_resource *resource)
{
	struct acpi_power_dependent_device *dep;
	acpi_status status = AE_OK;

	status = acpi_evaluate_object(resource->device.handle, "_ON", NULL, NULL);
@@ -243,6 +362,21 @@ static int __acpi_power_on(struct acpi_power_resource *resource)
	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Power resource [%s] turned on\n",
			  resource->name));

	/*
	 * If there are other dependents on this power resource we need to
	 * resume them now so that their drivers can re-initialize the
	 * hardware properly after it went back to D0.
	 */
	if (list_empty(&resource->dependents) ||
	    list_is_singular(&resource->dependents))
		return 0;

	list_for_each_entry(dep, &resource->dependents, node) {
		dev_dbg(dep->dev, "runtime resuming because [%s] turned on\n",
			resource->name);
		pm_request_resume(dep->dev);
	}

	return 0;
}

@@ -810,6 +944,7 @@ int acpi_add_power_resource(acpi_handle handle)
				ACPI_STA_DEFAULT);
	mutex_init(&resource->resource_lock);
	INIT_LIST_HEAD(&resource->list_node);
	INIT_LIST_HEAD(&resource->dependents);
	resource->name = device->pnp.bus_id;
	strcpy(acpi_device_name(device), ACPI_POWER_DEVICE_NAME);
	strcpy(acpi_device_class(device), ACPI_POWER_CLASS);
+13 −1
Original line number Diff line number Diff line
@@ -685,12 +685,21 @@ static pci_power_t acpi_pci_get_power_state(struct pci_dev *dev)
	if (!adev || !acpi_device_power_manageable(adev))
		return PCI_UNKNOWN;

	if (acpi_device_get_power(adev, &state) || state == ACPI_STATE_UNKNOWN)
	state = adev->power.state;
	if (state == ACPI_STATE_UNKNOWN)
		return PCI_UNKNOWN;

	return state_conv[state];
}

static void acpi_pci_refresh_power_state(struct pci_dev *dev)
{
	struct acpi_device *adev = ACPI_COMPANION(&dev->dev);

	if (adev && acpi_device_power_manageable(adev))
		acpi_device_update_power(adev, NULL);
}

static int acpi_pci_propagate_wakeup(struct pci_bus *bus, bool enable)
{
	while (bus->parent) {
@@ -748,6 +757,7 @@ 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,
	.refresh_state = acpi_pci_refresh_power_state,
	.choose_state = acpi_pci_choose_state,
	.set_wakeup = acpi_pci_wakeup,
	.need_resume = acpi_pci_need_resume,
@@ -901,6 +911,7 @@ static void pci_acpi_setup(struct device *dev)
		device_wakeup_enable(dev);

	acpi_pci_wakeup(pci_dev, false);
	acpi_device_power_add_dependent(adev, dev);
}

static void pci_acpi_cleanup(struct device *dev)
@@ -913,6 +924,7 @@ static void pci_acpi_cleanup(struct device *dev)

	pci_acpi_remove_pm_notifier(adev);
	if (adev->wakeup.flags.valid) {
		acpi_device_power_remove_dependent(adev, dev);
		if (pci_dev->bridge_d3)
			device_wakeup_disable(dev);

+26 −5
Original line number Diff line number Diff line
@@ -678,6 +678,7 @@ static bool pci_has_legacy_pm_support(struct pci_dev *pci_dev)
static int pci_pm_prepare(struct device *dev)
{
	struct device_driver *drv = dev->driver;
	struct pci_dev *pci_dev = to_pci_dev(dev);

	if (drv && drv->pm && drv->pm->prepare) {
		int error = drv->pm->prepare(dev);
@@ -687,7 +688,15 @@ static int pci_pm_prepare(struct device *dev)
		if (!error && dev_pm_test_driver_flags(dev, DPM_FLAG_SMART_PREPARE))
			return 0;
	}
	return pci_dev_keep_suspended(to_pci_dev(dev));
	if (pci_dev_need_resume(pci_dev))
		return 0;

	/*
	 * The PME setting needs to be adjusted here in case the direct-complete
	 * optimization is used with respect to this device.
	 */
	pci_dev_adjust_pme(pci_dev);
	return 1;
}

static void pci_pm_complete(struct device *dev)
@@ -701,7 +710,14 @@ static void pci_pm_complete(struct device *dev)
	if (pm_runtime_suspended(dev) && pm_resume_via_firmware()) {
		pci_power_t pre_sleep_state = pci_dev->current_state;

		pci_update_current_state(pci_dev, pci_dev->current_state);
		pci_refresh_power_state(pci_dev);
		/*
		 * On platforms with ACPI this check may also trigger for
		 * devices sharing power resources if one of those power
		 * resources has been activated as a result of a change of the
		 * power state of another device sharing it.  However, in that
		 * case it is also better to resume the device, in general.
		 */
		if (pci_dev->current_state < pre_sleep_state)
			pm_request_resume(dev);
	}
@@ -757,9 +773,11 @@ static int pci_pm_suspend(struct device *dev)
	 * better to resume the device from runtime suspend here.
	 */
	if (!dev_pm_test_driver_flags(dev, DPM_FLAG_SMART_SUSPEND) ||
	    !pci_dev_keep_suspended(pci_dev)) {
	    pci_dev_need_resume(pci_dev)) {
		pm_runtime_resume(dev);
		pci_dev->state_saved = false;
	} else {
		pci_dev_adjust_pme(pci_dev);
	}

	if (pm->suspend) {
@@ -1130,10 +1148,13 @@ static int pci_pm_poweroff(struct device *dev)

	/* The reason to do that is the same as in pci_pm_suspend(). */
	if (!dev_pm_test_driver_flags(dev, DPM_FLAG_SMART_SUSPEND) ||
	    !pci_dev_keep_suspended(pci_dev))
	    pci_dev_need_resume(pci_dev)) {
		pm_runtime_resume(dev);

		pci_dev->state_saved = false;
	} else {
		pci_dev_adjust_pme(pci_dev);
	}

	if (pm->poweroff) {
		int error;

+82 −34
Original line number Diff line number Diff line
@@ -777,6 +777,12 @@ 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;
}

static inline void platform_pci_refresh_power_state(struct pci_dev *dev)
{
	if (pci_platform_pm && pci_platform_pm->refresh_state)
		pci_platform_pm->refresh_state(dev);
}

static inline pci_power_t platform_pci_choose_state(struct pci_dev *dev)
{
	return pci_platform_pm ?
@@ -937,6 +943,21 @@ void pci_update_current_state(struct pci_dev *dev, pci_power_t state)
	}
}

/**
 * pci_refresh_power_state - Refresh the given device's power state data
 * @dev: Target PCI device.
 *
 * Ask the platform to refresh the devices power state information and invoke
 * pci_update_current_state() to update its current PCI power state.
 */
void pci_refresh_power_state(struct pci_dev *dev)
{
	if (platform_pci_power_manageable(dev))
		platform_pci_refresh_power_state(dev);

	pci_update_current_state(dev, dev->current_state);
}

/**
 * pci_power_up - Put the given device into D0 forcibly
 * @dev: PCI device to power up
@@ -1004,15 +1025,10 @@ static void __pci_start_power_transition(struct pci_dev *dev, pci_power_t state)
	if (state == PCI_D0) {
		pci_platform_power_transition(dev, PCI_D0);
		/*
		 * Mandatory power management transition delays, see
		 * PCI Express Base Specification Revision 2.0 Section
		 * 6.6.1: Conventional Reset.  Do not delay for
		 * devices powered on/off by corresponding bridge,
		 * because have already delayed for the bridge.
		 * Mandatory power management transition delays are
		 * handled in the PCIe portdrv resume hooks.
		 */
		if (dev->runtime_d3cold) {
			if (dev->d3cold_delay && !dev->imm_ready)
				msleep(dev->d3cold_delay);
			/*
			 * When powering on a bridge from D3cold, the
			 * whole hierarchy may be powered on into
@@ -2065,6 +2081,13 @@ static void pci_pme_list_scan(struct work_struct *work)
			 */
			if (bridge && bridge->current_state != PCI_D0)
				continue;
			/*
			 * If the device is in D3cold it should not be
			 * polled either.
			 */
			if (pme_dev->dev->current_state == PCI_D3cold)
				continue;

			pci_pme_wakeup(pme_dev->dev, NULL);
		} else {
			list_del(&pme_dev->list);
@@ -2459,45 +2482,56 @@ bool pci_dev_run_wake(struct pci_dev *dev)
EXPORT_SYMBOL_GPL(pci_dev_run_wake);

/**
 * pci_dev_keep_suspended - Check if the device can stay in the suspended state.
 * pci_dev_need_resume - Check if it is necessary to resume the device.
 * @pci_dev: Device to check.
 *
 * Return 'true' if the device is runtime-suspended, it doesn't have to be
 * Return 'true' if the device is not runtime-suspended or it has to be
 * reconfigured due to wakeup settings difference between system and runtime
 * suspend and the current power state of it is suitable for the upcoming
 * (system) transition.
 *
 * If the device is not configured for system wakeup, disable PME for it before
 * returning 'true' to prevent it from waking up the system unnecessarily.
 * suspend, or the current power state of it is not suitable for the upcoming
 * (system-wide) transition.
 */
bool pci_dev_keep_suspended(struct pci_dev *pci_dev)
bool pci_dev_need_resume(struct pci_dev *pci_dev)
{
	struct device *dev = &pci_dev->dev;
	bool wakeup = device_may_wakeup(dev);
	pci_power_t target_state;

	if (!pm_runtime_suspended(dev)
	    || pci_target_state(pci_dev, wakeup) != pci_dev->current_state
	    || platform_pci_need_resume(pci_dev))
		return false;
	if (!pm_runtime_suspended(dev) || platform_pci_need_resume(pci_dev))
		return true;

	target_state = pci_target_state(pci_dev, device_may_wakeup(dev));

	/*
	 * At this point the device is good to go unless it's been configured
	 * to generate PME at the runtime suspend time, but it is not supposed
	 * to wake up the system.  In that case, simply disable PME for it
	 * (it will have to be re-enabled on exit from system resume).
	 * If the earlier platform check has not triggered, D3cold is just power
	 * removal on top of D3hot, so no need to resume the device in that
	 * case.
	 */
	return target_state != pci_dev->current_state &&
		target_state != PCI_D3cold &&
		pci_dev->current_state != PCI_D3hot;
}

/**
 * pci_dev_adjust_pme - Adjust PME setting for a suspended device.
 * @pci_dev: Device to check.
 *
 * If the device is suspended and it is not configured for system wakeup,
 * disable PME for it to prevent it from waking up the system unnecessarily.
 *
	 * If the device's power state is D3cold and the platform check above
	 * hasn't triggered, the device's configuration is suitable and we don't
	 * need to manipulate it at all.
 * Note that if the device's power state is D3cold and the platform check in
 * pci_dev_need_resume() has not triggered, the device's configuration need not
 * be changed.
 */
void pci_dev_adjust_pme(struct pci_dev *pci_dev)
{
	struct device *dev = &pci_dev->dev;

	spin_lock_irq(&dev->power.lock);

	if (pm_runtime_suspended(dev) && pci_dev->current_state < PCI_D3cold &&
	    !wakeup)
	if (pm_runtime_suspended(dev) && !device_may_wakeup(dev) &&
	    pci_dev->current_state < PCI_D3cold)
		__pci_pme_active(pci_dev, false);

	spin_unlock_irq(&dev->power.lock);
	return true;
}

/**
@@ -4568,14 +4602,16 @@ static int pci_pm_reset(struct pci_dev *dev, int probe)

	return pci_dev_wait(dev, "PM D3->D0", PCIE_RESET_READY_POLL_MS);
}

/**
 * pcie_wait_for_link - Wait until link is active or inactive
 * pcie_wait_for_link_delay - Wait until link is active or inactive
 * @pdev: Bridge device
 * @active: waiting for active or inactive?
 * @delay: Delay to wait after link has become active (in ms)
 *
 * Use this to wait till link becomes active or inactive.
 */
bool pcie_wait_for_link(struct pci_dev *pdev, bool active)
bool pcie_wait_for_link_delay(struct pci_dev *pdev, bool active, int delay)
{
	int timeout = 1000;
	bool ret;
@@ -4612,13 +4648,25 @@ bool pcie_wait_for_link(struct pci_dev *pdev, bool active)
		timeout -= 10;
	}
	if (active && ret)
		msleep(100);
		msleep(delay);
	else if (ret != active)
		pci_info(pdev, "Data Link Layer Link Active not %s in 1000 msec\n",
			active ? "set" : "cleared");
	return ret == active;
}

/**
 * pcie_wait_for_link - Wait until link is active or inactive
 * @pdev: Bridge device
 * @active: waiting for active or inactive?
 *
 * Use this to wait till link becomes active or inactive.
 */
bool pcie_wait_for_link(struct pci_dev *pdev, bool active)
{
	return pcie_wait_for_link_delay(pdev, active, 100);
}

void pci_reset_secondary_bus(struct pci_dev *dev)
{
	u16 ctrl;
+7 −1
Original line number Diff line number Diff line
@@ -51,6 +51,8 @@ int pci_bus_error_reset(struct pci_dev *dev);
 *
 * @get_state: queries the platform firmware for a device's current power state
 *
 * @refresh_state: asks the platform to refresh the device's power state data
 *
 * @choose_state: returns PCI power state of given device preferred by the
 *		  platform; to be used during system-wide transitions from a
 *		  sleeping state to the working state and vice versa
@@ -69,6 +71,7 @@ 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);
	void (*refresh_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);
@@ -76,13 +79,15 @@ struct pci_platform_pm_ops {

int 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_refresh_power_state(struct pci_dev *dev);
void pci_power_up(struct pci_dev *dev);
void pci_disable_enabled_device(struct pci_dev *dev);
int pci_finish_runtime_suspend(struct pci_dev *dev);
void pcie_clear_root_pme_status(struct pci_dev *dev);
int __pci_pme_wakeup(struct pci_dev *dev, void *ign);
void pci_pme_restore(struct pci_dev *dev);
bool pci_dev_keep_suspended(struct pci_dev *dev);
bool pci_dev_need_resume(struct pci_dev *dev);
void pci_dev_adjust_pme(struct pci_dev *dev);
void pci_dev_complete_resume(struct pci_dev *pci_dev);
void pci_config_pm_runtime_get(struct pci_dev *dev);
void pci_config_pm_runtime_put(struct pci_dev *dev);
@@ -493,6 +498,7 @@ static inline int pci_dev_specific_disable_acs_redir(struct pci_dev *dev)
void pcie_do_recovery(struct pci_dev *dev, enum pci_channel_state state,
		      u32 service);

bool pcie_wait_for_link_delay(struct pci_dev *pdev, bool active, int delay);
bool pcie_wait_for_link(struct pci_dev *pdev, bool active);
#ifdef CONFIG_PCIEASPM
void pcie_aspm_init_link_state(struct pci_dev *pdev);
Loading