PM / QoS: Add function dev_pm_qos_read_value() (v3)
[pandora-kernel.git] / drivers / base / power / qos.c
index cc4c541..91e0614 100644 (file)
  *
  * This QoS design is best effort based. Dependents register their QoS needs.
  * Watchers register to keep track of the current QoS needs of the system.
+ * Watchers can register different types of notification callbacks:
+ *  . a per-device notification callback using the dev_pm_qos_*_notifier API.
+ *    The notification chain data is stored in the per-device constraint
+ *    data struct.
+ *  . a system-wide notification callback using the dev_pm_qos_*_global_notifier
+ *    API. The notification chain data is stored in a static variable.
  *
  * Note about the per-device constraint data struct allocation:
  * . The per-device constraints data struct ptr is tored into the device
  * . To minimize the data usage by the per-device constraints, the data struct
  *   is only allocated at the first call to dev_pm_qos_add_request.
  * . The data is later free'd when the device is removed from the system.
- * . The constraints_state variable from dev_pm_info tracks the data struct
- *    allocation state:
- *    DEV_PM_QOS_NO_DEVICE: No device present or device removed, no data
- *     allocated,
- *    DEV_PM_QOS_DEVICE_PRESENT: Device present, data not allocated and will be
- *     allocated at the first call to dev_pm_qos_add_request,
- *    DEV_PM_QOS_ALLOCATED: Device present, data allocated. The per-device
- *     PM QoS constraints framework is operational and constraints can be
- *     added, updated or removed using the dev_pm_qos_* API.
  *  . A global mutex protects the constraints users from the data being
  *     allocated and free'd.
  */
 
 static DEFINE_MUTEX(dev_pm_qos_mtx);
 
+static BLOCKING_NOTIFIER_HEAD(dev_pm_notifiers);
+
+/**
+ * dev_pm_qos_read_value - Get PM QoS constraint for a given device.
+ * @dev: Device to get the PM QoS constraint value for.
+ */
+s32 dev_pm_qos_read_value(struct device *dev)
+{
+       struct pm_qos_constraints *c;
+       unsigned long flags;
+       s32 ret = 0;
+
+       spin_lock_irqsave(&dev->power.lock, flags);
+
+       c = dev->power.constraints;
+       if (c)
+               ret = pm_qos_read_value(c);
+
+       spin_unlock_irqrestore(&dev->power.lock, flags);
+
+       return ret;
+}
+
+/*
+ * apply_constraint
+ * @req: constraint request to apply
+ * @action: action to perform add/update/remove, of type enum pm_qos_req_action
+ * @value: defines the qos request
+ *
+ * Internal function to update the constraints list using the PM QoS core
+ * code and if needed call the per-device and the global notification
+ * callbacks
+ */
+static int apply_constraint(struct dev_pm_qos_request *req,
+                           enum pm_qos_req_action action, int value)
+{
+       int ret, curr_value;
+
+       ret = pm_qos_update_target(req->dev->power.constraints,
+                                  &req->node, action, value);
+
+       if (ret) {
+               /* Call the global callbacks if needed */
+               curr_value = pm_qos_read_value(req->dev->power.constraints);
+               blocking_notifier_call_chain(&dev_pm_notifiers,
+                                            (unsigned long)curr_value,
+                                            req);
+       }
+
+       return ret;
+}
+
 /*
  * dev_pm_qos_constraints_allocate
  * @dev: device to allocate data for
@@ -69,27 +118,31 @@ static int dev_pm_qos_constraints_allocate(struct device *dev)
        }
        BLOCKING_INIT_NOTIFIER_HEAD(n);
 
+       plist_head_init(&c->list);
+       c->target_value = PM_QOS_DEV_LAT_DEFAULT_VALUE;
+       c->default_value = PM_QOS_DEV_LAT_DEFAULT_VALUE;
+       c->type = PM_QOS_MIN;
+       c->notifiers = n;
+
+       spin_lock_irq(&dev->power.lock);
        dev->power.constraints = c;
-       plist_head_init(&dev->power.constraints->list);
-       dev->power.constraints->target_value = PM_QOS_DEV_LAT_DEFAULT_VALUE;
-       dev->power.constraints->default_value = PM_QOS_DEV_LAT_DEFAULT_VALUE;
-       dev->power.constraints->type = PM_QOS_MIN;
-       dev->power.constraints->notifiers = n;
-       dev->power.constraints_state = DEV_PM_QOS_ALLOCATED;
+       spin_unlock_irq(&dev->power.lock);
 
        return 0;
 }
 
 /**
- * dev_pm_qos_constraints_init
+ * dev_pm_qos_constraints_init - Initalize device's PM QoS constraints pointer.
  * @dev: target device
  *
- * Called from the device PM subsystem at device insertion
+ * Called from the device PM subsystem during device insertion under
+ * device_pm_lock().
  */
 void dev_pm_qos_constraints_init(struct device *dev)
 {
        mutex_lock(&dev_pm_qos_mtx);
-       dev->power.constraints_state = DEV_PM_QOS_DEVICE_PRESENT;
+       dev->power.constraints = NULL;
+       dev->power.power_state = PMSG_ON;
        mutex_unlock(&dev_pm_qos_mtx);
 }
 
@@ -97,35 +150,38 @@ void dev_pm_qos_constraints_init(struct device *dev)
  * dev_pm_qos_constraints_destroy
  * @dev: target device
  *
- * Called from the device PM subsystem at device removal
+ * Called from the device PM subsystem on device removal under device_pm_lock().
  */
 void dev_pm_qos_constraints_destroy(struct device *dev)
 {
        struct dev_pm_qos_request *req, *tmp;
+       struct pm_qos_constraints *c;
 
        mutex_lock(&dev_pm_qos_mtx);
 
-       if (dev->power.constraints_state == DEV_PM_QOS_ALLOCATED) {
-               /* Flush the constraints list for the device */
-               plist_for_each_entry_safe(req, tmp,
-                                         &dev->power.constraints->list,
-                                         node) {
-                       /*
-                        * Update constraints list and call the per-device
-                        * callbacks if needed
-                        */
-                       pm_qos_update_target(req->dev->power.constraints,
-                                          &req->node, PM_QOS_REMOVE_REQ,
-                                          PM_QOS_DEFAULT_VALUE);
-                       memset(req, 0, sizeof(*req));
-               }
+       dev->power.power_state = PMSG_INVALID;
+       c = dev->power.constraints;
+       if (!c)
+               goto out;
 
-               kfree(dev->power.constraints->notifiers);
-               kfree(dev->power.constraints);
-               dev->power.constraints = NULL;
+       /* Flush the constraints list for the device */
+       plist_for_each_entry_safe(req, tmp, &c->list, node) {
+               /*
+                * Update constraints list and call the notification
+                * callbacks if needed
+                */
+               apply_constraint(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE);
+               memset(req, 0, sizeof(*req));
        }
-       dev->power.constraints_state = DEV_PM_QOS_NO_DEVICE;
 
+       spin_lock_irq(&dev->power.lock);
+       dev->power.constraints = NULL;
+       spin_unlock_irq(&dev->power.lock);
+
+       kfree(c->notifiers);
+       kfree(c);
+
+ out:
        mutex_unlock(&dev_pm_qos_mtx);
 }
 
@@ -143,11 +199,12 @@ void dev_pm_qos_constraints_destroy(struct device *dev)
  *
  * Returns 1 if the aggregated constraint value has changed,
  * 0 if the aggregated constraint value has not changed,
- * -EINVAL in case of wrong parameters, -ENODEV if the device has been
- * removed from the system
+ * -EINVAL in case of wrong parameters, -ENOMEM if there's not enough memory
+ * to allocate for data structures, -ENODEV if the device has just been removed
+ * from the system.
  */
 int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
-                           s32 value)
+                          s32 value)
 {
        int ret = 0;
 
@@ -160,29 +217,32 @@ int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
                return -EINVAL;
        }
 
-       mutex_lock(&dev_pm_qos_mtx);
        req->dev = dev;
 
-       /* Return if the device has been removed */
-       if (req->dev->power.constraints_state == DEV_PM_QOS_NO_DEVICE) {
-               ret = -ENODEV;
-               goto out;
-       }
+       mutex_lock(&dev_pm_qos_mtx);
 
-       /*
-        * Allocate the constraints data on the first call to add_request,
-        * i.e. only if the data is not already allocated and if the device has
-        * not been removed
-        */
-       if (dev->power.constraints_state == DEV_PM_QOS_DEVICE_PRESENT)
-               ret = dev_pm_qos_constraints_allocate(dev);
+       if (!dev->power.constraints) {
+               if (dev->power.power_state.event == PM_EVENT_INVALID) {
+                       /* The device has been removed from the system. */
+                       req->dev = NULL;
+                       ret = -ENODEV;
+                       goto out;
+               } else {
+                       /*
+                        * Allocate the constraints data on the first call to
+                        * add_request, i.e. only if the data is not already
+                        * allocated and if the device has not been removed.
+                        */
+                       ret = dev_pm_qos_constraints_allocate(dev);
+               }
+       }
 
        if (!ret)
-               ret = pm_qos_update_target(dev->power.constraints, &req->node,
-                                          PM_QOS_ADD_REQ, value);
+               ret = apply_constraint(req, PM_QOS_ADD_REQ, value);
 
-out:
+ out:
        mutex_unlock(&dev_pm_qos_mtx);
+
        return ret;
 }
 EXPORT_SYMBOL_GPL(dev_pm_qos_add_request);
@@ -218,12 +278,10 @@ int dev_pm_qos_update_request(struct dev_pm_qos_request *req,
 
        mutex_lock(&dev_pm_qos_mtx);
 
-       if (req->dev->power.constraints_state == DEV_PM_QOS_ALLOCATED) {
+       if (req->dev->power.constraints) {
                if (new_value != req->node.prio)
-                       ret = pm_qos_update_target(req->dev->power.constraints,
-                                                  &req->node,
-                                                  PM_QOS_UPDATE_REQ,
-                                                  new_value);
+                       ret = apply_constraint(req, PM_QOS_UPDATE_REQ,
+                                              new_value);
        } else {
                /* Return if the device has been removed */
                ret = -ENODEV;
@@ -261,10 +319,9 @@ int dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
 
        mutex_lock(&dev_pm_qos_mtx);
 
-       if (req->dev->power.constraints_state == DEV_PM_QOS_ALLOCATED) {
-               ret = pm_qos_update_target(req->dev->power.constraints,
-                                          &req->node, PM_QOS_REMOVE_REQ,
-                                          PM_QOS_DEFAULT_VALUE);
+       if (req->dev->power.constraints) {
+               ret = apply_constraint(req, PM_QOS_REMOVE_REQ,
+                                      PM_QOS_DEFAULT_VALUE);
                memset(req, 0, sizeof(*req));
        } else {
                /* Return if the device has been removed */
@@ -292,15 +349,12 @@ int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier)
 
        mutex_lock(&dev_pm_qos_mtx);
 
-       /* Silently return if the device has been removed */
-       if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED)
-               goto out;
-
-       retval = blocking_notifier_chain_register(
-                       dev->power.constraints->notifiers,
-                       notifier);
+       /* Silently return if the constraints object is not present. */
+       if (dev->power.constraints)
+               retval = blocking_notifier_chain_register(
+                               dev->power.constraints->notifiers,
+                               notifier);
 
-out:
        mutex_unlock(&dev_pm_qos_mtx);
        return retval;
 }
@@ -323,16 +377,43 @@ int dev_pm_qos_remove_notifier(struct device *dev,
 
        mutex_lock(&dev_pm_qos_mtx);
 
-       /* Silently return if the device has been removed */
-       if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED)
-               goto out;
-
-       retval = blocking_notifier_chain_unregister(
-                       dev->power.constraints->notifiers,
-                       notifier);
+       /* Silently return if the constraints object is not present. */
+       if (dev->power.constraints)
+               retval = blocking_notifier_chain_unregister(
+                               dev->power.constraints->notifiers,
+                               notifier);
 
-out:
        mutex_unlock(&dev_pm_qos_mtx);
        return retval;
 }
 EXPORT_SYMBOL_GPL(dev_pm_qos_remove_notifier);
+
+/**
+ * dev_pm_qos_add_global_notifier - sets notification entry for changes to
+ * target value of the PM QoS constraints for any device
+ *
+ * @notifier: notifier block managed by caller.
+ *
+ * Will register the notifier into a notification chain that gets called
+ * upon changes to the target value for any device.
+ */
+int dev_pm_qos_add_global_notifier(struct notifier_block *notifier)
+{
+       return blocking_notifier_chain_register(&dev_pm_notifiers, notifier);
+}
+EXPORT_SYMBOL_GPL(dev_pm_qos_add_global_notifier);
+
+/**
+ * dev_pm_qos_remove_global_notifier - deletes notification for changes to
+ * target value of PM QoS constraints for any device
+ *
+ * @notifier: notifier block to be removed.
+ *
+ * Will remove the notifier from the notification chain that gets called
+ * upon changes to the target value for any device.
+ */
+int dev_pm_qos_remove_global_notifier(struct notifier_block *notifier)
+{
+       return blocking_notifier_chain_unregister(&dev_pm_notifiers, notifier);
+}
+EXPORT_SYMBOL_GPL(dev_pm_qos_remove_global_notifier);