From 1351286ca4e0d1f4d2151067fa218ec8a057bbf0 Mon Sep 17 00:00:00 2001 From: David Collins Date: Tue, 23 Jun 2020 12:13:17 -0700 Subject: [PATCH 1/5] regulator: add debug control library Add a debug control library which can be used by regulator drivers to provide a debugfs interface to vote on the enable state, voltage, mode, and load current of its regulators. This interface is useful during live testing and debugging. Change-Id: Ib7625d3f217d4959cbd9eecb74e357e9e61503b7 Signed-off-by: David Collins --- drivers/regulator/Kconfig | 9 + drivers/regulator/Makefile | 1 + drivers/regulator/debug-regulator.c | 593 ++++++++++++++++++++++ include/linux/regulator/debug-regulator.h | 34 ++ 4 files changed, 637 insertions(+) create mode 100644 drivers/regulator/debug-regulator.c create mode 100644 include/linux/regulator/debug-regulator.h diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 8913653b8c3a..dcfe575a5b00 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -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 diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index c34f46d3ca0c..7f496720eb91 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -176,5 +176,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 diff --git a/drivers/regulator/debug-regulator.c b/drivers/regulator/debug-regulator.c new file mode 100644 index 000000000000..426d48d68b10 --- /dev/null +++ b/drivers/regulator/debug-regulator.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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: " fmt, rdev_name((dreg)->rdev), ##__VA_ARGS__) +#define dreg_dbg(dreg, fmt, ...) \ + pr_debug("%s: " fmt, 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; + + regulator_lock(rdev); + 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); + } + regulator_unlock(rdev); + + *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; + + regulator_lock(rdev); + + /* 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); + } + + regulator_unlock(rdev); + + 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("dev or rdev is NULL\n"); + 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, ®_enable_fops); + if (ops->set_bypass) + debugfs_create_file_unsafe("bypass", 0644, dir, dreg, + ®_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, + ®_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, + ®_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, + ®_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, + ®_set_load_fops); + + debugfs_create_file("consumers", 0444, dir, rdev, ®_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("invalid regulator device pointer\n"); + 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("invalid regulator device pointer\n"); + 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); + +MODULE_DESCRIPTION("Regulator debug control library"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/regulator/debug-regulator.h b/include/linux/regulator/debug-regulator.h new file mode 100644 index 000000000000..4a54a2c4beb0 --- /dev/null +++ b/include/linux/regulator/debug-regulator.h @@ -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 From 80c9b923b708e87253f276db00f19024f2117faf Mon Sep 17 00:00:00 2001 From: David Collins Date: Fri, 26 Jun 2020 14:17:01 -0700 Subject: [PATCH 2/5] regulator: stub-regulator: add support for debugfs control Add support for debugfs control of a regulator's state. This is useful for testing and debugging. Change-Id: I0be1fc1e6278df828d5ffce22f425c95b425f041 Signed-off-by: David Collins --- drivers/regulator/stub-regulator.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/regulator/stub-regulator.c b/drivers/regulator/stub-regulator.c index d9c198d5cd9f..d34e40b18adc 100644 --- a/drivers/regulator/stub-regulator.c +++ b/drivers/regulator/stub-regulator.c @@ -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 @@ -12,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -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; } From 361f4b3a23e6cd19392c946814351534bbf0c3df Mon Sep 17 00:00:00 2001 From: David Collins Date: Mon, 24 Aug 2020 17:22:28 -0700 Subject: [PATCH 3/5] regulator: debug-regulator: add regulator suspend logging support Add support to print a list of the regulators found to be enabled when entering suspend into the kernel log. This is useful for identifying which regulator consumers are not voting as expected during system suspend and resume. Change-Id: I6089871d7a5945d75fec40fdfe4787efafa1aaa0 Signed-off-by: David Collins --- drivers/regulator/debug-regulator.c | 157 ++++++++++++++++++++++++++-- 1 file changed, 150 insertions(+), 7 deletions(-) diff --git a/drivers/regulator/debug-regulator.c b/drivers/regulator/debug-regulator.c index 426d48d68b10..c65b7b8d5f65 100644 --- a/drivers/regulator/debug-regulator.c +++ b/drivers/regulator/debug-regulator.c @@ -3,8 +3,6 @@ * Copyright (c) 2020, The Linux Foundation. All rights reserved. */ -#define pr_fmt(fmt) "%s: " fmt, __func__ - #include #include #include @@ -17,10 +15,13 @@ #include #include #include +#include #include #include #include +#include + #include "internal.h" struct debug_regulator { @@ -44,9 +45,10 @@ static const char *rdev_name(struct regulator_dev *rdev) } #define dreg_err(dreg, fmt, ...) \ - pr_err("%s: " fmt, rdev_name((dreg)->rdev), ##__VA_ARGS__) + pr_err("%s: %s: " fmt, __func__, rdev_name((dreg)->rdev), ##__VA_ARGS__) #define dreg_dbg(dreg, fmt, ...) \ - pr_debug("%s: " fmt, rdev_name((dreg)->rdev), ##__VA_ARGS__) + pr_debug("%s: %s: " fmt, __func__, rdev_name((dreg)->rdev), \ + ##__VA_ARGS__) static struct regulator *reg_debug_get_consumer(struct debug_regulator *dreg) { @@ -365,7 +367,7 @@ static struct debug_regulator *regulator_debug_add(struct device *dev, mode_t mode; if (!dev || !rdev) { - pr_err("dev or rdev is NULL\n"); + pr_err("%s: dev or rdev is NULL\n", __func__); return ERR_PTR(-EINVAL); } @@ -468,7 +470,7 @@ void regulator_debug_unregister(struct regulator_dev *rdev) struct debug_regulator *dreg, *temp; if (IS_ERR_OR_NULL(rdev)) { - pr_err("invalid regulator device pointer\n"); + pr_err("%s: invalid regulator device pointer\n", __func__); return; } @@ -575,7 +577,7 @@ void devm_regulator_debug_unregister(struct regulator_dev *rdev) struct debug_regulator *dreg, *temp; if (IS_ERR_OR_NULL(rdev)) { - pr_err("invalid regulator device pointer\n"); + pr_err("%s: invalid regulator device pointer\n", __func__); return; } @@ -589,5 +591,146 @@ void devm_regulator_debug_unregister(struct regulator_dev *rdev) } 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, + ®_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"); From 6d41e3c4eeae856838b92f9564e9afdd8bc330f1 Mon Sep 17 00:00:00 2001 From: David Collins Date: Mon, 9 Nov 2020 17:15:53 -0800 Subject: [PATCH 4/5] regulator: debug-regulator: replace regulator_lock() and regulator_unlock() The regulator_lock() and regulator_unlock() functions were unexported in commit 4c9db39361da ("regulator: unexport regulator_lock/unlock()"). The debug-regulator library uses these calls to ensure mutual exclusion while reading regulator device and regulator consumer states. It does not rely upon the per-process reentrancy provided by regulator_lock() and regulator_unlock(). Therefore, it is safe to replace the calls with ww_mutex_lock() and ww_mutex_unlock() respectively. Replace these calls. Change-Id: I2dc337d5a126191cce8d2ce707ad910770ab2e1e Signed-off-by: David Collins --- drivers/regulator/debug-regulator.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/regulator/debug-regulator.c b/drivers/regulator/debug-regulator.c index c65b7b8d5f65..30d42c539562 100644 --- a/drivers/regulator/debug-regulator.c +++ b/drivers/regulator/debug-regulator.c @@ -116,7 +116,7 @@ static int reg_debug_bypass_enable_get(void *data, u64 *val) bool enable = false; int ret = 0; - regulator_lock(rdev); + ww_mutex_lock(&rdev->mutex, NULL); if (rdev->desc->ops->get_bypass) { ret = rdev->desc->ops->get_bypass(rdev, &enable); if (ret) @@ -124,7 +124,7 @@ static int reg_debug_bypass_enable_get(void *data, u64 *val) } else { enable = (rdev->bypass_count == rdev->open_count); } - regulator_unlock(rdev); + ww_mutex_unlock(&rdev->mutex); *val = enable; @@ -307,7 +307,7 @@ static int reg_debug_consumers_show(struct seq_file *m, void *v) struct regulator *reg; const char *supply_name; - regulator_lock(rdev); + ww_mutex_lock(&rdev->mutex, NULL); /* Print a header if there are consumers. */ if (rdev->open_count) @@ -327,7 +327,7 @@ static int reg_debug_consumers_show(struct seq_file *m, void *v) reg->uA_load); } - regulator_unlock(rdev); + ww_mutex_unlock(&rdev->mutex); return 0; } From be38cc2b80ff512b1ee15f90beb776840c5b0fe8 Mon Sep 17 00:00:00 2001 From: David Collins Date: Fri, 8 Nov 2019 15:08:18 -0800 Subject: [PATCH 5/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 dcfe575a5b00..83536038dade 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 7f496720eb91..2146dad6e8cb 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