ACPI / PCI / PM: Fix device PM regression related to D3hot/D3cold
[linux-flexiantxendom0-3.2.10.git] / drivers / acpi / power.c
index 0cb4eab..0500f71 100644 (file)
 #include <linux/init.h>
 #include <linux/types.h>
 #include <linux/slab.h>
+#include <linux/pm_runtime.h>
 #include <acpi/acpi_bus.h>
 #include <acpi/acpi_drivers.h>
 #include "sleep.h"
+#include "internal.h"
 
 #define PREFIX "ACPI: "
 
@@ -56,9 +58,6 @@ ACPI_MODULE_NAME("power");
 #define ACPI_POWER_RESOURCE_STATE_ON   0x01
 #define ACPI_POWER_RESOURCE_STATE_UNKNOWN 0xFF
 
-int acpi_power_nocheck;
-module_param_named(power_nocheck, acpi_power_nocheck, bool, 000);
-
 static int acpi_power_add(struct acpi_device *device);
 static int acpi_power_remove(struct acpi_device *device, int type);
 static int acpi_power_resume(struct acpi_device *device);
@@ -80,6 +79,20 @@ static struct acpi_driver acpi_power_driver = {
                },
 };
 
+/*
+ * 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_resource {
        struct acpi_device * device;
        acpi_bus_id name;
@@ -87,6 +100,9 @@ struct acpi_power_resource {
        u32 order;
        unsigned int ref_count;
        struct mutex resource_lock;
+
+       /* List of devices relying on this power resource */
+       struct acpi_power_resource_device *devices;
 };
 
 static struct list_head acpi_power_resource_list;
@@ -148,9 +164,8 @@ static int acpi_power_get_state(acpi_handle handle, int *state)
 
 static int acpi_power_get_list_state(struct acpi_handle_list *list, int *state)
 {
-       int result = 0, state1;
-       u32 i = 0;
-
+       int cur_state;
+       int i = 0;
 
        if (!list || !state)
                return -EINVAL;
@@ -158,29 +173,55 @@ static int acpi_power_get_list_state(struct acpi_handle_list *list, int *state)
        /* The state of the list is 'on' IFF all resources are 'on'. */
 
        for (i = 0; i < list->count; i++) {
-               /*
-                * The state of the power resource can be obtained by
-                * using the ACPI handle. In such case it is unnecessary to
-                * get the Power resource first and then get its state again.
-                */
-               result = acpi_power_get_state(list->handles[i], &state1);
+               struct acpi_power_resource *resource;
+               acpi_handle handle = list->handles[i];
+               int result;
+
+               result = acpi_power_get_context(handle, &resource);
                if (result)
                        return result;
 
-               *state = state1;
+               mutex_lock(&resource->resource_lock);
+
+               result = acpi_power_get_state(handle, &cur_state);
 
-               if (*state != ACPI_POWER_RESOURCE_STATE_ON)
+               mutex_unlock(&resource->resource_lock);
+
+               if (result)
+                       return result;
+
+               if (cur_state != ACPI_POWER_RESOURCE_STATE_ON)
                        break;
        }
 
        ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource list is %s\n",
-                         *state ? "on" : "off"));
+                         cur_state ? "on" : "off"));
 
-       return result;
+       *state = cur_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)
+{
+       struct acpi_device *acpi_dev;
+       acpi_handle handle = device->handle;
+       int state;
+
+       if (acpi_bus_get_device(handle, &acpi_dev))
+               return;
+
+       if(acpi_power_get_inferred_state(acpi_dev, &state))
+               return;
+
+       if (state == ACPI_STATE_D0 && pm_runtime_suspended(device->dev))
+               pm_request_resume(device->dev);
 }
 
 static int __acpi_power_on(struct acpi_power_resource *resource)
 {
+       struct acpi_power_resource_device *device_list = resource->devices;
        acpi_status status = AE_OK;
 
        status = acpi_evaluate_object(resource->device->handle, "_ON", NULL, NULL);
@@ -193,6 +234,12 @@ static int __acpi_power_on(struct acpi_power_resource *resource)
        ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Power resource [%s] turned on\n",
                          resource->name));
 
+       while (device_list) {
+               acpi_power_on_device(device_list->device);
+
+               device_list = device_list->next;
+       }
+
        return 0;
 }
 
@@ -222,7 +269,7 @@ static int acpi_power_on(acpi_handle handle)
        return result;
 }
 
-static int acpi_power_off_device(acpi_handle handle)
+static int acpi_power_off(acpi_handle handle)
 {
        int result = 0;
        acpi_status status = AE_OK;
@@ -271,7 +318,7 @@ static void __acpi_power_off_list(struct acpi_handle_list *list, int num_res)
        int i;
 
        for (i = num_res - 1; i >= 0 ; i--)
-               acpi_power_off_device(list->handles[i]);
+               acpi_power_off(list->handles[i]);
 }
 
 static void acpi_power_off_list(struct acpi_handle_list *list)
@@ -295,6 +342,125 @@ 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)
+{
+       struct acpi_power_resource *resource = NULL;
+       struct acpi_power_resource_device *prev, *curr;
+
+       if (acpi_power_get_context(res_handle, &resource))
+               return;
+
+       mutex_lock(&resource->resource_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->resource_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;
+
+       if (!dev || !handle)
+               return;
+
+       if (acpi_bus_get_device(handle, &acpi_dev))
+               return;
+
+       list = &acpi_dev->power.states[ACPI_STATE_D0].resources;
+
+       for (i = 0; i < list->count; i++)
+               __acpi_power_resource_unregister_device(dev,
+                       list->handles[i]);
+}
+
+static int __acpi_power_resource_register_device(
+       struct acpi_power_managed_device *powered_device, acpi_handle handle)
+{
+       struct acpi_power_resource *resource = NULL;
+       struct acpi_power_resource_device *power_resource_device;
+       int result;
+
+       result = acpi_power_get_context(handle, &resource);
+       if (result)
+               return result;
+
+       power_resource_device = kzalloc(
+               sizeof(*power_resource_device), GFP_KERNEL);
+       if (!power_resource_device)
+               return -ENOMEM;
+
+       power_resource_device->device = powered_device;
+
+       mutex_lock(&resource->resource_lock);
+       power_resource_device->next = resource->devices;
+       resource->devices = power_resource_device;
+       mutex_unlock(&resource->resource_lock);
+
+       return 0;
+}
+
+/* Link dev to all power resources in _PR0 */
+int acpi_power_resource_register_device(struct device *dev, acpi_handle handle)
+{
+       struct acpi_device *acpi_dev;
+       struct acpi_handle_list *list;
+       struct acpi_power_managed_device *powered_device;
+       int i, ret;
+
+       if (!dev || !handle)
+               return -ENODEV;
+
+       ret = acpi_bus_get_device(handle, &acpi_dev);
+       if (ret)
+               goto no_power_resource;
+
+       if (!acpi_dev->power.flags.power_resources)
+               goto no_power_resource;
+
+       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;
+               }
+       }
+
+       return ret;
+
+no_power_resource:
+       printk(KERN_WARNING PREFIX "Invalid Power Resource to register!");
+       return -ENODEV;
+}
+
 /**
  * acpi_device_sleep_wake - execute _DSW (Device Sleep Wake) or (deprecated in
  *                          ACPI 3.0) _PSW (Power State Wake)
@@ -433,8 +599,7 @@ int acpi_disable_wakeup_device_power(struct acpi_device *dev)
 
        /* Close power resource */
        for (i = 0; i < dev->wakeup.resources.count; i++) {
-               int ret = acpi_power_off_device(
-                               dev->wakeup.resources.handles[i]);
+               int ret = acpi_power_off(dev->wakeup.resources.handles[i]);
                if (ret) {
                        printk(KERN_ERR PREFIX "Transition power state\n");
                        dev->wakeup.flags.valid = 0;
@@ -466,7 +631,7 @@ int acpi_power_get_inferred_state(struct acpi_device *device, int *state)
         * We know a device's inferred power state when all the resources
         * required for a given D-state are 'on'.
         */
-       for (i = ACPI_STATE_D0; i < ACPI_STATE_D3; i++) {
+       for (i = ACPI_STATE_D0; i < ACPI_STATE_D3_HOT; i++) {
                list = &device->power.states[i].resources;
                if (list->count < 1)
                        continue;
@@ -495,16 +660,16 @@ int acpi_power_on_resources(struct acpi_device *device, int state)
 
 int acpi_power_transition(struct acpi_device *device, int state)
 {
-       int result;
+       int result = 0;
 
-       if (!device || (state < ACPI_STATE_D0) || (state > ACPI_STATE_D3))
+       if (!device || (state < ACPI_STATE_D0) || (state > ACPI_STATE_D3_COLD))
                return -EINVAL;
 
        if (device->power.state == state)
                return 0;
 
        if ((device->power.state < ACPI_STATE_D0)
-           || (device->power.state > ACPI_STATE_D3))
+           || (device->power.state > ACPI_STATE_D3_COLD))
                return -ENODEV;
 
        /* TBD: Resources must be ordered. */
@@ -514,8 +679,11 @@ int acpi_power_transition(struct acpi_device *device, int state)
         * (e.g. so the device doesn't lose power while transitioning).  Then,
         * we dereference all power resources used in the current list.
         */
-       result = acpi_power_on_list(&device->power.states[state].resources);
-       if (!result)
+       if (state < ACPI_STATE_D3_COLD)
+               result = acpi_power_on_list(
+                       &device->power.states[state].resources);
+
+       if (!result && device->power.state < ACPI_STATE_D3_COLD)
                acpi_power_off_list(
                        &device->power.states[device->power.state].resources);