]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/regulator/core.c
Merge remote-tracking branch 'regulator/topic/fixed' into regulator-next
[karo-tx-linux.git] / drivers / regulator / core.c
index 906deb7354eda83d846de1170aacf789bdc786b6..960103a6100041e008ce6793304c82299ca59b16 100644 (file)
@@ -53,6 +53,7 @@ static DEFINE_MUTEX(regulator_list_mutex);
 static LIST_HEAD(regulator_list);
 static LIST_HEAD(regulator_map_list);
 static LIST_HEAD(regulator_ena_gpio_list);
+static LIST_HEAD(regulator_supply_alias_list);
 static bool has_full_constraints;
 static bool board_wants_dummy_regulator;
 
@@ -83,6 +84,19 @@ struct regulator_enable_gpio {
        unsigned int ena_gpio_invert:1;
 };
 
+/*
+ * struct regulator_supply_alias
+ *
+ * Used to map lookups for a supply onto an alternative device.
+ */
+struct regulator_supply_alias {
+       struct list_head list;
+       struct device *src_dev;
+       const char *src_supply;
+       struct device *alias_dev;
+       const char *alias_supply;
+};
+
 static int _regulator_is_enabled(struct regulator_dev *rdev);
 static int _regulator_disable(struct regulator_dev *rdev);
 static int _regulator_get_voltage(struct regulator_dev *rdev);
@@ -905,6 +919,36 @@ static int machine_constraints_voltage(struct regulator_dev *rdev,
        return 0;
 }
 
+static int machine_constraints_current(struct regulator_dev *rdev,
+       struct regulation_constraints *constraints)
+{
+       struct regulator_ops *ops = rdev->desc->ops;
+       int ret;
+
+       if (!constraints->min_uA && !constraints->max_uA)
+               return 0;
+
+       if (constraints->min_uA > constraints->max_uA) {
+               rdev_err(rdev, "Invalid current constraints\n");
+               return -EINVAL;
+       }
+
+       if (!ops->set_current_limit || !ops->get_current_limit) {
+               rdev_warn(rdev, "Operation of current configuration missing\n");
+               return 0;
+       }
+
+       /* Set regulator current in constraints range */
+       ret = ops->set_current_limit(rdev, constraints->min_uA,
+                       constraints->max_uA);
+       if (ret < 0) {
+               rdev_err(rdev, "Failed to set current constraint, %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
 /**
  * set_machine_constraints - sets regulator constraints
  * @rdev: regulator source
@@ -935,6 +979,10 @@ static int set_machine_constraints(struct regulator_dev *rdev,
        if (ret != 0)
                goto out;
 
+       ret = machine_constraints_current(rdev, rdev->constraints);
+       if (ret != 0)
+               goto out;
+
        /* do we need to setup our suspend state */
        if (rdev->constraints->initial_state) {
                ret = suspend_prepare(rdev, rdev->constraints->initial_state);
@@ -1168,11 +1216,39 @@ overflow_err:
 
 static int _regulator_get_enable_time(struct regulator_dev *rdev)
 {
+       if (rdev->constraints && rdev->constraints->enable_time)
+               return rdev->constraints->enable_time;
        if (!rdev->desc->ops->enable_time)
                return rdev->desc->enable_time;
        return rdev->desc->ops->enable_time(rdev);
 }
 
+static struct regulator_supply_alias *regulator_find_supply_alias(
+               struct device *dev, const char *supply)
+{
+       struct regulator_supply_alias *map;
+
+       list_for_each_entry(map, &regulator_supply_alias_list, list)
+               if (map->src_dev == dev && strcmp(map->src_supply, supply) == 0)
+                       return map;
+
+       return NULL;
+}
+
+static void regulator_supply_alias(struct device **dev, const char **supply)
+{
+       struct regulator_supply_alias *map;
+
+       map = regulator_find_supply_alias(*dev, *supply);
+       if (map) {
+               dev_dbg(*dev, "Mapping supply %s to %s,%s\n",
+                               *supply, map->alias_supply,
+                               dev_name(map->alias_dev));
+               *dev = map->alias_dev;
+               *supply = map->alias_supply;
+       }
+}
+
 static struct regulator_dev *regulator_dev_lookup(struct device *dev,
                                                  const char *supply,
                                                  int *ret)
@@ -1182,6 +1258,8 @@ static struct regulator_dev *regulator_dev_lookup(struct device *dev,
        struct regulator_map *map;
        const char *devname = NULL;
 
+       regulator_supply_alias(&dev, &supply);
+
        /* first do a dt based lookup */
        if (dev && dev->of_node) {
                node = of_get_regulator(dev, supply);
@@ -1234,7 +1312,7 @@ static struct regulator *_regulator_get(struct device *dev, const char *id,
 
        if (id == NULL) {
                pr_err("get() with no identifier\n");
-               return regulator;
+               return ERR_PTR(-EINVAL);
        }
 
        if (dev)
@@ -1432,6 +1510,134 @@ void regulator_put(struct regulator *regulator)
 }
 EXPORT_SYMBOL_GPL(regulator_put);
 
+/**
+ * regulator_register_supply_alias - Provide device alias for supply lookup
+ *
+ * @dev: device that will be given as the regulator "consumer"
+ * @id: Supply name or regulator ID
+ * @alias_dev: device that should be used to lookup the supply
+ * @alias_id: Supply name or regulator ID that should be used to lookup the
+ * supply
+ *
+ * All lookups for id on dev will instead be conducted for alias_id on
+ * alias_dev.
+ */
+int regulator_register_supply_alias(struct device *dev, const char *id,
+                                   struct device *alias_dev,
+                                   const char *alias_id)
+{
+       struct regulator_supply_alias *map;
+
+       map = regulator_find_supply_alias(dev, id);
+       if (map)
+               return -EEXIST;
+
+       map = kzalloc(sizeof(struct regulator_supply_alias), GFP_KERNEL);
+       if (!map)
+               return -ENOMEM;
+
+       map->src_dev = dev;
+       map->src_supply = id;
+       map->alias_dev = alias_dev;
+       map->alias_supply = alias_id;
+
+       list_add(&map->list, &regulator_supply_alias_list);
+
+       pr_info("Adding alias for supply %s,%s -> %s,%s\n",
+               id, dev_name(dev), alias_id, dev_name(alias_dev));
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(regulator_register_supply_alias);
+
+/**
+ * regulator_unregister_supply_alias - Remove device alias
+ *
+ * @dev: device that will be given as the regulator "consumer"
+ * @id: Supply name or regulator ID
+ *
+ * Remove a lookup alias if one exists for id on dev.
+ */
+void regulator_unregister_supply_alias(struct device *dev, const char *id)
+{
+       struct regulator_supply_alias *map;
+
+       map = regulator_find_supply_alias(dev, id);
+       if (map) {
+               list_del(&map->list);
+               kfree(map);
+       }
+}
+EXPORT_SYMBOL_GPL(regulator_unregister_supply_alias);
+
+/**
+ * regulator_bulk_register_supply_alias - register multiple aliases
+ *
+ * @dev: device that will be given as the regulator "consumer"
+ * @id: List of supply names or regulator IDs
+ * @alias_dev: device that should be used to lookup the supply
+ * @alias_id: List of supply names or regulator IDs that should be used to
+ * lookup the supply
+ * @num_id: Number of aliases to register
+ *
+ * @return 0 on success, an errno on failure.
+ *
+ * This helper function allows drivers to register several supply
+ * aliases in one operation.  If any of the aliases cannot be
+ * registered any aliases that were registered will be removed
+ * before returning to the caller.
+ */
+int regulator_bulk_register_supply_alias(struct device *dev, const char **id,
+                                        struct device *alias_dev,
+                                        const char **alias_id,
+                                        int num_id)
+{
+       int i;
+       int ret;
+
+       for (i = 0; i < num_id; ++i) {
+               ret = regulator_register_supply_alias(dev, id[i], alias_dev,
+                                                     alias_id[i]);
+               if (ret < 0)
+                       goto err;
+       }
+
+       return 0;
+
+err:
+       dev_err(dev,
+               "Failed to create supply alias %s,%s -> %s,%s\n",
+               id[i], dev_name(dev), alias_id[i], dev_name(alias_dev));
+
+       while (--i >= 0)
+               regulator_unregister_supply_alias(dev, id[i]);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(regulator_bulk_register_supply_alias);
+
+/**
+ * regulator_bulk_unregister_supply_alias - unregister multiple aliases
+ *
+ * @dev: device that will be given as the regulator "consumer"
+ * @id: List of supply names or regulator IDs
+ * @num_id: Number of aliases to unregister
+ *
+ * This helper function allows drivers to unregister several supply
+ * aliases in one operation.
+ */
+void regulator_bulk_unregister_supply_alias(struct device *dev,
+                                           const char **id,
+                                           int num_id)
+{
+       int i;
+
+       for (i = 0; i < num_id; ++i)
+               regulator_unregister_supply_alias(dev, id[i]);
+}
+EXPORT_SYMBOL_GPL(regulator_bulk_unregister_supply_alias);
+
+
 /* Manage enable GPIO list. Same GPIO pin can be shared among regulators */
 static int regulator_ena_gpio_request(struct regulator_dev *rdev,
                                const struct regulator_config *config)
@@ -1563,11 +1769,39 @@ static int _regulator_do_enable(struct regulator_dev *rdev)
         * together.  */
        trace_regulator_enable_delay(rdev_get_name(rdev));
 
-       if (delay >= 1000) {
-               mdelay(delay / 1000);
-               udelay(delay % 1000);
-       } else if (delay) {
-               udelay(delay);
+       /*
+        * Delay for the requested amount of time as per the guidelines in:
+        *
+        *     Documentation/timers/timers-howto.txt
+        *
+        * The assumption here is that regulators will never be enabled in
+        * atomic context and therefore sleeping functions can be used.
+        */
+       if (delay) {
+               unsigned int ms = delay / 1000;
+               unsigned int us = delay % 1000;
+
+               if (ms > 0) {
+                       /*
+                        * For small enough values, handle super-millisecond
+                        * delays in the usleep_range() call below.
+                        */
+                       if (ms < 20)
+                               us += ms * 1000;
+                       else
+                               msleep(ms);
+               }
+
+               /*
+                * Give the scheduler some room to coalesce with any other
+                * wakeup sources. For delays shorter than 10 us, don't even
+                * bother setting up high-resolution timers and just busy-
+                * loop.
+                */
+               if (us >= 10)
+                       usleep_range(us, us + 100);
+               else
+                       udelay(us);
        }
 
        trace_regulator_enable_complete(rdev_get_name(rdev));
@@ -2348,6 +2582,8 @@ static int _regulator_get_voltage(struct regulator_dev *rdev)
                ret = rdev->desc->ops->get_voltage(rdev);
        } else if (rdev->desc->ops->list_voltage) {
                ret = rdev->desc->ops->list_voltage(rdev, 0);
+       } else if (rdev->desc->fixed_uV && (rdev->desc->n_voltages == 1)) {
+               ret = rdev->desc->fixed_uV;
        } else {
                return -EINVAL;
        }
@@ -2983,7 +3219,8 @@ static int add_regulator_attributes(struct regulator_dev *rdev)
        /* some attributes need specific methods to be displayed */
        if ((ops->get_voltage && ops->get_voltage(rdev) >= 0) ||
            (ops->get_voltage_sel && ops->get_voltage_sel(rdev) >= 0) ||
-           (ops->list_voltage && ops->list_voltage(rdev, 0) >= 0)) {
+           (ops->list_voltage && ops->list_voltage(rdev, 0) >= 0) ||
+               (rdev->desc->fixed_uV && (rdev->desc->n_voltages == 1))) {
                status = device_create_file(dev, &dev_attr_microvolts);
                if (status < 0)
                        return status;