opp: Not all power-domains are scalable
[linux-2.6-block.git] / drivers / opp / core.c
index 3a9789388bfb947980c35f540c3cf86d51ec4b83..08b830f84d8c0029ac2dfa59ee70057460145c67 100644 (file)
@@ -682,7 +682,7 @@ static int _set_opp_custom(const struct opp_table *opp_table,
 
        data->old_opp.rate = old_freq;
        size = sizeof(*old_supply) * opp_table->regulator_count;
-       if (IS_ERR(old_supply))
+       if (!old_supply)
                memset(data->old_opp.supplies, 0, size);
        else
                memcpy(data->old_opp.supplies, old_supply, size);
@@ -708,7 +708,7 @@ static int _set_required_opps(struct device *dev,
 
        /* Single genpd case */
        if (!genpd_virt_devs) {
-               pstate = opp->required_opps[0]->pstate;
+               pstate = likely(opp) ? opp->required_opps[0]->pstate : 0;
                ret = dev_pm_genpd_set_performance_state(dev, pstate);
                if (ret) {
                        dev_err(dev, "Failed to set performance state of %s: %d (%d)\n",
@@ -726,7 +726,7 @@ static int _set_required_opps(struct device *dev,
        mutex_lock(&opp_table->genpd_virt_dev_lock);
 
        for (i = 0; i < opp_table->required_opp_count; i++) {
-               pstate = opp->required_opps[i]->pstate;
+               pstate = likely(opp) ? opp->required_opps[i]->pstate : 0;
 
                if (!genpd_virt_devs[i])
                        continue;
@@ -748,29 +748,37 @@ static int _set_required_opps(struct device *dev,
  * @dev:        device for which we do this operation
  * @target_freq: frequency to achieve
  *
- * This configures the power-supplies and clock source to the levels specified
- * by the OPP corresponding to the target_freq.
+ * This configures the power-supplies to the levels specified by the OPP
+ * corresponding to the target_freq, and programs the clock to a value <=
+ * target_freq, as rounded by clk_round_rate(). Device wanting to run at fmax
+ * provided by the opp, should have already rounded to the target OPP's
+ * frequency.
  */
 int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 {
        struct opp_table *opp_table;
-       unsigned long freq, old_freq;
+       unsigned long freq, old_freq, temp_freq;
        struct dev_pm_opp *old_opp, *opp;
        struct clk *clk;
        int ret;
 
-       if (unlikely(!target_freq)) {
-               dev_err(dev, "%s: Invalid target frequency %lu\n", __func__,
-                       target_freq);
-               return -EINVAL;
-       }
-
        opp_table = _find_opp_table(dev);
        if (IS_ERR(opp_table)) {
                dev_err(dev, "%s: device opp doesn't exist\n", __func__);
                return PTR_ERR(opp_table);
        }
 
+       if (unlikely(!target_freq)) {
+               if (opp_table->required_opp_tables) {
+                       ret = _set_required_opps(dev, opp_table, NULL);
+               } else {
+                       dev_err(dev, "target frequency can't be 0\n");
+                       ret = -EINVAL;
+               }
+
+               goto put_opp_table;
+       }
+
        clk = opp_table->clk;
        if (IS_ERR(clk)) {
                dev_err(dev, "%s: No clock available for the device\n",
@@ -793,13 +801,15 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
                goto put_opp_table;
        }
 
-       old_opp = _find_freq_ceil(opp_table, &old_freq);
+       temp_freq = old_freq;
+       old_opp = _find_freq_ceil(opp_table, &temp_freq);
        if (IS_ERR(old_opp)) {
                dev_err(dev, "%s: failed to find current OPP for freq %lu (%ld)\n",
                        __func__, old_freq, PTR_ERR(old_opp));
        }
 
-       opp = _find_freq_ceil(opp_table, &freq);
+       temp_freq = freq;
+       opp = _find_freq_ceil(opp_table, &temp_freq);
        if (IS_ERR(opp)) {
                ret = PTR_ERR(opp);
                dev_err(dev, "%s: failed to find OPP for freq %lu (%d)\n",
@@ -1741,91 +1751,133 @@ void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table)
 }
 EXPORT_SYMBOL_GPL(dev_pm_opp_unregister_set_opp_helper);
 
+static void _opp_detach_genpd(struct opp_table *opp_table)
+{
+       int index;
+
+       for (index = 0; index < opp_table->required_opp_count; index++) {
+               if (!opp_table->genpd_virt_devs[index])
+                       continue;
+
+               dev_pm_domain_detach(opp_table->genpd_virt_devs[index], false);
+               opp_table->genpd_virt_devs[index] = NULL;
+       }
+
+       kfree(opp_table->genpd_virt_devs);
+       opp_table->genpd_virt_devs = NULL;
+}
+
 /**
- * dev_pm_opp_set_genpd_virt_dev - Set virtual genpd device for an index
- * @dev: Consumer device for which the genpd device is getting set.
- * @virt_dev: virtual genpd device.
- * @index: index.
+ * dev_pm_opp_attach_genpd - Attach genpd(s) for the device and save virtual device pointer
+ * @dev: Consumer device for which the genpd is getting attached.
+ * @names: Null terminated array of pointers containing names of genpd to attach.
  *
  * Multiple generic power domains for a device are supported with the help of
  * virtual genpd devices, which are created for each consumer device - genpd
  * pair. These are the device structures which are attached to the power domain
  * and are required by the OPP core to set the performance state of the genpd.
+ * The same API also works for the case where single genpd is available and so
+ * we don't need to support that separately.
  *
  * This helper will normally be called by the consumer driver of the device
- * "dev", as only that has details of the genpd devices.
+ * "dev", as only that has details of the genpd names.
+ *
+ * This helper needs to be called once with a list of all genpd to attach.
+ * Otherwise the original device structure will be used instead by the OPP core.
  *
- * This helper needs to be called once for each of those virtual devices, but
- * only if multiple domains are available for a device. Otherwise the original
- * device structure will be used instead by the OPP core.
+ * The order of entries in the names array must match the order in which
+ * "required-opps" are added in DT.
  */
-struct opp_table *dev_pm_opp_set_genpd_virt_dev(struct device *dev,
-                                               struct device *virt_dev,
-                                               int index)
+struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names)
 {
        struct opp_table *opp_table;
+       struct device *virt_dev;
+       int index = 0, ret = -EINVAL;
+       const char **name = names;
 
        opp_table = dev_pm_opp_get_opp_table(dev);
        if (!opp_table)
                return ERR_PTR(-ENOMEM);
 
+       /*
+        * If the genpd's OPP table isn't already initialized, parsing of the
+        * required-opps fail for dev. We should retry this after genpd's OPP
+        * table is added.
+        */
+       if (!opp_table->required_opp_count) {
+               ret = -EPROBE_DEFER;
+               goto put_table;
+       }
+
        mutex_lock(&opp_table->genpd_virt_dev_lock);
 
-       if (unlikely(!opp_table->genpd_virt_devs ||
-                    index >= opp_table->required_opp_count ||
-                    opp_table->genpd_virt_devs[index])) {
+       opp_table->genpd_virt_devs = kcalloc(opp_table->required_opp_count,
+                                            sizeof(*opp_table->genpd_virt_devs),
+                                            GFP_KERNEL);
+       if (!opp_table->genpd_virt_devs)
+               goto unlock;
 
-               dev_err(dev, "Invalid request to set required device\n");
-               dev_pm_opp_put_opp_table(opp_table);
-               mutex_unlock(&opp_table->genpd_virt_dev_lock);
+       while (*name) {
+               if (index >= opp_table->required_opp_count) {
+                       dev_err(dev, "Index can't be greater than required-opp-count - 1, %s (%d : %d)\n",
+                               *name, opp_table->required_opp_count, index);
+                       goto err;
+               }
 
-               return ERR_PTR(-EINVAL);
+               if (opp_table->genpd_virt_devs[index]) {
+                       dev_err(dev, "Genpd virtual device already set %s\n",
+                               *name);
+                       goto err;
+               }
+
+               virt_dev = dev_pm_domain_attach_by_name(dev, *name);
+               if (IS_ERR(virt_dev)) {
+                       ret = PTR_ERR(virt_dev);
+                       dev_err(dev, "Couldn't attach to pm_domain: %d\n", ret);
+                       goto err;
+               }
+
+               opp_table->genpd_virt_devs[index] = virt_dev;
+               index++;
+               name++;
        }
 
-       opp_table->genpd_virt_devs[index] = virt_dev;
        mutex_unlock(&opp_table->genpd_virt_dev_lock);
 
        return opp_table;
+
+err:
+       _opp_detach_genpd(opp_table);
+unlock:
+       mutex_unlock(&opp_table->genpd_virt_dev_lock);
+
+put_table:
+       dev_pm_opp_put_opp_table(opp_table);
+
+       return ERR_PTR(ret);
 }
+EXPORT_SYMBOL_GPL(dev_pm_opp_attach_genpd);
 
 /**
- * dev_pm_opp_put_genpd_virt_dev() - Releases resources blocked for genpd device.
- * @opp_table: OPP table returned by dev_pm_opp_set_genpd_virt_dev().
- * @virt_dev: virtual genpd device.
- *
- * This releases the resource previously acquired with a call to
- * dev_pm_opp_set_genpd_virt_dev(). The consumer driver shall call this helper
- * if it doesn't want OPP core to update performance state of a power domain
- * anymore.
+ * dev_pm_opp_detach_genpd() - Detach genpd(s) from the device.
+ * @opp_table: OPP table returned by dev_pm_opp_attach_genpd().
+ *
+ * This detaches the genpd(s), resets the virtual device pointers, and puts the
+ * OPP table.
  */
-void dev_pm_opp_put_genpd_virt_dev(struct opp_table *opp_table,
-                                  struct device *virt_dev)
+void dev_pm_opp_detach_genpd(struct opp_table *opp_table)
 {
-       int i;
-
        /*
         * Acquire genpd_virt_dev_lock to make sure virt_dev isn't getting
         * used in parallel.
         */
        mutex_lock(&opp_table->genpd_virt_dev_lock);
-
-       for (i = 0; i < opp_table->required_opp_count; i++) {
-               if (opp_table->genpd_virt_devs[i] != virt_dev)
-                       continue;
-
-               opp_table->genpd_virt_devs[i] = NULL;
-               dev_pm_opp_put_opp_table(opp_table);
-
-               /* Drop the vote */
-               dev_pm_genpd_set_performance_state(virt_dev, 0);
-               break;
-       }
-
+       _opp_detach_genpd(opp_table);
        mutex_unlock(&opp_table->genpd_virt_dev_lock);
 
-       if (unlikely(i == opp_table->required_opp_count))
-               dev_err(virt_dev, "Failed to find required device entry\n");
+       dev_pm_opp_put_opp_table(opp_table);
 }
+EXPORT_SYMBOL_GPL(dev_pm_opp_detach_genpd);
 
 /**
  * dev_pm_opp_xlate_performance_state() - Find required OPP's pstate for src_table.