Allow the user to read and edit regulator information in user space
through the debugfs file system.

Signed-off-by: Brandon Leong <[email protected]>
---
Dynamic allocation of buffer added for debugfs.
Cleaned up error messages and stylistic errors.
Clean-up function created.

 drivers/regulator/core.c         |  403 ++++++++++++++++++++++++++++++++++++++
 include/linux/regulator/driver.h |    5 +
 2 files changed, 408 insertions(+), 0 deletions(-)

diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index ba521f0..50137c3 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -13,6 +13,8 @@
  *
  */
 
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/device.h>
@@ -21,6 +23,8 @@
 #include <linux/mutex.h>
 #include <linux/suspend.h>
 #include <linux/delay.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
 #include <linux/regulator/consumer.h>
 #include <linux/regulator/driver.h>
 #include <linux/regulator/machine.h>
@@ -48,6 +52,20 @@ struct regulator_map {
 };
 
 /*
+ * struct regulator_debug
+ *
+ * Created for each regulator. Used specifically for debugfs purposes.
+ */
+struct regulator_debug {
+       struct regulator *reg;
+       struct regulator_dev *rdev;
+       struct dentry *reg_subdir;
+       int enabled;
+       char *debug_buffer;
+       int debug_buffer_len;
+};
+
+/*
  * struct regulator
  *
  * One for each consumer device.
@@ -2271,6 +2289,390 @@ static int add_regulator_attributes(struct 
regulator_dev *rdev)
        return status;
 }
 
+#ifdef CONFIG_DEBUG_FS
+
+#define INIT_DEBUG_BUF_SIZE 20
+
+/* debug_buf_resize: dynamically resizes debug_buffer as needed */
+static char *debug_buf_resize(struct regulator_debug *reg_debug, int bufsize)
+{
+       if (bufsize <= reg_debug->debug_buffer_len)
+               return reg_debug->debug_buffer;
+
+       kfree(reg_debug->debug_buffer);
+
+       reg_debug->debug_buffer = kmalloc(bufsize, GFP_KERNEL);
+       reg_debug->debug_buffer_len = bufsize;
+
+       return reg_debug->debug_buffer;
+}
+
+static int reg_debug_enable_set(void *data, u64 val)
+{
+       int err_info = 0;
+       struct regulator_debug *reg_debug = data;
+
+       BUG_ON(data == NULL);
+
+       if (val && !(reg_debug->enabled))
+               err_info = regulator_enable(reg_debug->reg);
+       else if (!val && (reg_debug->enabled))
+               err_info = regulator_disable(reg_debug->reg);
+       else if (val && (reg_debug->enabled))
+               pr_err("regulator_enable has already been called for this 
regulator\n");
+       else
+               pr_err("regulator_disable has already been called for this 
regulator\n");
+
+       if (err_info < 0)
+               pr_err("regulator_enable/disable returned error: %d\n",
+                       err_info);
+       else
+               reg_debug->enabled = val;
+
+       return 0;
+}
+
+static int reg_debug_enable_get(void *data, u64 *val)
+{
+       struct regulator_debug *reg_debug = data;
+
+       BUG_ON(data == NULL);
+
+       *val = regulator_is_enabled(reg_debug->reg);
+       return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(reg_enable_fops, reg_debug_enable_get,
+                       reg_debug_enable_set, "%llu\n");
+
+static int reg_debug_fdisable_set(void *data, u64 val)
+{
+       int err_info;
+       struct regulator_debug *reg_debug = data;
+
+       BUG_ON(data == NULL);
+
+       if (val > 0) {
+               err_info = regulator_force_disable(reg_debug->reg);
+               reg_debug->enabled = 0;
+       } else
+               err_info = 0;
+
+       if (err_info < 0)
+               pr_err("regulator_force_disable returned error: %d\n",
+                       err_info);
+
+       return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(reg_fdisable_fops, reg_debug_enable_get,
+                       reg_debug_fdisable_set, "%llu\n");
+
+static ssize_t reg_debug_voltage_set(struct file *file, const char __user *buf,
+                                       size_t count, loff_t *ppos)
+{
+       int err_info, filled;
+       char *voltage_buffer;
+       struct regulator_debug *reg_debug = file->private_data;
+       int min, max = -1;
+
+       BUG_ON(file == NULL);
+
+       voltage_buffer = debug_buf_resize(reg_debug, count);
+
+       if (voltage_buffer == NULL) {
+               pr_err("debug_buf_resize returned NULL\n");
+               return -ENOMEM;
+       }
+
+       if (copy_from_user(voltage_buffer, (void __user *) buf, count))
+               return -EFAULT;
+
+       filled = sscanf(voltage_buffer, "%d %d", &min, &max);
+
+       /* check that user entered two integers */
+       if (filled < 2 || min < 0 || max < min) {
+               pr_info("Error, correct format: 'echo \"min max\" > voltage\n");
+               return -ENOMEM;
+       } else
+               err_info = regulator_set_voltage(reg_debug->reg, min, max);
+
+       if (err_info < 0)
+               pr_err("regulator_set_voltage returned error: %d\n", err_info);
+
+       return count;
+}
+
+static ssize_t reg_debug_voltage_get(struct file *file, char __user *buf,
+                                       size_t count, loff_t *ppos)
+{
+       int voltage, output;
+       char *voltage_buffer;
+       int rc = -1;
+       struct regulator_debug *reg_debug = file->private_data;
+
+       BUG_ON(file == NULL);
+
+       voltage = regulator_get_voltage(reg_debug->reg);
+
+       if (voltage < 0) {
+               pr_err("regulator_get_voltage returned error: %d", voltage);
+               return -ENOMEM;
+       } else {
+               voltage_buffer = debug_buf_resize(reg_debug,
+                                                       INIT_DEBUG_BUF_SIZE);
+
+               output = snprintf(voltage_buffer, reg_debug->debug_buffer_len,
+                                       "%d\n", voltage);
+               rc = simple_read_from_buffer((void __user *) buf, output, ppos,
+                               (void *) voltage_buffer, output);
+
+               return rc;
+       }
+}
+
+static int reg_debug_voltage_open(struct inode *inode, struct file *file)
+{
+       BUG_ON(file == NULL);
+
+       file->private_data = inode->i_private;
+       return 0;
+}
+
+static const struct file_operations reg_voltage_fops = {
+       .write  = reg_debug_voltage_set,
+       .open   = reg_debug_voltage_open,
+       .read   = reg_debug_voltage_get,
+};
+
+static int reg_debug_mode_set(void *data, u64 val)
+{
+       int err_info;
+       struct regulator_debug *reg_debug = data;
+
+       BUG_ON(data == NULL);
+
+       err_info = regulator_set_mode(reg_debug->reg, (unsigned int)val);
+
+       if (err_info < 0)
+               pr_err("regulator_set_mode returned error: %d\n", err_info);
+
+       return 0;
+}
+
+static int reg_debug_mode_get(void *data, u64 *val)
+{
+       int err_info;
+       struct regulator_debug *reg_debug = data;
+
+       BUG_ON(data == NULL);
+
+       err_info = regulator_get_mode(reg_debug->reg);
+
+       if (err_info < 0) {
+               pr_err("Regulator_get_mode returned error: %d\n", err_info);
+       } else {
+               *val = err_info;
+       }
+
+       return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(reg_mode_fops, reg_debug_mode_get,
+                       reg_debug_mode_set, "%llu\n");
+
+static int reg_debug_optimum_mode_set(void *data, u64 val)
+{
+       int err_info;
+       struct regulator_debug *reg_debug = data;
+
+       BUG_ON(data == NULL);
+
+       err_info = regulator_set_optimum_mode(reg_debug->reg,
+                                               (unsigned int)val);
+
+       if (err_info < 0) {
+               pr_err("Regulator_set_optimum_mode returned error: %d\n",
+                       err_info);
+       }
+
+       return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(reg_optimum_mode_fops, reg_debug_mode_get,
+                       reg_debug_optimum_mode_set, "%llu\n");
+
+static struct dentry *debugfs_base;
+
+static int reg_debug_init(void)
+{
+       debugfs_base = debugfs_create_dir("regulator", NULL);
+       if (IS_ERR(debugfs_base) || debugfs_base == NULL) {
+               pr_err("Debugfs_create_dir PTR_ERR: %ld\n",
+                       PTR_ERR(debugfs_base));
+               return -ENOMEM;
+       }
+
+       return 0;
+}
+
+static void regulator_debugfs_cleanup(struct regulator_dev *rdev)
+{
+       BUG_ON(rdev == NULL);
+
+       regulator_put(rdev->debug_data->reg);
+       debugfs_remove_recursive(rdev->debug_data->reg_subdir);
+       kfree(rdev->debug_data->debug_buffer);
+       kfree(rdev->debug_data);
+}
+
+/**
+ * regulator_debug_create_directory - Creates debugfs directories and files
+ *                                     for each regulator
+ *
+ * @param regulator_dev Voltage/Current regulator class device.
+ *
+ * Description: This function is run everytime "regulator_register" is run.
+ *             It is called for every regulator and initilaizes both the
+ *             regulator directory and the data files inside (ex. enable,
+ *             get/set voltage etc).
+ */
+static int regulator_debug_create_directory(struct regulator_dev 
*regulator_dev)
+{
+       struct dentry *reg_subdir;
+       struct dentry *err_ptr;
+       struct regulator *reg;
+       struct regulator_ops *reg_ops;
+       struct regulator_debug *debugfs_info;
+       mode_t mode;
+
+       if (IS_ERR(regulator_dev) || regulator_dev == NULL) {
+               pr_err("regulator_dev PTR_ERR: %ld\n", PTR_ERR(regulator_dev));
+               goto error;
+       }
+
+       if (IS_ERR(debugfs_base) || debugfs_base == NULL) {
+               pr_err("debugfs_base PTR_ERR: %ld\n", PTR_ERR(debugfs_base));
+               goto error;
+       }
+
+       reg_subdir = debugfs_create_dir(regulator_dev->desc->name,
+                                       debugfs_base);
+
+       reg = regulator_get(NULL, regulator_dev->desc->name);
+       if (IS_ERR(reg) || reg == NULL) {
+               pr_err("regulator_get PTR_ERR: %ld\n", PTR_ERR(reg));
+               goto error;
+       }
+
+       debugfs_info = kzalloc(sizeof(struct regulator_debug), GFP_KERNEL);
+
+       if (debugfs_info == NULL) {
+               pr_err("debugfs_info is NULL after kzalloc\n");
+               goto error;
+       }
+
+       /* Populate debugfs_info */
+       debugfs_info->reg = reg;
+       debugfs_info->reg_subdir = reg_subdir;
+       debugfs_info->debug_buffer = NULL;
+
+       regulator_dev->debug_data = debugfs_info;
+
+       reg_ops = regulator_dev->desc->ops;
+
+       /* Enabled File */
+       mode = 0;
+       if (reg_ops->is_enabled)
+               mode |= S_IRUGO;
+       if (reg_ops->enable || reg_ops->disable)
+               mode |= S_IWUSR;
+       if (mode)
+               err_ptr = debugfs_create_file("enable", mode, reg_subdir,
+                                               debugfs_info, &reg_enable_fops);
+       if (IS_ERR(err_ptr)) {
+               pr_err("Failed to create enable file: %ld\n", PTR_ERR(err_ptr));
+               debugfs_remove_recursive(reg_subdir);
+               goto error;
+       }
+
+       /* Force-Disable File */
+       mode = 0;
+       if (reg_ops->is_enabled)
+               mode |= S_IRUGO;
+       if (reg_ops->enable || reg_ops->disable)
+               mode |= S_IWUSR;
+       if (mode)
+               err_ptr = debugfs_create_file("force_disable", mode, reg_subdir,
+                                       debugfs_info, &reg_fdisable_fops);
+       if (IS_ERR(err_ptr)) {
+               pr_err("Failed to create force_disable file: %ld\n",
+                       PTR_ERR(err_ptr));
+               debugfs_remove_recursive(reg_subdir);
+               goto error;
+       }
+
+       /* Voltage File */
+       mode = 0;
+       if (reg_ops->get_voltage)
+               mode |= S_IRUGO;
+       if (reg_ops->set_voltage)
+               mode |= S_IWUSR;
+       if (mode)
+               err_ptr = debugfs_create_file("voltage", mode, reg_subdir,
+                                       debugfs_info, &reg_voltage_fops);
+       if (IS_ERR(err_ptr)) {
+               pr_err("Failed to create voltage file: %ld\n",
+                       PTR_ERR(err_ptr));
+               debugfs_remove_recursive(reg_subdir);
+               goto error;
+       }
+
+       /* Mode File */
+       mode = 0;
+       if (reg_ops->get_mode)
+               mode |= S_IRUGO;
+       if (reg_ops->set_mode)
+               mode |= S_IWUSR;
+       if (mode)
+               err_ptr = debugfs_create_file("mode", mode, reg_subdir,
+                                               debugfs_info, &reg_mode_fops);
+       if (IS_ERR(err_ptr)) {
+               pr_err("Failed to create mode file: %ld\n", PTR_ERR(err_ptr));
+               debugfs_remove_recursive(reg_subdir);
+               goto error;
+       }
+
+       /* Optimum Mode File */
+       mode = 0;
+       if (reg_ops->get_mode)
+               mode |= S_IRUGO;
+       if (reg_ops->set_mode)
+               mode |= S_IWUSR;
+       if (mode)
+               err_ptr = debugfs_create_file("optimum_mode", mode, reg_subdir,
+                                       debugfs_info, &reg_optimum_mode_fops);
+       if (IS_ERR(err_ptr)) {
+               pr_err("Failed to create optimum_mode file: %ld\n",
+                       PTR_ERR(err_ptr));
+               debugfs_remove_recursive(reg_subdir);
+               goto error;
+       }
+
+       return 0;
+
+error:
+       return -ENOMEM;
+}
+#else
+static inline void regulator_debug_create_directory(struct regulator_dev
+                                               *regulator_dev) { }
+
+static inline void reg_debug_init(void) { }
+
+static inline void regulator_debugfs_cleanup(struct regulator_dev *rdev) { }
+#endif
+
 /**
  * regulator_register - register regulator
  * @regulator_desc: regulator to register
@@ -2432,6 +2834,7 @@ void regulator_unregister(struct regulator_dev *rdev)
 
        mutex_lock(&regulator_list_mutex);
        WARN_ON(rdev->open_count);
+       regulator_debugfs_cleanup(rdev);
        unset_regulator_supplies(rdev);
        list_del(&rdev->list);
        if (rdev->supply)
diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h
index 592cd7c..ce63090 100644
--- a/include/linux/regulator/driver.h
+++ b/include/linux/regulator/driver.h
@@ -20,6 +20,7 @@
 
 struct regulator_dev;
 struct regulator_init_data;
+struct regulator_debug;
 
 enum regulator_status {
        REGULATOR_STATUS_OFF,
@@ -188,6 +189,10 @@ struct regulator_dev {
        struct regulator_dev *supply;   /* for tree */
 
        void *reg_data;         /* regulator_dev data */
+
+#ifdef CONFIG_DEBUG_FS
+       struct regulator_debug *debug_data;
+#endif
 };
 
 struct regulator_dev *regulator_register(struct regulator_desc *regulator_desc,
-- 
1.7.3.1

-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to