Merge "regulator: add proxy consumer library"

This commit is contained in:
qctecmdr 2022-05-28 16:00:00 -07:00 committed by Gerrit - the friendly Code Review server
commit a860a072e5
7 changed files with 1246 additions and 1 deletions

View File

@ -30,6 +30,15 @@ config REGULATOR_DEBUG
help
Say yes here to enable debugging support.
config REGULATOR_DEBUG_CONTROL
tristate "Regulator debug control support"
depends on DEBUG_FS
help
This driver supports debugfs based control of regulators for testing
purposes. It provides a mechanism to vote on the enable state,
voltage, mode, and load current of regulators that make use of its
interface. It also extends the monitoring interface for regulators.
config REGULATOR_FIXED_VOLTAGE
tristate "Fixed voltage regulator support"
help
@ -56,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

View File

@ -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
@ -176,5 +177,6 @@ obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o
obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o
obj-$(CONFIG_REGULATOR_STUB) += stub-regulator.o
obj-$(CONFIG_REGULATOR_DEBUG_CONTROL) += debug-regulator.o
ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG

View File

@ -0,0 +1,736 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*/
#include <linux/bitops.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/debug-regulator.h>
#include <trace/events/power.h>
#include "internal.h"
struct debug_regulator {
struct list_head list;
struct regulator *reg;
struct device *dev;
struct regulator_dev *rdev;
};
static DEFINE_MUTEX(debug_reg_list_lock);
static LIST_HEAD(debug_reg_list);
static const char *rdev_name(struct regulator_dev *rdev)
{
if (rdev->constraints && rdev->constraints->name)
return rdev->constraints->name;
else if (rdev->desc->name)
return rdev->desc->name;
else
return "";
}
#define dreg_err(dreg, fmt, ...) \
pr_err("%s: %s: " fmt, __func__, rdev_name((dreg)->rdev), ##__VA_ARGS__)
#define dreg_dbg(dreg, fmt, ...) \
pr_debug("%s: %s: " fmt, __func__, rdev_name((dreg)->rdev), \
##__VA_ARGS__)
static struct regulator *reg_debug_get_consumer(struct debug_regulator *dreg)
{
struct regulator *regulator;
if (dreg->reg)
return dreg->reg;
regulator = regulator_get(NULL, rdev_name(dreg->rdev));
if (IS_ERR(regulator)) {
dreg_dbg(dreg, "debug regulator get failed, ret=%ld\n",
PTR_ERR(regulator));
return NULL;
}
dreg->reg = regulator;
return regulator;
}
static int reg_debug_enable_get(void *data, u64 *val)
{
struct debug_regulator *dreg = data;
struct regulator *regulator = reg_debug_get_consumer(dreg);
if (!regulator) {
dreg_err(dreg, "debug consumer missing\n");
return -ENODEV;
}
*val = regulator_is_enabled(regulator);
return 0;
}
static int reg_debug_enable_set(void *data, u64 val)
{
struct debug_regulator *dreg = data;
struct regulator *regulator = reg_debug_get_consumer(dreg);
int ret;
if (!regulator) {
dreg_err(dreg, "debug consumer missing\n");
return -ENODEV;
}
if (val) {
ret = regulator_enable(regulator);
if (ret)
dreg_err(dreg, "enable failed, ret=%d\n", ret);
} else {
ret = regulator_disable(regulator);
if (ret)
dreg_err(dreg, "disable failed, ret=%d\n", ret);
}
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(reg_enable_fops, reg_debug_enable_get,
reg_debug_enable_set, "%llu\n");
static int reg_debug_bypass_enable_get(void *data, u64 *val)
{
struct debug_regulator *dreg = data;
struct regulator_dev *rdev = dreg->rdev;
bool enable = false;
int ret = 0;
ww_mutex_lock(&rdev->mutex, NULL);
if (rdev->desc->ops->get_bypass) {
ret = rdev->desc->ops->get_bypass(rdev, &enable);
if (ret)
dreg_err(dreg, "get_bypass() failed, ret=%d\n", ret);
} else {
enable = (rdev->bypass_count == rdev->open_count);
}
ww_mutex_unlock(&rdev->mutex);
*val = enable;
return ret;
}
static int reg_debug_bypass_enable_set(void *data, u64 val)
{
struct debug_regulator *dreg = data;
struct regulator *regulator = reg_debug_get_consumer(dreg);
if (!regulator) {
dreg_err(dreg, "debug consumer missing\n");
return -ENODEV;
}
return regulator_allow_bypass(regulator, val);
}
DEFINE_DEBUGFS_ATTRIBUTE(reg_bypass_enable_fops, reg_debug_bypass_enable_get,
reg_debug_bypass_enable_set, "%llu\n");
static int reg_debug_force_disable_set(void *data, u64 val)
{
struct debug_regulator *dreg = data;
struct regulator *regulator = reg_debug_get_consumer(dreg);
int ret = 0;
if (!regulator) {
dreg_err(dreg, "debug consumer missing\n");
return -ENODEV;
}
if (val) {
ret = regulator_force_disable(regulator);
if (ret)
dreg_err(dreg, "force_disable failed, ret=%d\n", ret);
}
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(reg_force_disable_fops, reg_debug_enable_get,
reg_debug_force_disable_set, "%llu\n");
#define MAX_DEBUG_BUF_LEN 50
static ssize_t reg_debug_voltage_read(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct debug_regulator *dreg = file->private_data;
struct regulator *regulator = reg_debug_get_consumer(dreg);
char buf[MAX_DEBUG_BUF_LEN];
int voltage, ret;
if (!regulator) {
dreg_err(dreg, "debug consumer missing\n");
return -ENODEV;
}
voltage = regulator_get_voltage(regulator);
ret = scnprintf(buf, MAX_DEBUG_BUF_LEN, "%d\n", voltage);
return simple_read_from_buffer(ubuf, count, ppos, buf, ret);
}
static ssize_t reg_debug_voltage_write(struct file *file,
const char __user *ubuf, size_t count, loff_t *ppos)
{
struct debug_regulator *dreg = file->private_data;
struct regulator *regulator = reg_debug_get_consumer(dreg);
char buf[MAX_DEBUG_BUF_LEN];
int ret, filled;
int min_uV, max_uV = -1;
if (!regulator) {
dreg_err(dreg, "debug consumer missing\n");
return -ENODEV;
}
if (count < MAX_DEBUG_BUF_LEN) {
if (copy_from_user(buf, ubuf, count))
return -EFAULT;
buf[count] = '\0';
filled = sscanf(buf, "%d %d", &min_uV, &max_uV);
/* Check that both min and max voltage were specified. */
if (filled < 2 || min_uV < 0 || max_uV < min_uV) {
dreg_err(dreg, "incorrect values specified: \"%s\"; should be: \"min_uV max_uV\"\n",
buf);
return -EINVAL;
}
ret = regulator_set_voltage(regulator, min_uV, max_uV);
if (ret) {
dreg_err(dreg, "set_voltage(%d, %d) failed, ret=%d\n",
min_uV, max_uV, ret);
return ret;
}
} else {
dreg_err(dreg, "voltage request string exceeds maximum buffer size\n");
return -EINVAL;
}
return count;
}
static const struct file_operations reg_voltage_fops = {
.open = simple_open,
.read = reg_debug_voltage_read,
.write = reg_debug_voltage_write,
};
static int reg_debug_mode_get(void *data, u64 *val)
{
struct debug_regulator *dreg = data;
struct regulator *regulator = reg_debug_get_consumer(dreg);
int mode;
if (!regulator) {
dreg_err(dreg, "debug consumer missing\n");
return -ENODEV;
}
mode = regulator_get_mode(regulator);
if (mode < 0) {
dreg_err(dreg, "get mode failed, ret=%d\n", mode);
return mode;
}
*val = mode;
return 0;
}
static int reg_debug_mode_set(void *data, u64 val)
{
struct debug_regulator *dreg = data;
struct regulator *regulator = reg_debug_get_consumer(dreg);
unsigned int mode = val;
int ret;
if (!regulator) {
dreg_err(dreg, "debug consumer missing\n");
return -ENODEV;
}
ret = regulator_set_mode(regulator, mode);
if (ret)
dreg_err(dreg, "set mode=%u failed, ret=%d\n", mode, ret);
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(reg_mode_fops, reg_debug_mode_get, reg_debug_mode_set,
"%llu\n");
static int reg_debug_set_load(void *data, u64 val)
{
struct debug_regulator *dreg = data;
struct regulator *regulator = reg_debug_get_consumer(dreg);
int load = val;
int ret;
if (!regulator) {
dreg_err(dreg, "debug consumer missing\n");
return -ENODEV;
}
ret = regulator_set_load(regulator, load);
if (ret)
dreg_err(dreg, "set load=%d failed, ret=%d\n", load, ret);
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(reg_set_load_fops, reg_debug_mode_get,
reg_debug_set_load, "%llu\n");
static int reg_debug_consumers_show(struct seq_file *m, void *v)
{
struct regulator_dev *rdev = m->private;
struct regulator *reg;
const char *supply_name;
ww_mutex_lock(&rdev->mutex, NULL);
/* Print a header if there are consumers. */
if (rdev->open_count)
seq_printf(m, "%-32s EN Min_uV Max_uV load_uA\n",
"Device-Supply");
list_for_each_entry(reg, &rdev->consumer_list, list) {
if (reg->supply_name)
supply_name = reg->supply_name;
else
supply_name = "(null)-(null)";
seq_printf(m, "%-32s %c %8d %8d %8d\n", supply_name,
(reg->enable_count ? 'Y' : 'N'),
reg->voltage[PM_SUSPEND_ON].min_uV,
reg->voltage[PM_SUSPEND_ON].max_uV,
reg->uA_load);
}
ww_mutex_unlock(&rdev->mutex);
return 0;
}
static int reg_debug_consumers_open(struct inode *inode, struct file *file)
{
return single_open(file, reg_debug_consumers_show, inode->i_private);
}
static const struct file_operations reg_consumers_fops = {
.open = reg_debug_consumers_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
/**
* regulator_debug_add() - register a debug regulator for the specified
* regulator
* @dev: Device pointer of the regulator
* @rdev: Regulator dev pointer of the regulator
*
* This function adds various debugfs files for the 'rdev' regulator which
* provide a mechanism for userspace to vote and monitor the state of the
* regulator. When one of these debugfs files is accessed,
* reg_debug_get_consumer() is called which uses regulator_get() to get a handle
* to the regulator.
*
* Returns 0 on success or an errno on failure.
*/
static struct debug_regulator *regulator_debug_add(struct device *dev,
struct regulator_dev *rdev)
{
struct debug_regulator *dreg = NULL;
const struct regulator_ops *ops;
struct dentry *dir;
mode_t mode;
if (!dev || !rdev) {
pr_err("%s: dev or rdev is NULL\n", __func__);
return ERR_PTR(-EINVAL);
}
dreg = kzalloc(sizeof(*dreg), GFP_KERNEL);
if (!dreg)
return ERR_PTR(-ENOMEM);
dreg->dev = dev;
dreg->rdev = rdev;
mutex_lock(&debug_reg_list_lock);
list_add(&dreg->list, &debug_reg_list);
mutex_unlock(&debug_reg_list_lock);
ops = rdev->desc->ops;
dir = rdev->debugfs;
debugfs_create_file_unsafe("enable", 0644, dir, dreg, &reg_enable_fops);
if (ops->set_bypass)
debugfs_create_file_unsafe("bypass", 0644, dir, dreg,
&reg_bypass_enable_fops);
mode = 0;
if (ops->is_enabled)
mode |= 0444;
if (ops->disable)
mode |= 0200;
if (mode)
debugfs_create_file_unsafe("force_disable", mode, dir, dreg,
&reg_force_disable_fops);
mode = 0;
if (ops->get_voltage || ops->get_voltage_sel)
mode |= 0444;
if (ops->set_voltage || ops->set_voltage_sel)
mode |= 0200;
if (mode)
debugfs_create_file_unsafe("voltage", mode, dir, dreg,
&reg_voltage_fops);
mode = 0;
if (ops->get_mode)
mode |= 0444;
if (ops->set_mode)
mode |= 0200;
if (mode)
debugfs_create_file_unsafe("mode", mode, dir, dreg,
&reg_mode_fops);
mode = 0;
if (ops->get_mode)
mode |= 0444;
if (ops->set_load || (ops->get_optimum_mode && ops->set_mode))
mode |= 0200;
if (mode)
debugfs_create_file_unsafe("load", mode, dir, dreg,
&reg_set_load_fops);
debugfs_create_file("consumers", 0444, dir, rdev, &reg_consumers_fops);
return dreg;
}
/**
* regulator_debug_register() - register a debug regulator for the specified
* regulator
* @dev: Device pointer of the regulator
* @rdev: Regulator dev pointer of the regulator
*
* This function calls regulator_debug_add() which adds several debugfs files
* for the 'rdev' regulator which allow for userspace regulator state voting and
* monitoring.
*
* Returns 0 on success or an errno on failure.
*/
int regulator_debug_register(struct device *dev, struct regulator_dev *rdev)
{
return PTR_ERR_OR_ZERO(regulator_debug_add(dev, rdev));
}
EXPORT_SYMBOL(regulator_debug_register);
/* debug_reg_list_lock must be held by caller. */
static void regulator_debug_remove(struct debug_regulator *dreg)
{
regulator_put(dreg->reg);
list_del(&dreg->list);
kfree(dreg);
}
/**
* regulator_debug_unregister() - unregister the debug regulator associated with
* a regulator
* @rdev: Regulator dev pointer of the regulator
*
* This function removes the debugfs consumer registered for 'rdev' and then
* frees the debug regulator's resources.
*/
void regulator_debug_unregister(struct regulator_dev *rdev)
{
struct debug_regulator *dreg, *temp;
if (IS_ERR_OR_NULL(rdev)) {
pr_err("%s: invalid regulator device pointer\n", __func__);
return;
}
mutex_lock(&debug_reg_list_lock);
list_for_each_entry_safe(dreg, temp, &debug_reg_list, list) {
if (dreg->rdev == rdev)
regulator_debug_remove(dreg);
}
mutex_unlock(&debug_reg_list_lock);
}
EXPORT_SYMBOL(regulator_debug_unregister);
/* debug_reg_list_lock must be held by caller. */
static void _devm_regulator_debug_release(struct device *dev, void *res)
{
struct debug_regulator *dreg = *(struct debug_regulator **)res;
struct debug_regulator *temp;
bool found = false;
/*
* The debug regulator may have already been removed due to a
* devm_regulator_debug_unregister() call. Therefore, verify that it is
* still in the list before attempting to remove it.
*/
list_for_each_entry(temp, &debug_reg_list, list) {
if (temp == dreg) {
found = true;
break;
}
}
if (found)
regulator_debug_remove(dreg);
}
static void devm_regulator_debug_release(struct device *dev, void *res)
{
mutex_lock(&debug_reg_list_lock);
_devm_regulator_debug_release(dev, res);
mutex_unlock(&debug_reg_list_lock);
}
/**
* devm_regulator_debug_register() - resource managed version of
* regulator_debug_register()
* @dev: Device pointer of the regulator
* @rdev: Regulator dev pointer of the regulator
*
* This is a resource managed version of regulator_debug_register().
* The debugfs consumer added via this call is automatically removed via
* regulator_debug_unregister() on driver detach. See regulator_debug_register()
* for more details.
*
* Returns 0 on success or an errno on failure.
*/
int devm_regulator_debug_register(struct device *dev,
struct regulator_dev *rdev)
{
struct debug_regulator *dreg;
struct debug_regulator **ptr;
ptr = devres_alloc(devm_regulator_debug_release, sizeof(*ptr),
GFP_KERNEL);
if (!ptr)
return -ENOMEM;
dreg = regulator_debug_add(dev, rdev);
if (IS_ERR_OR_NULL(dreg)) {
devres_free(ptr);
return PTR_ERR(dreg);
}
*ptr = dreg;
devres_add(dev, ptr);
return 0;
}
EXPORT_SYMBOL(devm_regulator_debug_register);
static int devm_regulator_debug_match(struct device *dev, void *res, void *data)
{
struct debug_regulator **dreg = res;
if (!dreg || !*dreg) {
WARN_ON(!dreg || !*dreg);
return 0;
}
return *dreg == data;
}
/**
* devm_regulator_debug_unregister() - resource managed version of
* regulator_debug_unregister()
* @rdev: Regulator dev pointer of the regulator
*
* Deallocate the debug regulator allocated for 'rdev' with
* devm_regulator_debug_register(). Normally this function will not
* need to be called and the resource management code will ensure that the
* resource is freed.
*/
void devm_regulator_debug_unregister(struct regulator_dev *rdev)
{
struct debug_regulator *dreg, *temp;
if (IS_ERR_OR_NULL(rdev)) {
pr_err("%s: invalid regulator device pointer\n", __func__);
return;
}
mutex_lock(&debug_reg_list_lock);
list_for_each_entry_safe(dreg, temp, &debug_reg_list, list) {
if (dreg->rdev == rdev)
devres_release(dreg->dev, _devm_regulator_debug_release,
devm_regulator_debug_match, dreg);
}
mutex_unlock(&debug_reg_list_lock);
}
EXPORT_SYMBOL(devm_regulator_debug_unregister);
static int _regulator_is_enabled(struct regulator_dev *rdev)
{
if (rdev->ena_pin)
return rdev->ena_gpio_state;
if (!rdev->desc->ops->is_enabled)
return 1;
return rdev->desc->ops->is_enabled(rdev);
}
static void regulator_debug_print_enabled(struct regulator_dev *rdev)
{
struct regulator *reg;
const char *supply_name;
int mode = -EPERM;
int uV = -EPERM;
if (_regulator_is_enabled(rdev) <= 0)
return;
uV = regulator_get_voltage_rdev(rdev);
if (rdev->desc->ops->get_mode)
mode = rdev->desc->ops->get_mode(rdev);
if (uV != -EPERM && mode != -EPERM)
pr_info("%s[%u] %d uV, mode=%d\n",
rdev_name(rdev), rdev->use_count, uV, mode);
else if (uV != -EPERM)
pr_info("%s[%u] %d uV\n",
rdev_name(rdev), rdev->use_count, uV);
else if (mode != -EPERM)
pr_info("%s[%u], mode=%d\n",
rdev_name(rdev), rdev->use_count, mode);
else
pr_info("%s[%u]\n", rdev_name(rdev), rdev->use_count);
/* Print a header if there are consumers. */
if (rdev->open_count)
pr_info(" %-32s EN Min_uV Max_uV load_uA\n",
"Device-Supply");
list_for_each_entry(reg, &rdev->consumer_list, list) {
if (reg->supply_name)
supply_name = reg->supply_name;
else
supply_name = "(null)-(null)";
pr_info(" %-32s %d %8d %8d %8d\n", supply_name,
reg->enable_count,
reg->voltage[PM_SUSPEND_ON].min_uV,
reg->voltage[PM_SUSPEND_ON].max_uV,
reg->uA_load);
}
}
static void regulator_debug_suspend_trace_probe(void *unused,
const char *action, int val, bool start)
{
struct debug_regulator *dreg;
if (start && val > 0 && !strcmp("machine_suspend", action)) {
pr_info("Enabled regulators:\n");
list_for_each_entry(dreg, &debug_reg_list, list)
regulator_debug_print_enabled(dreg->rdev);
}
}
static bool debug_suspend;
static struct dentry *regulator_suspend_debugfs;
static int reg_debug_suspend_enable_get(void *data, u64 *val)
{
*val = debug_suspend;
return 0;
}
static int reg_debug_suspend_enable_set(void *data, u64 val)
{
int ret;
val = !!val;
if (val == debug_suspend)
return 0;
if (val)
ret = register_trace_suspend_resume(
regulator_debug_suspend_trace_probe, NULL);
else
ret = unregister_trace_suspend_resume(
regulator_debug_suspend_trace_probe, NULL);
if (ret) {
pr_err("%s: Failed to %sregister suspend trace callback, ret=%d\n",
__func__, val ? "" : "un", ret);
return ret;
}
debug_suspend = val;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(reg_debug_suspend_enable_fops,
reg_debug_suspend_enable_get, reg_debug_suspend_enable_set, "%llu\n");
static int __init regulator_debug_init(void)
{
static struct dentry *dir;
int ret;
dir = debugfs_lookup("regulator", NULL);
if (IS_ERR_OR_NULL(dir)) {
ret = PTR_ERR(dir);
pr_err("%s: unable to find root regulator debugfs directory, ret=%d\n",
__func__, ret);
return 0;
}
regulator_suspend_debugfs = debugfs_create_file_unsafe("debug_suspend",
0644, dir, NULL,
&reg_debug_suspend_enable_fops);
dput(dir);
if (IS_ERR(regulator_suspend_debugfs)) {
ret = PTR_ERR(regulator_suspend_debugfs);
pr_err("%s: unable to create regulator debug_suspend debugfs directory, ret=%d\n",
__func__, ret);
}
return 0;
}
module_init(regulator_debug_init);
static void __exit regulator_debug_exit(void)
{
debugfs_remove(regulator_suspend_debugfs);
if (debug_suspend)
unregister_trace_suspend_resume(
regulator_debug_suspend_trace_probe, NULL);
}
module_exit(regulator_debug_exit);
MODULE_DESCRIPTION("Regulator debug control library");
MODULE_LICENSE("GPL v2");

View File

@ -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 <linux/bitops.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.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;
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");

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012, 2016, 2018, The Linux Foundation. All rights reserved.
* Copyright (c) 2012, 2016, 2018, 2020, The Linux Foundation.
* All rights reserved.
*/
#include <linux/device.h>
@ -12,6 +13,7 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/regulator/debug-regulator.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
@ -207,6 +209,10 @@ static int regulator_stub_probe(struct platform_device *pdev)
return rc;
}
rc = devm_regulator_debug_register(dev, rdev);
if (rc)
dev_err(dev, "failed to register debug regulator, rc=%d\n", rc);
return 0;
}

View File

@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*/
#ifndef _LINUX_REGULATOR_DEBUG_CONTROL_H_
#define _LINUX_REGULATOR_DEBUG_CONTROL_H_
struct device;
struct regulator_dev;
#if IS_ENABLED(CONFIG_REGULATOR_DEBUG_CONTROL)
int regulator_debug_register(struct device *dev, struct regulator_dev *rdev);
void regulator_debug_unregister(struct regulator_dev *rdev);
int devm_regulator_debug_register(struct device *dev,
struct regulator_dev *rdev);
void devm_regulator_debug_unregister(struct regulator_dev *rdev);
#else
static inline int regulator_debug_register(struct device *dev,
struct regulator_dev *rdev)
{ return 0; }
static inline void regulator_debug_unregister(struct regulator_dev *rdev)
{ }
static inline int devm_regulator_debug_register(struct device *dev,
struct regulator_dev *rdev)
{ return 0; }
static inline void devm_regulator_debug_unregister(struct regulator_dev *rdev)
{ }
#endif
#endif

View File

@ -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 <linux/device.h>
#include <linux/of.h>
#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