From cd91cb8d065e44dc40a5738dfb0902cd87ab22f9 Mon Sep 17 00:00:00 2001 From: David Collins Date: Fri, 8 Nov 2019 15:08:18 -0800 Subject: [PATCH] regulator: add proxy consumer library Add a proxy consumer library which can be used by regulator drivers to ensure that a given regulator maintains a certain minimum power state during bootup. Enable state, voltage, and current may be forced to specified levels. Change-Id: I46853f55a1201195ed50f0365399cf49086f24ff Signed-off-by: David Collins --- drivers/regulator/Kconfig | 21 ++ drivers/regulator/Makefile | 1 + drivers/regulator/proxy-consumer.c | 399 +++++++++++++++++++++++ include/linux/regulator/proxy-consumer.h | 38 +++ 4 files changed, 459 insertions(+) create mode 100644 drivers/regulator/proxy-consumer.c create mode 100644 include/linux/regulator/proxy-consumer.h diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 7f3ce532f467..41e8897b8137 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -65,6 +65,27 @@ config REGULATOR_USERSPACE_CONSUMER If unsure, say no. +config REGULATOR_PROXY_CONSUMER + tristate "Boot time regulator proxy consumer support" + help + This library provides support for boot time regulator proxy requests. + It can enforce a specified voltage range, set a minimum current, + and/or keep a regulator enabled. It is needed in circumstances where + reducing one or more of these three quantities will cause hardware to + stop working if performed before the driver managing the hardware has + probed. + +config REGULATOR_PROXY_CONSUMER_LEGACY + bool "Legacy proxy consumer unvoting support" + depends on REGULATOR_PROXY_CONSUMER + help + By default, the proxy consumer library unvotes the proxy requests made + for a given device when the sync_state() callback of the device is + called after all of its consumers have probed. When this legacy + unvoting feature is enabled, unvoting happens unconditionally at + late_initcall_sync() instead of per-device via sync_state() + callback calls. + config REGULATOR_88PG86X tristate "Marvell 88PG86X voltage regulators" depends on I2C diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index f7b816521297..0615584a0277 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_OF) += of_regulator.o obj-$(CONFIG_REGULATOR_FIXED_VOLTAGE) += fixed.o obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o obj-$(CONFIG_REGULATOR_USERSPACE_CONSUMER) += userspace-consumer.o +obj-$(CONFIG_REGULATOR_PROXY_CONSUMER) += proxy-consumer.o obj-$(CONFIG_REGULATOR_88PG86X) += 88pg86x.o obj-$(CONFIG_REGULATOR_88PM800) += 88pm800-regulator.o diff --git a/drivers/regulator/proxy-consumer.c b/drivers/regulator/proxy-consumer.c new file mode 100644 index 000000000000..3c44845672d4 --- /dev/null +++ b/drivers/regulator/proxy-consumer.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct proxy_consumer { + struct list_head list; + struct regulator *reg; + struct device *dev; + bool enable; + int min_uV; + int max_uV; + u32 current_uA; +}; + +static DEFINE_MUTEX(proxy_consumer_list_lock); +static LIST_HEAD(proxy_consumer_list); +static bool proxy_consumers_removed; + +/** + * regulator_proxy_consumer_add() - conditionally add a proxy consumer for the + * specified regulator and set its boot time + * parameters + * @dev: Device pointer of the regulator + * @node: Device node pointer of the regulator + * + * This function calls regulator_get() after first checking if any proxy + * consumer properties are present in the 'node' device node. After that, the + * voltage, minimum current, and/or the enable state will be set based upon the + * device node property values. + * + * Returns a valid pointer on successfully proxy voting, NULL if no proxy voting + * is needed, or an ERR_PTR(errno) if an error occurred. + */ +static struct proxy_consumer *regulator_proxy_consumer_add(struct device *dev, + struct device_node *node) +{ + struct proxy_consumer *consumer = NULL; + const char *reg_name = ""; + const char *supply_name; + u32 voltage[2] = {0}; + int ret; + + if (!dev || !node) { + pr_err("dev or node is NULL\n"); + return ERR_PTR(-EINVAL); + } + + /* Return immediately if no proxy consumer properties are specified. */ + if (!of_find_property(node, "qcom,proxy-consumer-enable", NULL) + && !of_find_property(node, "qcom,proxy-consumer-voltage", NULL) + && !of_find_property(node, "qcom,proxy-consumer-current", NULL)) + return NULL; + + mutex_lock(&proxy_consumer_list_lock); + + /* Do not register new consumers if they cannot be removed later. */ + if (proxy_consumers_removed) { + ret = -EPERM; + goto unlock_list; + } + + if (node->name) + reg_name = node->name; + + consumer = kzalloc(sizeof(*consumer), GFP_KERNEL); + if (!consumer) { + ret = -ENOMEM; + goto unlock_list; + } + + consumer->dev = dev; + consumer->enable + = of_property_read_bool(node, "qcom,proxy-consumer-enable"); + of_property_read_u32(node, "qcom,proxy-consumer-current", + &consumer->current_uA); + ret = of_property_read_u32_array(node, "qcom,proxy-consumer-voltage", + voltage, 2); + if (!ret) { + consumer->min_uV = voltage[0]; + consumer->max_uV = voltage[1]; + } + + dev_dbg(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); + + supply_name = "proxy"; + of_property_read_string(node, "qcom,proxy-consumer-name", &supply_name); + + consumer->reg = regulator_get(dev, supply_name); + if (IS_ERR_OR_NULL(consumer->reg)) { + ret = PTR_ERR(consumer->reg); + pr_err("regulator_get(%s) failed for %s, ret=%d\n", supply_name, + reg_name, ret); + goto free_consumer; + } + + if (consumer->max_uV > 0 && consumer->min_uV <= consumer->max_uV) { + ret = regulator_set_voltage(consumer->reg, consumer->min_uV, + consumer->max_uV); + if (ret) { + pr_err("regulator_set_voltage %s failed, ret=%d\n", + reg_name, ret); + goto free_regulator; + } + } + + if (consumer->current_uA > 0) { + ret = regulator_set_load(consumer->reg, consumer->current_uA); + if (ret < 0) { + pr_err("regulator_set_load %s failed, ret=%d\n", + reg_name, ret); + goto remove_voltage; + } + } + + if (consumer->enable) { + ret = regulator_enable(consumer->reg); + if (ret) { + pr_err("regulator_enable %s failed, ret=%d\n", reg_name, + ret); + goto remove_current; + } + } + + list_add(&consumer->list, &proxy_consumer_list); + mutex_unlock(&proxy_consumer_list_lock); + + 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); +free_consumer: + kfree(consumer); +unlock_list: + mutex_unlock(&proxy_consumer_list_lock); + + return ERR_PTR(ret); +} + +/** + * regulator_proxy_consumer_register() - conditionally register a proxy consumer + * for the specified regulator and set its boot time parameters + * @dev: Device pointer of the regulator + * @node: Device node pointer of the regulator + * + * This function calls regulator_get() after first checking if any proxy + * consumer properties are present in the 'node' device node. After that, the + * voltage, minimum current, and/or the enable state will be set based upon the + * device node property values. + * + * Returns 0 on successfully proxy voting or if no proxy voting is needed, or an + * errno if an error occurred. + */ +int regulator_proxy_consumer_register(struct device *dev, + struct device_node *node) +{ + struct proxy_consumer *consumer; + + consumer = regulator_proxy_consumer_add(dev, node); + + return PTR_ERR_OR_ZERO(consumer); +} +EXPORT_SYMBOL(regulator_proxy_consumer_register); + +/* proxy_consumer_list_lock must be held by caller. */ +static int regulator_proxy_consumer_remove(struct proxy_consumer *consumer) +{ + int ret = 0; + + if (consumer->enable) { + ret = regulator_disable(consumer->reg); + if (ret) + pr_err("regulator_disable failed, ret=%d\n", ret); + } + + if (consumer->current_uA > 0) { + ret = regulator_set_load(consumer->reg, 0); + if (ret < 0) + pr_err("regulator_set_load failed, ret=%d\n", + ret); + } + + if (consumer->max_uV > 0 && consumer->min_uV <= consumer->max_uV) { + ret = regulator_set_voltage(consumer->reg, 0, INT_MAX); + if (ret) + pr_err("regulator_set_voltage failed, ret=%d\n", ret); + } + + regulator_put(consumer->reg); + list_del(&consumer->list); + kfree(consumer); + + return ret; +} + +/** + * regulator_proxy_consumer_unregister() - unregister the proxy consumers of a + * device and remove their boot time + * requests + * @dev: Device pointer of the regulator + * + * This function removes all requests made by the proxy consumers of regulators + * in dev which where issued in regulator_proxy_consumer_register() and then + * frees the consumers' resources. + * + * Returns 0 on success or an errno on failure. + */ +void regulator_proxy_consumer_unregister(struct device *dev) +{ + struct proxy_consumer *consumer, *temp; + + if (IS_ERR_OR_NULL(dev)) { + pr_err("invalid device pointer\n"); + return; + } + + mutex_lock(&proxy_consumer_list_lock); + list_for_each_entry_safe(consumer, temp, &proxy_consumer_list, list) { + if (consumer->dev == dev) + regulator_proxy_consumer_remove(consumer); + } + mutex_unlock(&proxy_consumer_list_lock); +} +EXPORT_SYMBOL(regulator_proxy_consumer_unregister); + +/* proxy_consumer_list_lock must be held by caller. */ +static void +_devm_regulator_proxy_consumer_release(struct device *dev, void *res) +{ + struct proxy_consumer *consumer = *(struct proxy_consumer **)res; + struct proxy_consumer *temp; + bool found = false; + + /* + * The proxy consumer may have already been removed due to a + * sync_state() or devm_regulator_proxy_consumer_unregister() call. + * Therefore, verify that it is still in the list before attempting to + * remove it. + */ + list_for_each_entry(temp, &proxy_consumer_list, list) { + if (temp == consumer) { + found = true; + break; + } + } + + if (found) + regulator_proxy_consumer_remove(consumer); +} + +static void devm_regulator_proxy_consumer_release(struct device *dev, void *res) +{ + mutex_lock(&proxy_consumer_list_lock); + _devm_regulator_proxy_consumer_release(dev, res); + mutex_unlock(&proxy_consumer_list_lock); +} + +/** + * devm_regulator_proxy_consumer_register() - resource managed version of + * regulator_proxy_consumer_register() + * @dev: Device pointer of the regulator + * @node: Device node pointer of the regulator + * + * This is a resource managed version of regulator_proxy_consumer_register(). + * Proxy consumer requests made via this call are automatically removed via + * regulator_proxy_consumer_unregister() on driver detach. See + * regulator_proxy_consumer_register() for more details. + * + * Returns 0 on success or an errno on failure. + */ +int devm_regulator_proxy_consumer_register(struct device *dev, + struct device_node *node) +{ + struct proxy_consumer *consumer; + struct proxy_consumer **ptr; + + ptr = devres_alloc(devm_regulator_proxy_consumer_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + consumer = regulator_proxy_consumer_add(dev, node); + if (IS_ERR_OR_NULL(consumer)) { + devres_free(ptr); + return PTR_ERR(consumer); + } + + *ptr = consumer; + devres_add(dev, ptr); + + return 0; +} +EXPORT_SYMBOL(devm_regulator_proxy_consumer_register); + +static int devm_regulator_proxy_consumer_match(struct device *dev, void *res, + void *data) +{ + struct proxy_consumer **consumer = res; + + if (!consumer || !*consumer) { + WARN_ON(!consumer || !*consumer); + return 0; + } + + return *consumer == data; +} + +/** + * devm_regulator_proxy_consumer_unregister() - resource managed version of + * regulator_proxy_consumer_unregister() + * @dev: Device pointer of the regulator + * + * Deallocate the proxy consumers allocated for 'dev' with + * devm_regulator_proxy_consumer_register(). Normally this function will not + * need to be called and the resource management code will ensure that the + * resource is freed. + * + * Returns 0 on success or an errno on failure. + */ +void devm_regulator_proxy_consumer_unregister(struct device *dev) +{ + struct proxy_consumer *consumer, *temp; + + if (IS_ERR_OR_NULL(dev)) + return; + + mutex_lock(&proxy_consumer_list_lock); + list_for_each_entry_safe(consumer, temp, &proxy_consumer_list, list) { + if (consumer->dev == dev) + devres_release(dev, + _devm_regulator_proxy_consumer_release, + devm_regulator_proxy_consumer_match, + consumer); + } + mutex_unlock(&proxy_consumer_list_lock); +} +EXPORT_SYMBOL(devm_regulator_proxy_consumer_unregister); + +#ifndef CONFIG_REGULATOR_PROXY_CONSUMER_LEGACY + +void regulator_proxy_consumer_sync_state(struct device *dev) +{ + regulator_proxy_consumer_unregister(dev); +} +EXPORT_SYMBOL(regulator_proxy_consumer_sync_state); + +#else /* CONFIG_REGULATOR_PROXY_CONSUMER_LEGACY=y */ + +void regulator_proxy_consumer_sync_state(struct device *dev) { } +EXPORT_SYMBOL(regulator_proxy_consumer_sync_state); + +/* + * 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_lock); + 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_lock); + + return 0; +} +late_initcall_sync(regulator_proxy_consumer_remove_all); + +#endif + +MODULE_DESCRIPTION("Regulator proxy consumer library"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/regulator/proxy-consumer.h b/include/linux/regulator/proxy-consumer.h new file mode 100644 index 000000000000..62135f3cdd13 --- /dev/null +++ b/include/linux/regulator/proxy-consumer.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#ifndef _LINUX_REGULATOR_PROXY_CONSUMER_H_ +#define _LINUX_REGULATOR_PROXY_CONSUMER_H_ + +#include +#include + +#if IS_ENABLED(CONFIG_REGULATOR_PROXY_CONSUMER) + +int regulator_proxy_consumer_register(struct device *dev, + struct device_node *node); +void regulator_proxy_consumer_unregister(struct device *dev); +int devm_regulator_proxy_consumer_register(struct device *dev, + struct device_node *node); +void devm_regulator_proxy_consumer_unregister(struct device *dev); +void regulator_proxy_consumer_sync_state(struct device *dev); + +#else + +static inline int regulator_proxy_consumer_register(struct device *dev, + struct device_node *node) +{ return 0; } +static inline void regulator_proxy_consumer_unregister(struct device *dev) +{ } +static inline int devm_regulator_proxy_consumer_register(struct device *dev, + struct device_node *node) +{ return 0; } +static inline void devm_regulator_proxy_consumer_unregister(struct device *dev) +{ } +void regulator_proxy_consumer_sync_state(struct device *dev) +{ } + +#endif +#endif