summaryrefslogtreecommitdiff
path: root/drivers/regulator/proxy-consumer.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/regulator/proxy-consumer.c')
-rw-r--r--drivers/regulator/proxy-consumer.c231
1 files changed, 231 insertions, 0 deletions
diff --git a/drivers/regulator/proxy-consumer.c b/drivers/regulator/proxy-consumer.c
new file mode 100644
index 000000000000..37241bc41f11
--- /dev/null
+++ b/drivers/regulator/proxy-consumer.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/proxy-consumer.h>
+
+struct proxy_consumer {
+ struct list_head list;
+ struct regulator *reg;
+ bool enable;
+ int min_uV;
+ int max_uV;
+ u32 current_uA;
+};
+
+static DEFINE_MUTEX(proxy_consumer_list_mutex);
+static LIST_HEAD(proxy_consumer_list);
+static bool proxy_consumers_removed;
+
+/**
+ * regulator_proxy_consumer_register() - conditionally register a proxy consumer
+ * for the specified regulator and set its boot time parameters
+ * @reg_dev: Device pointer of the regulator
+ * @reg_node: Device node pointer of the regulator
+ *
+ * Returns a struct proxy_consumer pointer corresponding to the regulator on
+ * success, ERR_PTR() if an error occurred, or NULL if no proxy consumer is
+ * needed for the regulator. This function calls
+ * regulator_get(reg_dev, "proxy") after first checking if any proxy consumer
+ * properties are present in the reg_node device node. After that, the voltage,
+ * minimum current, and/or the enable state will be set based upon the device
+ * node property values.
+ */
+struct proxy_consumer *regulator_proxy_consumer_register(struct device *reg_dev,
+ struct device_node *reg_node)
+{
+ struct proxy_consumer *consumer = NULL;
+ const char *reg_name = "";
+ u32 voltage[2] = {0};
+ int rc;
+
+ /* Return immediately if no proxy consumer properties are specified. */
+ if (!of_find_property(reg_node, "qcom,proxy-consumer-enable", NULL)
+ && !of_find_property(reg_node, "qcom,proxy-consumer-voltage", NULL)
+ && !of_find_property(reg_node, "qcom,proxy-consumer-current", NULL))
+ return NULL;
+
+ mutex_lock(&proxy_consumer_list_mutex);
+
+ /* Do not register new consumers if they cannot be removed later. */
+ if (proxy_consumers_removed) {
+ rc = -EPERM;
+ goto unlock;
+ }
+
+ if (dev_name(reg_dev))
+ reg_name = dev_name(reg_dev);
+
+ consumer = kzalloc(sizeof(*consumer), GFP_KERNEL);
+ if (!consumer) {
+ pr_err("kzalloc failed\n");
+ rc = -ENOMEM;
+ goto unlock;
+ }
+
+ consumer->enable
+ = of_property_read_bool(reg_node, "qcom,proxy-consumer-enable");
+ of_property_read_u32(reg_node, "qcom,proxy-consumer-current",
+ &consumer->current_uA);
+ rc = of_property_read_u32_array(reg_node, "qcom,proxy-consumer-voltage",
+ voltage, 2);
+ if (!rc) {
+ consumer->min_uV = voltage[0];
+ consumer->max_uV = voltage[1];
+ }
+
+ dev_dbg(reg_dev, "proxy consumer request: enable=%d, voltage_range=[%d, %d] uV, min_current=%d uA\n",
+ consumer->enable, consumer->min_uV, consumer->max_uV,
+ consumer->current_uA);
+
+ consumer->reg = regulator_get(reg_dev, "proxy");
+ if (IS_ERR_OR_NULL(consumer->reg)) {
+ rc = PTR_ERR(consumer->reg);
+ pr_err("regulator_get() failed for %s, rc=%d\n", reg_name, rc);
+ goto unlock;
+ }
+
+ if (consumer->max_uV > 0 && consumer->min_uV <= consumer->max_uV) {
+ rc = regulator_set_voltage(consumer->reg, consumer->min_uV,
+ consumer->max_uV);
+ if (rc) {
+ pr_err("regulator_set_voltage %s failed, rc=%d\n",
+ reg_name, rc);
+ goto free_regulator;
+ }
+ }
+
+ if (consumer->current_uA > 0) {
+ rc = regulator_set_load(consumer->reg,
+ consumer->current_uA);
+ if (rc < 0) {
+ pr_err("regulator_set_load %s failed, rc=%d\n",
+ reg_name, rc);
+ goto remove_voltage;
+ }
+ }
+
+ if (consumer->enable) {
+ rc = regulator_enable(consumer->reg);
+ if (rc) {
+ pr_err("regulator_enable %s failed, rc=%d\n", reg_name,
+ rc);
+ goto remove_current;
+ }
+ }
+
+ list_add(&consumer->list, &proxy_consumer_list);
+ mutex_unlock(&proxy_consumer_list_mutex);
+
+ return consumer;
+
+remove_current:
+ regulator_set_load(consumer->reg, 0);
+remove_voltage:
+ regulator_set_voltage(consumer->reg, 0, INT_MAX);
+free_regulator:
+ regulator_put(consumer->reg);
+unlock:
+ kfree(consumer);
+ mutex_unlock(&proxy_consumer_list_mutex);
+ return ERR_PTR(rc);
+}
+
+/* proxy_consumer_list_mutex must be held by caller. */
+static int regulator_proxy_consumer_remove(struct proxy_consumer *consumer)
+{
+ int rc = 0;
+
+ if (consumer->enable) {
+ rc = regulator_disable(consumer->reg);
+ if (rc)
+ pr_err("regulator_disable failed, rc=%d\n", rc);
+ }
+
+ if (consumer->current_uA > 0) {
+ rc = regulator_set_load(consumer->reg, 0);
+ if (rc < 0)
+ pr_err("regulator_set_load failed, rc=%d\n",
+ rc);
+ }
+
+ if (consumer->max_uV > 0 && consumer->min_uV <= consumer->max_uV) {
+ rc = regulator_set_voltage(consumer->reg, 0, INT_MAX);
+ if (rc)
+ pr_err("regulator_set_voltage failed, rc=%d\n", rc);
+ }
+
+ regulator_put(consumer->reg);
+ list_del(&consumer->list);
+ kfree(consumer);
+
+ return rc;
+}
+
+/**
+ * regulator_proxy_consumer_unregister() - unregister a proxy consumer and
+ * remove its boot time requests
+ * @consumer: Pointer to proxy_consumer to be removed
+ *
+ * Returns 0 on success or errno on failure. This function removes all requests
+ * made by the proxy consumer in regulator_proxy_consumer_register() and then
+ * frees the consumer's resources.
+ */
+int regulator_proxy_consumer_unregister(struct proxy_consumer *consumer)
+{
+ int rc = 0;
+
+ if (IS_ERR_OR_NULL(consumer))
+ return 0;
+
+ mutex_lock(&proxy_consumer_list_mutex);
+ if (!proxy_consumers_removed)
+ rc = regulator_proxy_consumer_remove(consumer);
+ mutex_unlock(&proxy_consumer_list_mutex);
+
+ return rc;
+}
+
+/*
+ * Remove all proxy requests at late_initcall_sync. The assumption is that all
+ * devices have probed at this point and made their own regulator requests.
+ */
+static int __init regulator_proxy_consumer_remove_all(void)
+{
+ struct proxy_consumer *consumer;
+ struct proxy_consumer *temp;
+
+ mutex_lock(&proxy_consumer_list_mutex);
+ proxy_consumers_removed = true;
+
+ if (!list_empty(&proxy_consumer_list))
+ pr_info("removing regulator proxy consumer requests\n");
+
+ list_for_each_entry_safe(consumer, temp, &proxy_consumer_list, list) {
+ regulator_proxy_consumer_remove(consumer);
+ }
+ mutex_unlock(&proxy_consumer_list_mutex);
+
+ return 0;
+}
+late_initcall_sync(regulator_proxy_consumer_remove_all);