[PATCH 1/4] hwmon: (pmbus) add support for output voltage registers
From: Ruslan Babayev
Date: Tue Apr 16 2019 - 15:01:00 EST
From: Ruslan Babayev <ruslan@xxxxxxxxxxx>
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: xe-linux-external@xxxxxxxxx
Signed-off-by: Ruslan Babayev <ruslan@xxxxxxxxxxx>
---
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