From: Ruslan Babayev <[email protected]>

Registers VOUT_COMMAND, VOUT_MARGIN_HIGH and VOUT_MARGIN_LOW are
described in PMBUS Spec Part II Rev 1.2. Exposing them in the PMBUS
core allows the drivers to turn them on with a corresponding
PMBUS_HAVE_VOUT_... flags.

Cc: [email protected]
Signed-off-by: Ruslan Babayev <[email protected]>
---
 drivers/hwmon/pmbus/pmbus.h      |   7 ++
 drivers/hwmon/pmbus/pmbus_core.c | 183 +++++++++++++++++++++++++++++++
 2 files changed, 190 insertions(+)

diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h
index 1d24397d36ec..723648b3da36 100644
--- a/drivers/hwmon/pmbus/pmbus.h
+++ b/drivers/hwmon/pmbus/pmbus.h
@@ -223,6 +223,9 @@ enum pmbus_regs {
  * OPERATION
  */
 #define PB_OPERATION_CONTROL_ON                BIT(7)
+#define PB_OPERATION_MARGIN_HIGH       BIT(5)
+#define PB_OPERATION_MARGIN_LOW                BIT(4)
+#define PB_OPERATION_ACT_ON_FAULT      BIT(3)
 
 /*
  * CAPABILITY
@@ -291,6 +294,7 @@ enum pmbus_fan_mode { percent = 0, rpm };
 /*
  * STATUS_VOUT, STATUS_INPUT
  */
+#define PB_VOLTAGE_MAX_WARNING         BIT(3)
 #define PB_VOLTAGE_UV_FAULT            BIT(4)
 #define PB_VOLTAGE_UV_WARNING          BIT(5)
 #define PB_VOLTAGE_OV_WARNING          BIT(6)
@@ -371,6 +375,9 @@ enum pmbus_sensor_classes {
 #define PMBUS_HAVE_STATUS_VMON BIT(19)
 #define PMBUS_HAVE_PWM12       BIT(20)
 #define PMBUS_HAVE_PWM34       BIT(21)
+#define PMBUS_HAVE_VOUT_COMMAND                BIT(22)
+#define PMBUS_HAVE_VOUT_MARGIN_HIGH    BIT(23)
+#define PMBUS_HAVE_VOUT_MARGIN_LOW     BIT(24)
 
 #define PMBUS_PAGE_VIRTUAL     BIT(31)
 
diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c
index 2e2b5851139c..f35b239961e3 100644
--- a/drivers/hwmon/pmbus/pmbus_core.c
+++ b/drivers/hwmon/pmbus/pmbus_core.c
@@ -89,6 +89,14 @@ struct pmbus_label {
 #define to_pmbus_label(_attr) \
        container_of(_attr, struct pmbus_label, attribute)
 
+struct pmbus_operation {
+       char name[PMBUS_NAME_SIZE];     /* sysfs label name */
+       struct sensor_device_attribute attribute;
+       u8 page;
+};
+#define to_pmbus_operation(_attr) \
+       container_of(_attr, struct pmbus_operation, attribute)
+
 struct pmbus_data {
        struct device *dev;
        struct device *hwmon_dev;
@@ -1004,6 +1012,65 @@ static ssize_t pmbus_show_label(struct device *dev,
        return snprintf(buf, PAGE_SIZE, "%s\n", label->label);
 }
 
+static ssize_t pmbus_show_operation(struct device *dev,
+                                   struct device_attribute *devattr, char *buf)
+{
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       struct pmbus_operation *operation = to_pmbus_operation(attr);
+       struct i2c_client *client = to_i2c_client(dev->parent);
+       int ret;
+
+       ret = pmbus_read_byte_data(client, operation->page, PMBUS_OPERATION);
+       if (ret < 0)
+               return ret;
+
+       if (ret & PB_OPERATION_CONTROL_ON) {
+               if (ret & PB_OPERATION_MARGIN_HIGH)
+                       return snprintf(buf, PAGE_SIZE, "high\n");
+               else if (ret & PB_OPERATION_MARGIN_LOW)
+                       return snprintf(buf, PAGE_SIZE, "low\n");
+               else
+                       return snprintf(buf, PAGE_SIZE, "on\n");
+       } else {
+                       return snprintf(buf, PAGE_SIZE, "off\n");
+       }
+}
+
+static ssize_t pmbus_set_operation(struct device *dev,
+                                  struct device_attribute *devattr,
+                                  const char *buf, size_t count)
+{
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       struct pmbus_operation *operation = to_pmbus_operation(attr);
+       struct i2c_client *client = to_i2c_client(dev->parent);
+       int ret;
+       u8 val;
+
+       /*
+        * sysfs_streq() doesn't need the \n's, but we add them so the strings
+        * will be shared with pmbus_show_operation() above.
+        */
+       if (sysfs_streq(buf, "on\n"))
+               val = PB_OPERATION_CONTROL_ON;
+       else if (sysfs_streq(buf, "off\n"))
+               val = 0;
+       else if (sysfs_streq(buf, "high\n"))
+               val = PB_OPERATION_CONTROL_ON | PB_OPERATION_ACT_ON_FAULT |
+                     PB_OPERATION_MARGIN_HIGH;
+       else if (sysfs_streq(buf, "low\n"))
+               val = PB_OPERATION_CONTROL_ON | PB_OPERATION_ACT_ON_FAULT |
+                     PB_OPERATION_MARGIN_LOW;
+       else
+               return -EINVAL;
+
+       ret = pmbus_write_byte_data(client, operation->page,
+                                   PMBUS_OPERATION, val);
+       if (ret < 0)
+               return ret;
+
+       return count;
+}
+
 static int pmbus_add_attribute(struct pmbus_data *data, struct attribute *attr)
 {
        if (data->num_attributes >= data->max_attributes - 1) {
@@ -1143,6 +1210,29 @@ static int pmbus_add_label(struct pmbus_data *data,
        return pmbus_add_attribute(data, &a->attr);
 }
 
+static int pmbus_add_operation(struct pmbus_data *data, const char *name,
+                              int seq, int page)
+{
+       struct pmbus_operation *operation;
+       struct sensor_device_attribute *a;
+
+       operation = devm_kzalloc(data->dev, sizeof(*operation), GFP_KERNEL);
+       if (!operation)
+               return -ENOMEM;
+
+       a = &operation->attribute;
+
+       snprintf(operation->name, sizeof(operation->name), "%s%d_operation",
+                name, seq);
+
+       operation->page = page;
+
+       pmbus_attr_init(a, operation->name, S_IRUGO | S_IWUSR,
+                       pmbus_show_operation, pmbus_set_operation, seq);
+
+       return pmbus_add_attribute(data, &a->dev_attr.attr);
+}
+
 /*
  * Search for attributes. Allocate sensors, booleans, and labels as needed.
  */
@@ -1901,6 +1991,93 @@ static int pmbus_add_fan_attributes(struct i2c_client 
*client,
        return 0;
 }
 
+static const struct pmbus_limit_attr vout_cmd_limit_attrs[] = {
+       {
+               .reg = PMBUS_VOUT_MAX,
+               .attr = "max",
+               .alarm = "max_alarm",
+               .sbit = PB_VOLTAGE_MAX_WARNING,
+       }
+};
+
+static const struct pmbus_sensor_attr vout_attributes[] = {
+       {
+               .reg = PMBUS_VOUT_COMMAND,
+               .class = PSC_VOLTAGE_OUT,
+               .label = "command",
+               .paged = true,
+               .func = PMBUS_HAVE_VOUT_COMMAND,
+               .sfunc = PMBUS_HAVE_STATUS_VOUT,
+               .sbase = PB_STATUS_VOUT_BASE,
+               .gbit = PB_STATUS_VOUT_OV,
+               .limit = vout_cmd_limit_attrs,
+               .nlimit = ARRAY_SIZE(vout_cmd_limit_attrs),
+       }, {
+               .reg = PMBUS_VOUT_MARGIN_HIGH,
+               .class = PSC_VOLTAGE_OUT,
+               .label = "margin_high",
+               .paged = true,
+               .func = PMBUS_HAVE_VOUT_MARGIN_HIGH,
+       }, {
+               .reg = PMBUS_VOUT_MARGIN_LOW,
+               .class = PSC_VOLTAGE_OUT,
+               .label = "margin_low",
+               .paged = true,
+               .func = PMBUS_HAVE_VOUT_MARGIN_LOW,
+       }
+};
+
+static int pmbus_add_vout_attrs(struct i2c_client *client,
+                               struct pmbus_data *data,
+                               const char *name,
+                               const struct pmbus_sensor_attr *attr,
+                               int nattrs)
+{
+       const struct pmbus_driver_info *info = data->info;
+       struct pmbus_sensor *base;
+       int i, ret, index, page, pages;
+
+       index = 1;
+       for (page = 0; page < info->pages; page++) {
+               if (!pmbus_check_byte_register(client, page, PMBUS_OPERATION))
+                       continue;
+               ret = pmbus_add_operation(data, name, index, page);
+               if (ret)
+                       return ret;
+               index++;
+       }
+
+       for (i = 0; i < nattrs; i++) {
+               index = 1;
+               pages = attr->paged ? info->pages : 1;
+               for (page = 0; page < pages; page++) {
+                       if (!(info->func[page] & attr->func))
+                               continue;
+
+                       if (!pmbus_check_word_register(client, page, attr->reg))
+                               continue;
+
+                       base = pmbus_add_sensor(data, name, attr->label, index,
+                                               page, attr->reg, attr->class,
+                                               true, false, true);
+                       if (!base)
+                               return -ENOMEM;
+
+                       if (attr->sfunc) {
+                               ret = pmbus_add_limit_attrs(client, data, info,
+                                                           name, index, page,
+                                                           base, attr);
+                               if (ret < 0)
+                                       return ret;
+                       }
+
+                       index++;
+               }
+               attr++;
+       }
+       return 0;
+}
+
 static int pmbus_find_attributes(struct i2c_client *client,
                                 struct pmbus_data *data)
 {
@@ -1912,6 +2089,12 @@ static int pmbus_find_attributes(struct i2c_client 
*client,
        if (ret)
                return ret;
 
+       /* Output Voltage sensors */
+       ret = pmbus_add_vout_attrs(client, data, "out", vout_attributes,
+                                  ARRAY_SIZE(vout_attributes));
+       if (ret)
+               return ret;
+
        /* Current sensors */
        ret = pmbus_add_sensor_attrs(client, data, "curr", current_attributes,
                                     ARRAY_SIZE(current_attributes));
-- 
2.17.1

Reply via email to