[PATCH 1/4] hwmon: (pmbus) add support for output voltage registers

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [LM Sensors]     [Linux Sound]     [ALSA Users]     [ALSA Devel]     [Linux Audio Users]     [Linux Media]     [Kernel]     [Gimp]     [Yosemite News]     [Linux Media]

  Powered by Linux