From cd91cb8d065e44dc40a5738dfb0902cd87ab22f9 Mon Sep 17 00:00:00 2001 From: David Collins Date: Fri, 8 Nov 2019 15:08:18 -0800 Subject: [PATCH 1/5] 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 From 9484c331c3eea756ac6bda4b541e225738412efc Mon Sep 17 00:00:00 2001 From: David Collins Date: Fri, 15 Nov 2019 16:29:42 -0800 Subject: [PATCH 2/5] defconfig: lahaina-gki: enable the regulator proxy consumer library Enable the regulator proxy consumer library so that proxy consumer requests defined in device tree can be enforced at runtime immediately after a given regulator is registered. Change-Id: I1fe26c40521c1e7b9bf6dfc2d15119f6d9210355 Signed-off-by: David Collins --- arch/arm64/configs/vendor/lahaina_GKI.config | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/vendor/lahaina_GKI.config b/arch/arm64/configs/vendor/lahaina_GKI.config index 98021bb9fb20..e42f33ae8594 100644 --- a/arch/arm64/configs/vendor/lahaina_GKI.config +++ b/arch/arm64/configs/vendor/lahaina_GKI.config @@ -1,6 +1,7 @@ CONFIG_ARCH_QCOM=y CONFIG_ARCH_LAHAINA=y CONFIG_REGULATOR_STUB=m +CONFIG_REGULATOR_PROXY_CONSUMER=m CONFIG_PINCTRL_LAHAINA=m CONFIG_EXTCON=m CONFIG_NOP_USB_XCEIV=m From 15a7e9b328ed4fbde309e46f5d267752001d39df Mon Sep 17 00:00:00 2001 From: David Collins Date: Fri, 15 Nov 2019 17:20:05 -0800 Subject: [PATCH 3/5] regulator: rpmh-regulator: add support for proxy consumers Add support to enforce proxy consumer requests defined in device tree for rpmh-regulator devices. These can be used to ensure that regulators are not accidentally disabled or scaled down before all consumers have had a chance to make their own requests. Change-Id: Ic5f4ef7369584433b0a187f966d2d5f379b98cb6 Signed-off-by: David Collins --- drivers/regulator/rpmh-regulator.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/regulator/rpmh-regulator.c b/drivers/regulator/rpmh-regulator.c index 714ab23be0e5..73aed2ad4891 100644 --- a/drivers/regulator/rpmh-regulator.c +++ b/drivers/regulator/rpmh-regulator.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -1805,12 +1806,17 @@ static int rpmh_regulator_init_vreg(struct rpmh_vreg *vreg) return rc; } + rc = devm_regulator_proxy_consumer_register(dev, vreg->of_node); + if (rc) + vreg_err(vreg, "failed to register proxy consumer, rc=%d\n", + rc); + vreg_debug(vreg, "successfully registered; set=%s\n", vreg->set_active && vreg->set_sleep ? "active + sleep" : vreg->set_active ? "active" : "sleep"); - return rc; + return 0; } static const struct of_device_id rpmh_regulator_match_table[] = { @@ -1972,6 +1978,7 @@ static struct platform_driver rpmh_regulator_driver = { .driver = { .name = "qcom,rpmh-regulator", .of_match_table = rpmh_regulator_match_table, + .sync_state = regulator_proxy_consumer_sync_state, }, .probe = rpmh_regulator_probe, }; From 807599650dc4f17e6913711ca0e49d9dd238078d Mon Sep 17 00:00:00 2001 From: David Collins Date: Fri, 22 Nov 2019 16:10:42 -0800 Subject: [PATCH 4/5] regulator: fixed: add support for proxy consumers Add support to enforce proxy consumer requests defined in device tree for fixed regulator devices. These can be used to ensure that regulators are not accidentally disabled before all consumers have had a chance to make their own requests. Change-Id: Ib971aae138897ded6cb4efd9a7eddc17d9a07f7f Signed-off-by: David Collins --- drivers/regulator/fixed.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/regulator/fixed.c b/drivers/regulator/fixed.c index f81533070058..111b14c741c8 100644 --- a/drivers/regulator/fixed.c +++ b/drivers/regulator/fixed.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -249,6 +250,11 @@ static int reg_fixed_voltage_probe(struct platform_device *pdev) return ret; } + ret = devm_regulator_proxy_consumer_register(dev, dev->of_node); + if (ret) + dev_err(dev, "failed to register proxy consumer, ret=%d\n", + ret); + platform_set_drvdata(pdev, drvdata); dev_dbg(&pdev->dev, "%s supplying %duV\n", drvdata->desc.name, @@ -278,6 +284,7 @@ static struct platform_driver regulator_fixed_voltage_driver = { .driver = { .name = "reg-fixed-voltage", .of_match_table = of_match_ptr(fixed_of_match), + .sync_state = regulator_proxy_consumer_sync_state, }, }; From f107496e75eac102f2388360d032ada2eb5b2af3 Mon Sep 17 00:00:00 2001 From: David Collins Date: Fri, 22 Nov 2019 16:14:20 -0800 Subject: [PATCH 5/5] defconfig: lahaina-gki: enable the fixed regulator driver Enable the fixed regulator driver so that consumers can control fixed regulator devices at runtime. These include both GPIO controlled regulators and always-on (non-controllable) board regulators. Change-Id: I599c48e825fe546219146a893b5c8101a7e681ca Signed-off-by: David Collins --- arch/arm64/configs/vendor/lahaina_GKI.config | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/vendor/lahaina_GKI.config b/arch/arm64/configs/vendor/lahaina_GKI.config index e42f33ae8594..d1d5e20edd15 100644 --- a/arch/arm64/configs/vendor/lahaina_GKI.config +++ b/arch/arm64/configs/vendor/lahaina_GKI.config @@ -44,6 +44,7 @@ CONFIG_MSM_SERVICE_NOTIFIER=m CONFIG_QSEE_IPC_IRQ=m CONFIG_RPMSG_QCOM_GLINK_SPSS=m CONFIG_REGULATOR_RPMH=m +CONFIG_REGULATOR_FIXED_VOLTAGE=m CONFIG_MSM_GCC_LAHAINA=m CONFIG_REGULATOR_REFGEN=m CONFIG_MSM_VIDEOCC_LAHAINA=m