summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/acpi/device_pm.c56
-rw-r--r--drivers/acpi/internal.h1
-rw-r--r--drivers/acpi/power.c219
-rw-r--r--drivers/acpi/scan.c8
-rw-r--r--drivers/ata/libata-acpi.c18
-rw-r--r--drivers/pci/pci-acpi.c2
-rw-r--r--include/acpi/acpi_bus.h9
7 files changed, 158 insertions, 155 deletions
diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c
index 995019063f64..8be4b29e38aa 100644
--- a/drivers/acpi/device_pm.c
+++ b/drivers/acpi/device_pm.c
@@ -665,3 +665,59 @@ void acpi_dev_pm_detach(struct device *dev, bool power_off)
}
}
EXPORT_SYMBOL_GPL(acpi_dev_pm_detach);
+
+/**
+ * acpi_dev_pm_add_dependent - Add physical device depending for PM.
+ * @handle: Handle of ACPI device node.
+ * @depdev: Device depending on that node for PM.
+ */
+void acpi_dev_pm_add_dependent(acpi_handle handle, struct device *depdev)
+{
+ struct acpi_device_physical_node *dep;
+ struct acpi_device *adev;
+
+ if (!depdev || acpi_bus_get_device(handle, &adev))
+ return;
+
+ mutex_lock(&adev->physical_node_lock);
+
+ list_for_each_entry(dep, &adev->power_dependent, node)
+ if (dep->dev == depdev)
+ goto out;
+
+ dep = kzalloc(sizeof(*dep), GFP_KERNEL);
+ if (dep) {
+ dep->dev = depdev;
+ list_add_tail(&dep->node, &adev->power_dependent);
+ }
+
+ out:
+ mutex_unlock(&adev->physical_node_lock);
+}
+EXPORT_SYMBOL_GPL(acpi_dev_pm_add_dependent);
+
+/**
+ * acpi_dev_pm_remove_dependent - Remove physical device depending for PM.
+ * @handle: Handle of ACPI device node.
+ * @depdev: Device depending on that node for PM.
+ */
+void acpi_dev_pm_remove_dependent(acpi_handle handle, struct device *depdev)
+{
+ struct acpi_device_physical_node *dep;
+ struct acpi_device *adev;
+
+ if (!depdev || acpi_bus_get_device(handle, &adev))
+ return;
+
+ mutex_lock(&adev->physical_node_lock);
+
+ list_for_each_entry(dep, &adev->power_dependent, node)
+ if (dep->dev == depdev) {
+ list_del(&dep->node);
+ kfree(dep);
+ break;
+ }
+
+ mutex_unlock(&adev->physical_node_lock);
+}
+EXPORT_SYMBOL_GPL(acpi_dev_pm_remove_dependent);
diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h
index e050254ae143..b79b4258bd6b 100644
--- a/drivers/acpi/internal.h
+++ b/drivers/acpi/internal.h
@@ -38,6 +38,7 @@ static inline void acpi_debugfs_init(void) { return; }
Power Resource
-------------------------------------------------------------------------- */
int acpi_power_init(void);
+void acpi_power_add_remove_device(struct acpi_device *adev, bool add);
int acpi_device_sleep_wake(struct acpi_device *dev,
int enable, int sleep_state, int dev_state);
int acpi_power_get_inferred_state(struct acpi_device *device, int *state);
diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c
index 6e7b9d523812..659386c4f0cb 100644
--- a/drivers/acpi/power.c
+++ b/drivers/acpi/power.c
@@ -83,31 +83,20 @@ static struct acpi_driver acpi_power_driver = {
.drv.pm = &acpi_power_pm,
};
-/*
- * A power managed device
- * A device may rely on multiple power resources.
- * */
-struct acpi_power_managed_device {
- struct device *dev; /* The physical device */
- acpi_handle *handle;
-};
-
-struct acpi_power_resource_device {
- struct acpi_power_managed_device *device;
- struct acpi_power_resource_device *next;
+struct acpi_power_dependent_device {
+ struct list_head node;
+ struct acpi_device *adev;
+ struct work_struct work;
};
struct acpi_power_resource {
- struct acpi_device * device;
+ struct acpi_device *device;
+ struct list_head dependent;
acpi_bus_id name;
u32 system_level;
u32 order;
unsigned int ref_count;
struct mutex resource_lock;
-
- /* List of devices relying on this power resource */
- struct acpi_power_resource_device *devices;
- struct mutex devices_lock;
};
static struct list_head acpi_power_resource_list;
@@ -207,21 +196,30 @@ static int acpi_power_get_list_state(struct acpi_handle_list *list, int *state)
return 0;
}
-/* Resume the device when all power resources in _PR0 are on */
-static void acpi_power_on_device(struct acpi_power_managed_device *device)
+static void acpi_power_resume_dependent(struct work_struct *work)
{
- struct acpi_device *acpi_dev;
- acpi_handle handle = device->handle;
+ struct acpi_power_dependent_device *dep;
+ struct acpi_device_physical_node *pn;
+ struct acpi_device *adev;
int state;
- if (acpi_bus_get_device(handle, &acpi_dev))
+ dep = container_of(work, struct acpi_power_dependent_device, work);
+ adev = dep->adev;
+ if (acpi_power_get_inferred_state(adev, &state))
return;
- if(acpi_power_get_inferred_state(acpi_dev, &state))
+ if (state > ACPI_STATE_D0)
return;
- if (state == ACPI_STATE_D0 && pm_runtime_suspended(device->dev))
- pm_request_resume(device->dev);
+ mutex_lock(&adev->physical_node_lock);
+
+ list_for_each_entry(pn, &adev->physical_node_list, node)
+ pm_request_resume(pn->dev);
+
+ list_for_each_entry(pn, &adev->power_dependent, node)
+ pm_request_resume(pn->dev);
+
+ mutex_unlock(&adev->physical_node_lock);
}
static int __acpi_power_on(struct acpi_power_resource *resource)
@@ -244,9 +242,7 @@ static int __acpi_power_on(struct acpi_power_resource *resource)
static int acpi_power_on(acpi_handle handle)
{
int result = 0;
- bool resume_device = false;
struct acpi_power_resource *resource = NULL;
- struct acpi_power_resource_device *device_list;
result = acpi_power_get_context(handle, &resource);
if (result)
@@ -260,26 +256,17 @@ static int acpi_power_on(acpi_handle handle)
resource->name));
} else {
result = __acpi_power_on(resource);
- if (result)
+ if (result) {
resource->ref_count--;
- else
- resume_device = true;
- }
+ } else {
+ struct acpi_power_dependent_device *dep;
- mutex_unlock(&resource->resource_lock);
-
- if (!resume_device)
- return result;
-
- mutex_lock(&resource->devices_lock);
-
- device_list = resource->devices;
- while (device_list) {
- acpi_power_on_device(device_list->device);
- device_list = device_list->next;
+ list_for_each_entry(dep, &resource->dependent, node)
+ schedule_work(&dep->work);
+ }
}
- mutex_unlock(&resource->devices_lock);
+ mutex_unlock(&resource->resource_lock);
return result;
}
@@ -357,119 +344,81 @@ static int acpi_power_on_list(struct acpi_handle_list *list)
return result;
}
-static void __acpi_power_resource_unregister_device(struct device *dev,
- acpi_handle res_handle)
+static void acpi_power_add_dependent(acpi_handle rhandle,
+ struct acpi_device *adev)
{
- struct acpi_power_resource *resource = NULL;
- struct acpi_power_resource_device *prev, *curr;
+ struct acpi_power_dependent_device *dep;
+ struct acpi_power_resource *resource;
- if (acpi_power_get_context(res_handle, &resource))
+ if (!rhandle || !adev || acpi_power_get_context(rhandle, &resource))
return;
- mutex_lock(&resource->devices_lock);
- prev = NULL;
- curr = resource->devices;
- while (curr) {
- if (curr->device->dev == dev) {
- if (!prev)
- resource->devices = curr->next;
- else
- prev->next = curr->next;
-
- kfree(curr);
- break;
- }
-
- prev = curr;
- curr = curr->next;
- }
- mutex_unlock(&resource->devices_lock);
-}
-
-/* Unlink dev from all power resources in _PR0 */
-void acpi_power_resource_unregister_device(struct device *dev, acpi_handle handle)
-{
- struct acpi_device *acpi_dev;
- struct acpi_handle_list *list;
- int i;
+ mutex_lock(&resource->resource_lock);
- if (!dev || !handle)
- return;
+ list_for_each_entry(dep, &resource->dependent, node)
+ if (dep->adev == adev)
+ goto out;
- if (acpi_bus_get_device(handle, &acpi_dev))
- return;
+ dep = kzalloc(sizeof(*dep), GFP_KERNEL);
+ if (!dep)
+ goto out;
- list = &acpi_dev->power.states[ACPI_STATE_D0].resources;
+ dep->adev = adev;
+ INIT_WORK(&dep->work, acpi_power_resume_dependent);
+ list_add_tail(&dep->node, &resource->dependent);
- for (i = 0; i < list->count; i++)
- __acpi_power_resource_unregister_device(dev,
- list->handles[i]);
+ out:
+ mutex_unlock(&resource->resource_lock);
}
-EXPORT_SYMBOL_GPL(acpi_power_resource_unregister_device);
-static int __acpi_power_resource_register_device(
- struct acpi_power_managed_device *powered_device, acpi_handle handle)
+static void acpi_power_remove_dependent(acpi_handle rhandle,
+ struct acpi_device *adev)
{
- struct acpi_power_resource *resource = NULL;
- struct acpi_power_resource_device *power_resource_device;
- int result;
+ struct acpi_power_dependent_device *dep;
+ struct acpi_power_resource *resource;
+ struct work_struct *work = NULL;
- result = acpi_power_get_context(handle, &resource);
- if (result)
- return result;
+ if (!rhandle || !adev || acpi_power_get_context(rhandle, &resource))
+ return;
- power_resource_device = kzalloc(
- sizeof(*power_resource_device), GFP_KERNEL);
- if (!power_resource_device)
- return -ENOMEM;
+ mutex_lock(&resource->resource_lock);
- power_resource_device->device = powered_device;
+ list_for_each_entry(dep, &resource->dependent, node)
+ if (dep->adev == adev) {
+ list_del(&dep->node);
+ work = &dep->work;
+ break;
+ }
- mutex_lock(&resource->devices_lock);
- power_resource_device->next = resource->devices;
- resource->devices = power_resource_device;
- mutex_unlock(&resource->devices_lock);
+ mutex_unlock(&resource->resource_lock);
- return 0;
+ if (work) {
+ cancel_work_sync(work);
+ kfree(dep);
+ }
}
-/* Link dev to all power resources in _PR0 */
-int acpi_power_resource_register_device(struct device *dev, acpi_handle handle)
+void acpi_power_add_remove_device(struct acpi_device *adev, bool add)
{
- struct acpi_device *acpi_dev;
- struct acpi_handle_list *list;
- struct acpi_power_managed_device *powered_device;
- int i, ret;
+ if (adev->power.flags.power_resources) {
+ struct acpi_device_power_state *ps;
+ int j;
- if (!dev || !handle)
- return -ENODEV;
-
- ret = acpi_bus_get_device(handle, &acpi_dev);
- if (ret || !acpi_dev->power.flags.power_resources)
- return -ENODEV;
+ ps = &adev->power.states[ACPI_STATE_D0];
+ for (j = 0; j < ps->resources.count; j++) {
+ acpi_handle rhandle = ps->resources.handles[j];
- powered_device = kzalloc(sizeof(*powered_device), GFP_KERNEL);
- if (!powered_device)
- return -ENOMEM;
-
- powered_device->dev = dev;
- powered_device->handle = handle;
-
- list = &acpi_dev->power.states[ACPI_STATE_D0].resources;
-
- for (i = 0; i < list->count; i++) {
- ret = __acpi_power_resource_register_device(powered_device,
- list->handles[i]);
-
- if (ret) {
- acpi_power_resource_unregister_device(dev, handle);
- break;
+ if (add)
+ acpi_power_add_dependent(rhandle, adev);
+ else
+ acpi_power_remove_dependent(rhandle, adev);
}
}
-
- return ret;
}
-EXPORT_SYMBOL_GPL(acpi_power_resource_register_device);
+
+/* --------------------------------------------------------------------------
+ Device Power Management
+ -------------------------------------------------------------------------- */
/**
* acpi_device_sleep_wake - execute _DSW (Device Sleep Wake) or (deprecated in
@@ -623,10 +572,6 @@ int acpi_disable_wakeup_device_power(struct acpi_device *dev)
return err;
}
-/* --------------------------------------------------------------------------
- Device Power Management
- -------------------------------------------------------------------------- */
-
int acpi_power_get_inferred_state(struct acpi_device *device, int *state)
{
int result = 0;
@@ -725,7 +670,7 @@ static int acpi_power_add(struct acpi_device *device)
resource->device = device;
mutex_init(&resource->resource_lock);
- mutex_init(&resource->devices_lock);
+ INIT_LIST_HEAD(&resource->dependent);
strcpy(resource->name, device->pnp.bus_id);
strcpy(acpi_device_name(device), ACPI_POWER_DEVICE_NAME);
strcpy(acpi_device_class(device), ACPI_POWER_CLASS);
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index 7d164a966b0d..c6d60910e8a8 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -633,6 +633,7 @@ static int acpi_device_register(struct acpi_device *device)
INIT_LIST_HEAD(&device->wakeup_list);
INIT_LIST_HEAD(&device->physical_node_list);
mutex_init(&device->physical_node_lock);
+ INIT_LIST_HEAD(&device->power_dependent);
new_bus_id = kzalloc(sizeof(struct acpi_device_bus_id), GFP_KERNEL);
if (!new_bus_id) {
@@ -706,8 +707,14 @@ static void acpi_device_unregister(struct acpi_device *device)
acpi_detach_data(device->handle, acpi_bus_data_handler);
+ acpi_power_add_remove_device(device, false);
acpi_device_remove_files(device);
device_unregister(&device->dev);
+ /*
+ * Drop the reference counts of all power resources the device depends
+ * on and turn off the ones that have no more references.
+ */
+ acpi_power_transition(device, ACPI_STATE_D3_COLD);
}
/* --------------------------------------------------------------------------
@@ -1441,6 +1448,7 @@ static int acpi_add_single_object(struct acpi_device **child,
end:
if (!result) {
+ acpi_power_add_remove_device(device, true);
acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Adding %s [%s] parent %s\n", dev_name(&device->dev),
diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c
index ef01ac07502e..6fc67f7efb22 100644
--- a/drivers/ata/libata-acpi.c
+++ b/drivers/ata/libata-acpi.c
@@ -1029,30 +1029,20 @@ static void ata_acpi_register_power_resource(struct ata_device *dev)
{
struct scsi_device *sdev = dev->sdev;
acpi_handle handle;
- struct device *device;
handle = ata_dev_acpi_handle(dev);
- if (!handle)
- return;
-
- device = &sdev->sdev_gendev;
-
- acpi_power_resource_register_device(device, handle);
+ if (handle)
+ acpi_dev_pm_remove_dependent(handle, &sdev->sdev_gendev);
}
static void ata_acpi_unregister_power_resource(struct ata_device *dev)
{
struct scsi_device *sdev = dev->sdev;
acpi_handle handle;
- struct device *device;
handle = ata_dev_acpi_handle(dev);
- if (!handle)
- return;
-
- device = &sdev->sdev_gendev;
-
- acpi_power_resource_unregister_device(device, handle);
+ if (handle)
+ acpi_dev_pm_remove_dependent(handle, &sdev->sdev_gendev);
}
void ata_acpi_bind(struct ata_device *dev)
diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c
index 42736e213f25..e407c61559ca 100644
--- a/drivers/pci/pci-acpi.c
+++ b/drivers/pci/pci-acpi.c
@@ -345,7 +345,6 @@ static void pci_acpi_setup(struct device *dev)
acpi_pci_irq_add_prt(handle, pci_domain_nr(pci_dev->bus), bus);
}
- acpi_power_resource_register_device(dev, handle);
if (acpi_bus_get_device(handle, &adev) || !adev->wakeup.flags.valid)
return;
@@ -368,7 +367,6 @@ static void pci_acpi_cleanup(struct device *dev)
device_set_run_wake(dev, false);
pci_acpi_remove_pm_notifier(adev);
}
- acpi_power_resource_unregister_device(dev, handle);
if (pci_dev->subordinate)
acpi_pci_irq_del_prt(pci_domain_nr(pci_dev->bus),
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index 567851b4f043..29a1badfca55 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -279,6 +279,7 @@ struct acpi_device {
struct list_head physical_node_list;
struct mutex physical_node_lock;
DECLARE_BITMAP(physical_node_id_bitmap, ACPI_MAX_PHYSICAL_NODE);
+ struct list_head power_dependent;
};
static inline void *acpi_driver_data(struct acpi_device *d)
@@ -334,8 +335,6 @@ int acpi_device_set_power(struct acpi_device *device, int state);
int acpi_bus_update_power(acpi_handle handle, int *state_p);
bool acpi_bus_power_manageable(acpi_handle handle);
bool acpi_bus_can_wakeup(acpi_handle handle);
-int acpi_power_resource_register_device(struct device *dev, acpi_handle handle);
-void acpi_power_resource_unregister_device(struct device *dev, acpi_handle handle);
#ifdef CONFIG_ACPI_PROC_EVENT
int acpi_bus_generate_proc_event(struct acpi_device *device, u8 type, int data);
int acpi_bus_generate_proc_event4(const char *class, const char *bid, u8 type, int data);
@@ -414,6 +413,8 @@ acpi_status acpi_remove_pm_notifier(struct acpi_device *adev,
int acpi_device_power_state(struct device *dev, struct acpi_device *adev,
u32 target_state, int d_max_in, int *d_min_p);
int acpi_pm_device_sleep_state(struct device *, int *, int);
+void acpi_dev_pm_add_dependent(acpi_handle handle, struct device *depdev);
+void acpi_dev_pm_remove_dependent(acpi_handle handle, struct device *depdev);
#else
static inline acpi_status acpi_add_pm_notifier(struct acpi_device *adev,
acpi_notify_handler handler,
@@ -443,6 +444,10 @@ static inline int acpi_pm_device_sleep_state(struct device *d, int *p, int m)
{
return __acpi_device_power_state(m, p);
}
+static inline void acpi_dev_pm_add_dependent(acpi_handle handle,
+ struct device *depdev) {}
+static inline void acpi_dev_pm_remove_dependent(acpi_handle handle,
+ struct device *depdev) {}
#endif
#ifdef CONFIG_PM_RUNTIME