diff options
Diffstat (limited to 'drivers/regulator/proxy-consumer.c')
-rw-r--r-- | drivers/regulator/proxy-consumer.c | 231 |
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..b833c4e1fa69 --- /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_optimum_mode(consumer->reg, + consumer->current_uA); + if (rc < 0) { + pr_err("regulator_set_optimum_mode %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_optimum_mode(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_optimum_mode(consumer->reg, 0); + if (rc < 0) + pr_err("regulator_set_optimum_mode 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); |