From 491c9e1721da44f7f01d1a171debcb90c496db2d Mon Sep 17 00:00:00 2001 From: David Collins Date: Tue, 10 Jan 2017 17:34:21 -0800 Subject: [PATCH] regulator: core: add debugfs regulator monitoring and control features Add several debugfs files for each registered regulator which allow for monitoring of the regulator state and the requests from each consumer. Also configure these files as writeable so that the state of each regulator can be voted on via debugfs file write commands. Change-Id: Ia13b8f698868374499bb9be390ade4519c517e29 Signed-off-by: David Collins --- drivers/regulator/Kconfig | 10 + drivers/regulator/core.c | 398 ++++++++++++++++++++++++++++++- include/linux/regulator/driver.h | 6 + 3 files changed, 412 insertions(+), 2 deletions(-) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 3b676bf93524..120305a90945 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -29,6 +29,16 @@ config REGULATOR_DEBUG help Say yes here to enable debugging support. +config REGULATOR_QTI_DEBUG + bool "Regulator QTI debug feature support" + depends on DEBUG_FS && QGKI + help + Say yes to enable QTI debug features. This primarily provides + runtime debugfs control of regulator parameters such as enable state, + voltage, load current, and bypass state. Requests made via this + debugfs interface are aggregated the same way as normal kernel + consumer requests. + config REGULATOR_FIXED_VOLTAGE tristate "Fixed voltage regulator support" help diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 903ececbf49f..ae99fd54e855 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include #include @@ -4300,6 +4302,18 @@ int regulator_set_load(struct regulator *regulator, int uA_load) } EXPORT_SYMBOL_GPL(regulator_set_load); +#ifdef CONFIG_REGULATOR_QTI_DEBUG +static inline int regulator_open_offset(struct regulator_dev *rdev) +{ + return rdev->open_offset; +} +#else +static inline int regulator_open_offset(struct regulator_dev *rdev) +{ + return 0; +} +#endif + /** * regulator_allow_bypass - allow the regulator to go into bypass mode * @@ -4327,7 +4341,8 @@ int regulator_allow_bypass(struct regulator *regulator, bool enable) if (enable && !regulator->bypass) { rdev->bypass_count++; - if (rdev->bypass_count == rdev->open_count) { + if (rdev->bypass_count == rdev->open_count - + regulator_open_offset(rdev)) { ret = rdev->desc->ops->set_bypass(rdev, enable); if (ret != 0) rdev->bypass_count--; @@ -4336,7 +4351,8 @@ int regulator_allow_bypass(struct regulator *regulator, bool enable) } else if (!enable && regulator->bypass) { rdev->bypass_count--; - if (rdev->bypass_count != rdev->open_count) { + if (rdev->bypass_count != rdev->open_count - + regulator_open_offset(rdev)) { ret = rdev->desc->ops->set_bypass(rdev, enable); if (ret != 0) rdev->bypass_count++; @@ -4752,6 +4768,379 @@ static void regulator_dev_release(struct device *dev) kfree(rdev); } +#ifdef CONFIG_REGULATOR_QTI_DEBUG + +static struct regulator *reg_debug_get_consumer(struct regulator_dev *rdev) +{ + struct regulator *regulator; + + if (rdev->debug_consumer) + return rdev->debug_consumer; + + regulator = regulator_get(NULL, rdev_get_name(rdev)); + if (IS_ERR(regulator)) { + rdev_dbg(rdev, "debug consumer regulator get failed, ret=%ld\n", + PTR_ERR(regulator)); + return NULL; + } + rdev->debug_consumer = regulator; + if (regulator) + rdev->open_offset = 1; + + return regulator; +} + +static int reg_debug_enable_set(void *data, u64 val) +{ + struct regulator_dev *rdev = data; + struct regulator *regulator = reg_debug_get_consumer(rdev); + int ret; + + if (!regulator) { + rdev_err(rdev, "debug consumer missing\n"); + return -ENODEV; + } + + if (val) { + ret = regulator_enable(regulator); + if (ret) + rdev_err(rdev, "enable failed, ret=%d\n", ret); + } else { + ret = regulator_disable(regulator); + if (ret) + rdev_err(rdev, "disable failed, ret=%d\n", ret); + } + + return ret; +} + +static int reg_debug_enable_get(void *data, u64 *val) +{ + struct regulator_dev *rdev = data; + struct regulator *regulator = reg_debug_get_consumer(rdev); + + if (!regulator) { + rdev_err(rdev, "debug consumer missing\n"); + return -ENODEV; + } + + *val = regulator_is_enabled(regulator); + + return 0; +} +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 regulator_dev *rdev = data; + struct ww_acquire_ctx ww_ctx; + bool enable = false; + int ret = 0; + + regulator_lock_dependent(rdev, &ww_ctx); + if (rdev->desc->ops->get_bypass) { + ret = rdev->desc->ops->get_bypass(rdev, &enable); + if (ret) + rdev_err(rdev, "get_bypass() failed, ret=%d\n", ret); + } else { + enable = (rdev->bypass_count == rdev->open_count + - rdev->open_offset); + } + regulator_unlock_dependent(rdev, &ww_ctx); + + *val = enable; + + return ret; +} + +static int reg_debug_bypass_enable_set(void *data, u64 val) +{ + struct regulator_dev *rdev = data; + struct regulator *regulator = reg_debug_get_consumer(rdev); + struct ww_acquire_ctx ww_ctx; + int ret; + + if (!regulator) { + rdev_err(rdev, "debug consumer missing\n"); + return -ENODEV; + } + + regulator_lock_dependent(rdev, &ww_ctx); + rdev->open_offset = 0; + regulator_unlock_dependent(rdev, &ww_ctx); + + ret = regulator_allow_bypass(regulator, val); + + return ret; +} +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 regulator_dev *rdev = data; + struct regulator *regulator = reg_debug_get_consumer(rdev); + int ret = 0; + + if (!regulator) { + rdev_err(rdev, "debug consumer missing\n"); + return -ENODEV; + } + + if (val > 0) { + ret = regulator_force_disable(regulator); + if (ret) + rdev_err(rdev, "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_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct regulator_dev *rdev = file->private_data; + struct regulator *regulator = reg_debug_get_consumer(rdev); + char buf[MAX_DEBUG_BUF_LEN]; + int ret, filled; + int min_uV, max_uV = -1; + + if (!regulator) { + rdev_err(rdev, "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) { + rdev_err(rdev, "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) { + rdev_err(rdev, "set voltage(%d, %d) failed, ret=%d\n", + min_uV, max_uV, ret); + return ret; + } + } else { + rdev_err(rdev, "voltage request string exceeds maximum buffer size\n"); + return -EINVAL; + } + + return count; +} + +static ssize_t reg_debug_voltage_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct regulator_dev *rdev = file->private_data; + char buf[MAX_DEBUG_BUF_LEN]; + struct ww_acquire_ctx ww_ctx; + int voltage, ret; + + regulator_lock_dependent(rdev, &ww_ctx); + voltage = regulator_get_voltage_rdev(rdev); + regulator_unlock_dependent(rdev, &ww_ctx); + + ret = snprintf(buf, MAX_DEBUG_BUF_LEN - 1, "%d\n", voltage); + + return simple_read_from_buffer(ubuf, count, ppos, buf, ret); +} + +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_set(void *data, u64 val) +{ + struct regulator_dev *rdev = data; + struct regulator *regulator = reg_debug_get_consumer(rdev); + unsigned int mode = val; + int ret; + + if (!regulator) { + rdev_err(rdev, "debug consumer missing\n"); + return -ENODEV; + } + + ret = regulator_set_mode(regulator, mode); + if (ret) + rdev_err(regulator->rdev, "set mode=%u failed, ret=%d\n", + mode, ret); + + return ret; +} + +static int reg_debug_mode_get(void *data, u64 *val) +{ + struct regulator_dev *rdev = data; + int mode; + + mode = _regulator_get_mode(rdev); + if (mode < 0) { + rdev_err(rdev, "get mode failed, ret=%d\n", mode); + return mode; + } + + *val = mode; + + return 0; +} +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 regulator_dev *rdev = data; + struct regulator *regulator = reg_debug_get_consumer(rdev); + int load = val; + int ret; + + if (!regulator) { + rdev_err(rdev, "debug consumer missing\n"); + return -ENODEV; + } + + ret = regulator_set_load(regulator, load); + if (ret) + rdev_err(rdev, "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 void rdev_free_qti_debugfs(struct regulator_dev *rdev) +{ + if (!IS_ERR_OR_NULL(rdev)) { + if (rdev->debug_consumer) + rdev->debug_consumer->debugfs = NULL; + regulator_put(rdev->debug_consumer); + } +} + +static void rdev_init_qti_debugfs(struct regulator_dev *rdev) +{ + const struct regulator_ops *ops; + mode_t mode; + + rdev->debug_consumer = reg_debug_get_consumer(rdev); + ops = rdev->desc->ops; + + debugfs_create_file_unsafe("enable", 0644, rdev->debugfs, rdev, + ®_enable_fops); + if (ops->set_bypass) + debugfs_create_file_unsafe("bypass", 0644, rdev->debugfs, rdev, + ®_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, rdev->debugfs, + rdev, ®_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, rdev->debugfs, rdev, + ®_voltage_fops); + + mode = 0; + if (ops->get_mode) + mode |= 0444; + if (ops->set_mode) + mode |= 0200; + if (mode) + debugfs_create_file_unsafe("mode", mode, rdev->debugfs, rdev, + ®_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, rdev->debugfs, rdev, + ®_set_load_fops); +} + +#else + +static inline void rdev_free_qti_debugfs(struct regulator_dev *rdev) +{ +} + +static inline void rdev_init_qti_debugfs(struct regulator_dev *rdev) +{ +} + +#endif /* CONFIG_REGULATOR_QTI_DEBUG */ + +static int reg_debug_consumers_show(struct seq_file *m, void *v) +{ + struct regulator_dev *rdev = m->private; + struct ww_acquire_ctx ww_ctx; + struct regulator *reg; + const char *supply_name; + + regulator_lock_dependent(rdev, &ww_ctx); + + /* 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_dependent(rdev, &ww_ctx); + + 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, +}; + static void rdev_init_debugfs(struct regulator_dev *rdev) { struct device *parent = rdev->dev.parent; @@ -4777,6 +5166,10 @@ static void rdev_init_debugfs(struct regulator_dev *rdev) &rdev->open_count); debugfs_create_u32("bypass_count", 0444, rdev->debugfs, &rdev->bypass_count); + debugfs_create_file("consumers", 0444, rdev->debugfs, rdev, + ®_consumers_fops); + + rdev_init_qti_debugfs(rdev); } static int regulator_register_resolve_supply(struct device *dev, void *data) @@ -5234,6 +5627,7 @@ void regulator_unregister(struct regulator_dev *rdev) mutex_lock(®ulator_list_mutex); debugfs_remove_recursive(rdev->debugfs); + rdev_free_qti_debugfs(rdev); WARN_ON(rdev->open_count); regulator_remove_coupling(rdev); unset_regulator_supplies(rdev); diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index 9a911bb5fb61..e9d777373eec 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -448,6 +448,9 @@ struct regulator_dev { int exclusive; u32 use_count; u32 open_count; +#ifdef CONFIG_REGULATOR_QTI_DEBUG + u32 open_offset; +#endif u32 bypass_count; /* lists we belong to */ @@ -482,6 +485,9 @@ struct regulator_dev { /* time when this regulator was disabled last time */ unsigned long last_off_jiffy; +#ifdef CONFIG_REGULATOR_QTI_DEBUG + struct regulator *debug_consumer; +#endif }; struct regulator_dev *