From 707eebb0c9d7385593959e61d126788798697c80 Mon Sep 17 00:00:00 2001 From: Tao Huang Date: Wed, 27 Dec 2017 18:57:36 +0800 Subject: [PATCH] regulator: debugfs: Adding debugfs functions into regulator framework This change allows the user to read and edit regulator information in user space through the debugfs file system. Base on msm work. Change-Id: I4b40d4fd662e3d3d0856127e8e030fa60e938df9 Signed-off-by: Tao Huang Signed-off-by: Finley Xiao --- drivers/regulator/core.c | 299 ++++++++++++++++++++++++++++++- include/linux/regulator/driver.h | 1 + 2 files changed, 299 insertions(+), 1 deletion(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index b01fe96b14fe..e0a244ab0ef6 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -3860,11 +3862,243 @@ static struct class regulator_class = { .dev_groups = regulator_dev_groups, }; +#ifdef CONFIG_DEBUG_FS + +static int reg_debug_enable_set(void *data, u64 val) +{ + struct regulator *regulator = data; + int ret; + + if (val) { + ret = regulator_enable(regulator); + if (ret) + rdev_err(regulator->rdev, "enable failed, ret=%d\n", + ret); + } else { + ret = regulator_disable(regulator); + if (ret) + rdev_err(regulator->rdev, "disable failed, ret=%d\n", + ret); + } + + return ret; +} + +static int reg_debug_enable_get(void *data, u64 *val) +{ + struct regulator *regulator = data; + + *val = regulator_is_enabled(regulator); + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(reg_enable_fops, reg_debug_enable_get, + reg_debug_enable_set, "%llu\n"); + +static int reg_debug_force_disable_set(void *data, u64 val) +{ + struct regulator *regulator = data; + int ret = 0; + + if (val > 0) { + ret = regulator_force_disable(regulator); + if (ret) + rdev_err(regulator->rdev, "force_disable failed, ret=%d\n", + ret); + } + + return ret; +} +DEFINE_SIMPLE_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 *regulator = file->private_data; + struct regulator_dev *rdev = regulator->rdev; + struct regulator *reg; + + char buf[MAX_DEBUG_BUF_LEN]; + int ret; + int min_uV, max_uV = -1; + + if (count < MAX_DEBUG_BUF_LEN) { + if (copy_from_user(buf, ubuf, count)) + return -EFAULT; + + buf[count] = '\0'; + ret = kstrtoint(buf, 10, &min_uV); + + /* Check that target voltage were specified. */ + if (ret || min_uV < 0) { + rdev_err(regulator->rdev, "incorrect values specified: \"%s\"; should be: \"target_uV\"\n", + buf); + return -EINVAL; + } + + max_uV = rdev->constraints->max_uV; + + list_for_each_entry(reg, &rdev->consumer_list, list) { + if ((!reg->min_uV && !reg->max_uV) || + (reg == regulator)) + continue; + reg->min_uV = min_uV; + reg->max_uV = max_uV; + } + + ret = regulator_set_voltage(regulator, min_uV, max_uV); + if (ret) { + rdev_err(regulator->rdev, "set voltage(%d, %d) failed, ret=%d\n", + min_uV, max_uV, ret); + return ret; + } + } else { + rdev_err(regulator->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 *regulator = file->private_data; + char buf[MAX_DEBUG_BUF_LEN]; + int voltage, ret; + + voltage = regulator_get_voltage(regulator); + + ret = snprintf(buf, MAX_DEBUG_BUF_LEN - 1, "%d\n", voltage); + + return simple_read_from_buffer(ubuf, count, ppos, buf, ret); +} + +static int reg_debug_voltage_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +static const struct file_operations reg_voltage_fops = { + .write = reg_debug_voltage_write, + .open = reg_debug_voltage_open, + .read = reg_debug_voltage_read, +}; + +static int reg_debug_mode_set(void *data, u64 val) +{ + struct regulator *regulator = data; + unsigned int mode = val; + int ret; + + 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 *regulator = data; + int mode; + + mode = regulator_get_mode(regulator); + if (mode < 0) { + rdev_err(regulator->rdev, "get mode failed, ret=%d\n", mode); + return mode; + } + + *val = mode; + + return 0; +} +DEFINE_SIMPLE_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 *regulator = data; + int load = val; + int ret; + + ret = regulator_set_load(regulator, load); + if (ret) + rdev_err(regulator->rdev, "set load=%d failed, ret=%d\n", + load, ret); + + return ret; +} +DEFINE_SIMPLE_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; + char *supply_name; + + mutex_lock(&rdev->mutex); + + /* Print a header if there are consumers. */ + if (rdev->open_count) + seq_printf(m, "%-32s 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 %8d %8d %8d\n", supply_name, + reg->min_uV, reg->max_uV, reg->uA_load); + } + + 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 = { + .owner = THIS_MODULE, + .open = reg_debug_consumers_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void rdev_deinit_debugfs(struct regulator_dev *rdev) +{ + if (!IS_ERR_OR_NULL(rdev)) { + debugfs_remove_recursive(rdev->debugfs); + if (rdev->debug_consumer) + rdev->debug_consumer->debugfs = NULL; + regulator_put(rdev->debug_consumer); + } +} + static void rdev_init_debugfs(struct regulator_dev *rdev) { struct device *parent = rdev->dev.parent; const char *rname = rdev_get_name(rdev); char name[NAME_MAX]; + struct regulator *regulator; + const struct regulator_ops *ops; + mode_t mode; /* Avoid duplicate debugfs directory names */ if (parent && rname == rdev->desc->name) { @@ -3885,8 +4119,71 @@ 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); + + regulator = regulator_get(NULL, rdev_get_name(rdev)); + if (IS_ERR(regulator)) { + rdev_err(rdev, "regulator get failed, ret=%ld\n", + PTR_ERR(regulator)); + return; + } + rdev->debug_consumer = regulator; + + ops = rdev->desc->ops; + + debugfs_create_file("enable", 0644, rdev->debugfs, regulator, + ®_enable_fops); + + mode = 0; + if (ops->is_enabled) + mode |= 0444; + if (ops->disable) + mode |= 0200; + if (mode) + debugfs_create_file("force_disable", mode, rdev->debugfs, + regulator, ®_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("voltage", mode, rdev->debugfs, regulator, + ®_voltage_fops); + + mode = 0; + if (ops->get_mode) + mode |= 0444; + if (ops->set_mode) + mode |= 0200; + if (mode) + debugfs_create_file("mode", mode, rdev->debugfs, regulator, + ®_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("load", mode, rdev->debugfs, regulator, + ®_set_load_fops); } +#else + +static inline void rdev_deinit_debugfs(struct regulator_dev *rdev) +{ +} + +static inline void rdev_init_debugfs(struct regulator_dev *rdev) +{ +} + +#endif + static int regulator_register_resolve_supply(struct device *dev, void *data) { struct regulator_dev *rdev = dev_to_rdev(dev); @@ -4093,8 +4390,8 @@ void regulator_unregister(struct regulator_dev *rdev) regulator_disable(rdev->supply); regulator_put(rdev->supply); } + rdev_deinit_debugfs(rdev); mutex_lock(®ulator_list_mutex); - debugfs_remove_recursive(rdev->debugfs); flush_work(&rdev->disable_work.work); WARN_ON(rdev->open_count); unset_regulator_supplies(rdev); diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index 5a5afee02dd5..02e98d28de6f 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -414,6 +414,7 @@ struct regulator_dev { /* time when this regulator was disabled last time */ unsigned long last_off_jiffy; + struct regulator *debug_consumer; }; struct regulator_dev *