diff --git a/Documentation/devicetree/bindings/regulator/proxy-consumer.txt b/Documentation/devicetree/bindings/regulator/proxy-consumer.txt new file mode 100644 index 000000000000..c3fddd78f3f5 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/proxy-consumer.txt @@ -0,0 +1,32 @@ +Regulator Proxy Consumer Bindings + +Regulator proxy consumers provide a means to use a default regulator state +during bootup only which is removed at the end of boot. This feature can be +used in situations where a shared regulator can be scaled between several +possible voltages and hardware requires that it be at a high level at the +beginning of boot before the consumer device responsible for requesting the +high level has probed. + +Optional properties: +proxy-supply: phandle of the regulator's own device node. + This property is required if any of the three + properties below are specified. +qcom,proxy-consumer-enable: Boolean indicating that the regulator must be + kept enabled during boot. +qcom,proxy-consumer-voltage: List of two integers corresponding the minimum + and maximum voltage allowed during boot in + microvolts. +qcom,proxy-consumer-current: Minimum current in microamps required during + boot. + +Example: + + foo_vreg: regulator@0 { + regulator-name = "foo"; + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <2000000>; + proxy-supply = <&foo_vreg>; + qcom,proxy-consumer-voltage = <1500000 2000000>; + qcom,proxy-consumer-current = <25000>; + qcom,proxy-consumer-enable; + }; diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 329cdd33ed62..3a9ce96fc79b 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -54,6 +54,16 @@ config REGULATOR_USERSPACE_CONSUMER If unsure, say no. +config REGULATOR_PROXY_CONSUMER + bool "Boot time regulator proxy consumer support" + help + This driver 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_88PG86X tristate "Marvell 88PG86X voltage regulators" depends on I2C diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index bba9c4851faf..38ba7a3e1f66 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..6ae1c7a03815 --- /dev/null +++ b/drivers/regulator/proxy-consumer.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013-2014, 2016, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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) { + 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); diff --git a/include/linux/regulator/proxy-consumer.h b/include/linux/regulator/proxy-consumer.h new file mode 100644 index 000000000000..980440d8dc3c --- /dev/null +++ b/include/linux/regulator/proxy-consumer.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + */ + +#ifndef _LINUX_REGULATOR_PROXY_CONSUMER_H_ +#define _LINUX_REGULATOR_PROXY_CONSUMER_H_ + +#include +#include + +struct proxy_consumer; + +#ifdef CONFIG_REGULATOR_PROXY_CONSUMER + +struct proxy_consumer *regulator_proxy_consumer_register(struct device *reg_dev, + struct device_node *reg_node); + +int regulator_proxy_consumer_unregister(struct proxy_consumer *consumer); + +#else + +static inline struct proxy_consumer *regulator_proxy_consumer_register( + struct device *reg_dev, struct device_node *reg_node) +{ return NULL; } + +static inline int regulator_proxy_consumer_unregister( + struct proxy_consumer *consumer) +{ return 0; } + +#endif + +#endif