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