2010-07-05 04:12:48

by Guenter Roeck

[permalink] [raw]
Subject: [PATCH/RFC v2 0/4] hwmon: PMBus device driver

This patchset adds support for hardware monitoring features of PMBus devices.

The driver has not yet been tested with actual hardware. It is submitted for
early review and to solicit test coverage.

A PMBus device emulator is provided for reference, to show the level of testing
performed. I don't currently plan to submit this emulator in the final patchset.
If there is interest to have it included, and if someone is willing to review
the code, I will be happy to clean it up and include it.

I will test the driver with real HW for BMR453 and LTC2978 as soon as I get
access to it. For other devices, I will add a remark that support is untested,
unless I get feedback that a specific device was tested and is working.

---
Open issues/questions:
- The driver adds new power monitoring attributes to the hwmon sysfs API.
Need to get feedback if those are acceptable.
- Currently no support for chips with multiple phases (PHASE command).
Adding this will require access to a system with a chip supporting
multiple phases (e.g., UCD9240/46/48) to determine a feasible implementation.
- Need to add support for COEFFICIENTS command. Will require system with
real chip for testing, or will not add support at this time.
- Need to add fan support. Will require system with real chip for testing, or
will not add support at this time.
Datasheets of chips with fan control support indicate that chip specific
code may be necessary. See UCD9240 or UCD90124 datasheets.

---
v2 changes:
- Calculate maximum number of attributes / sensors / booleans / labels instead
of using defines
- Increased maximum number of supported pages to the PMBus maximum of 32.
- Replaced most fixed size arrays with dynamically allocated arrays/structures.
- Added comments and clarifications
- Renamed local defines to start with PB_ or PMBUS_ prefix
- Removed pages module parameter. Replaced with code to auto-detect the number
of supported pages.
- Split probe code into several functions to make it better readable
- Replaced _fault attributes with _lcrit_alarm and _crit_alarm attributes
- Several other minor changes to address review feedback
- PMBus emulator: Fixed fault status detection code. Also use more reasonable
power limits (eg 200W instead of 2kW).


2010-07-05 04:13:06

by Guenter Roeck

[permalink] [raw]
Subject: [PATCH/RFC v2 1/4] hwmon: PMBus device driver

This driver adds support for hardware monitoring features of various PMBus
devices.

Signed-off-by: Guenter Roeck <[email protected]>
---
drivers/hwmon/Kconfig | 12 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/pmbus.c | 1396 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/hwmon/pmbus.h | 209 ++++++++
4 files changed, 1618 insertions(+), 0 deletions(-)
create mode 100644 drivers/hwmon/pmbus.c
create mode 100644 drivers/hwmon/pmbus.h

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index e19cf8e..8d53cf7 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -702,6 +702,18 @@ config SENSORS_PCF8591
These devices are hard to detect and rarely found on mainstream
hardware. If unsure, say N.

+config SENSORS_PMBUS
+ tristate "PMBus devices"
+ depends on I2C && EXPERIMENTAL
+ default n
+ help
+ If you say yes here you get hardware monitoring support for various
+ PMBus devices, including but not limited to BMR45x, LTC2978, MAX16064,
+ MAX8688, and UCD92xx.
+
+ This driver can also be built as a module. If so, the module will
+ be called pmbus.
+
config SENSORS_SHT15
tristate "Sensiron humidity and temperature sensors. SHT15 and compat."
depends on GENERIC_GPIO
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 2138ceb..88b043e 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -83,6 +83,7 @@ obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
+obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o
obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o
obj-$(CONFIG_SENSORS_SHT15) += sht15.o
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
diff --git a/drivers/hwmon/pmbus.c b/drivers/hwmon/pmbus.c
new file mode 100644
index 0000000..549d915
--- /dev/null
+++ b/drivers/hwmon/pmbus.c
@@ -0,0 +1,1396 @@
+/*
+ * Hardware monitoring driver for PMBus devices
+ *
+ * Copyright (C) 2010 Ericsson AB.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/delay.h>
+#include "pmbus.h"
+
+/*
+ * Constants needed to determine number of sensors, booleans, and labels.
+ */
+#define PMBUS_MAX_INPUT_SENSORS 11 /* 6*volt, 3*curr, 2*power */
+#define PMBUS_VOUT_SENSORS_PER_PAGE 5 /* input, min, max, lcrit,
+ crit */
+#define PMBUS_IOUT_SENSORS_PER_PAGE 4 /* input, min, max, crit */
+#define PMBUS_POUT_SENSORS_PER_PAGE 4 /* input, cap, max, crit */
+#define PMBUS_MAX_SENSORS_PER_TEMP 5 /* input, min, max, lcrit,
+ crit */
+
+#define PMBUS_MAX_INPUT_BOOLEANS 7 /* v: min_alarm, max_alarm,
+ lcrit_alarm, crit_alarm;
+ c: alarm, crit_alarm;
+ p: crit_alarm */
+#define PMBUS_VOUT_BOOLEANS_PER_PAGE 4 /* min_alarm, max_alarm,
+ lcrit_alarm, crit_alarm */
+#define PMBUS_IOUT_BOOLEANS_PER_PAGE 3 /* alarm, lcrit_alarm,
+ crit_alarm */
+#define PMBUS_POUT_BOOLEANS_PER_PAGE 2 /* alarm, crit_alarm */
+#define PMBUS_MAX_BOOLEANS_PER_TEMP 4 /* min_alarm, max_alarm,
+ lcrit_alarm, crit_alarm */
+
+#define PMBUS_MAX_INPUT_LABELS 4 /* vin, vcap, iin, pin */
+
+#define PMBUS_PAGES 32 /* Per PMBus specification */
+
+enum chips { ltc2978, max16064, max8688, pmbus, ucd9220, ucd9240 };
+
+enum pmbus_sensor_classes {
+ PSC_VOLTAGE = 0,
+ PSC_TEMPERATURE,
+ PSC_CURRENT,
+ PSC_POWER,
+ PSC_NUM_CLASSES /* Number of power sensor classes */
+};
+
+struct pmbus_config {
+ int pages; /* Total number of pages (= output sensors) */
+ bool direct; /* true if device uses direct data format */
+ /*
+ * Support one set of coefficients for each sensor type
+ * Used for chips providing data in direct mode.
+ */
+ int m[PSC_NUM_CLASSES]; /* mantissa for direct data format */
+ int b[PSC_NUM_CLASSES]; /* offset */
+ int R[PSC_NUM_CLASSES]; /* exponent */
+};
+
+#define PB_HAVE_STATUS_VOUT (1<<0)
+#define PB_HAVE_STATUS_IOUT (1<<1)
+#define PB_HAVE_STATUS_INPUT (1<<2)
+#define PB_HAVE_STATUS_TEMP (1<<3)
+
+/*
+ * status, status_vout, status_iout are paged.
+ * status_input and status_temp are unpaged.
+ */
+#define PB_NUM_STATUS_REG (PMBUS_PAGES * 3 + 2)
+
+/*
+ * Index into status register array, per status register group
+ */
+#define PB_STATUS_BASE 0
+#define PB_STATUS_VOUT_BASE (PB_STATUS_BASE + PMBUS_PAGES)
+#define PB_STATUS_IOUT_BASE (PB_STATUS_VOUT_BASE + PMBUS_PAGES)
+#define PB_STATUS_INPUT_BASE (PB_STATUS_IOUT_BASE + PMBUS_PAGES)
+#define PB_STATUS_TEMP_BASE (PB_STATUS_INPUT_BASE + 1)
+
+struct pmbus_sensor {
+ char name[I2C_NAME_SIZE]; /* sysfs sensor name */
+ struct sensor_device_attribute attribute;
+ u8 page; /* page number */
+ u8 reg; /* register */
+ enum pmbus_sensor_classes class;/* sensor class */
+ bool update; /* runtime sensor update needed */
+ u16 data; /* Sensor data */
+};
+
+struct pmbus_boolean {
+ char name[I2C_NAME_SIZE]; /* sysfs boolean name */
+ struct sensor_device_attribute attribute;
+};
+
+struct pmbus_label {
+ char name[I2C_NAME_SIZE]; /* sysfs label name */
+ struct sensor_device_attribute attribute;
+ char label[I2C_NAME_SIZE]; /* label */
+};
+
+struct pmbus_data {
+ struct device *hwmon_dev;
+ enum chips type;
+
+ bool direct;
+ int pages;
+
+ /* Coefficients, for chips providing data in direct mode */
+ int m[PSC_NUM_CLASSES]; /* mantissa for direct data format */
+ int b[PSC_NUM_CLASSES]; /* offset */
+ int R[PSC_NUM_CLASSES]; /* exponent */
+
+ int max_attributes;
+ int num_attributes;
+ struct attribute **attributes;
+ struct attribute_group group;
+
+ /*
+ * Sensors cover both sensor and limit registers.
+ */
+ int max_sensors;
+ int num_sensors;
+ struct pmbus_sensor *sensors;
+ /*
+ * Booleans are used for alarms.
+ * Values are determined from status registers.
+ */
+ int max_booleans;
+ int num_booleans;
+ struct pmbus_boolean *booleans;
+ /*
+ * Labels are used to map generic names (e.g., "in1")
+ * to PMBus specific names (e.g., "vin" or "vout1").
+ */
+ int max_labels;
+ int num_labels;
+ struct pmbus_label *labels;
+
+ struct mutex update_lock;
+ bool valid;
+ unsigned long last_updated; /* in jiffies */
+
+ /*
+ * A single status register covers multiple attributes,
+ * so we keep them all together.
+ */
+ u8 status_bits;
+ u8 status[PB_NUM_STATUS_REG];
+
+ u8 currpage;
+};
+
+static const struct pmbus_config pmbus_config[] = {
+ [pmbus] = {
+ .pages = 1,
+ },
+ [ltc2978] = {
+ .pages = 8,
+ },
+ [max16064] = {
+ .pages = 4,
+ .direct = true,
+ .m[PSC_VOLTAGE] = 19995, /* Coefficients from datasheet */
+ .b[PSC_VOLTAGE] = 0,
+ .R[PSC_VOLTAGE] = -1,
+ .m[PSC_TEMPERATURE] = -7612,
+ .b[PSC_TEMPERATURE] = 335,
+ .R[PSC_TEMPERATURE] = -3,
+ },
+ [max8688] = {
+ .pages = 1,
+ .direct = true,
+ .m[PSC_VOLTAGE] = 19995, /* Coefficients from datasheet */
+ .b[PSC_VOLTAGE] = 0,
+ .R[PSC_VOLTAGE] = -1,
+ .m[PSC_TEMPERATURE] = -7612,
+ .b[PSC_TEMPERATURE] = 335,
+ .R[PSC_TEMPERATURE] = -3,
+ .m[PSC_CURRENT] = 23109,
+ .b[PSC_CURRENT] = 0,
+ .R[PSC_CURRENT] = -2,
+ },
+ [ucd9220] = {
+ .pages = 2,
+ },
+ [ucd9240] = {
+ .pages = 4,
+ },
+};
+
+static int pmbus_set_page(struct i2c_client *client, u8 page)
+{
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ int rv = 0;
+
+ if (page != data->currpage) {
+ rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ data->currpage = page;
+ }
+ return rv;
+}
+
+static int pmbus_write_byte(struct i2c_client *client, u8 page, u8 value)
+{
+ int rv;
+
+ rv = pmbus_set_page(client, page);
+ if (rv < 0)
+ return rv;
+
+ return i2c_smbus_write_byte(client, value);
+}
+
+static int pmbus_write_word_data(struct i2c_client *client, u8 page, u8 reg,
+ u16 word)
+{
+ int rv;
+
+ rv = pmbus_set_page(client, page);
+ if (rv < 0)
+ return rv;
+
+ return i2c_smbus_write_word_data(client, reg, word);
+}
+
+static int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg)
+{
+ int rv;
+
+ rv = pmbus_set_page(client, page);
+ if (rv < 0)
+ return rv;
+
+ return i2c_smbus_read_word_data(client, reg);
+}
+
+static int pmbus_read_byte_data(struct i2c_client *client, u8 page, u8 reg)
+{
+ int rv;
+
+ rv = pmbus_set_page(client, page);
+ if (rv < 0)
+ return rv;
+
+ return i2c_smbus_read_byte_data(client, reg);
+}
+
+static void pmbus_clear_faults(struct i2c_client *client)
+{
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ int i;
+
+ for (i = 0; i < data->pages; i++)
+ pmbus_write_byte(client, i, PMBUS_CLEAR_FAULTS);
+}
+
+static bool pmbus_check_byte_register(struct i2c_client *client, int page,
+ int reg)
+{
+ return (pmbus_read_byte_data(client, page, reg) >= 0);
+}
+
+static bool pmbus_check_word_register(struct i2c_client *client, int page,
+ int reg)
+{
+ return (pmbus_read_word_data(client, page, reg) >= 0);
+}
+
+static struct pmbus_data *pmbus_update_device(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ struct pmbus_data *ret = data;
+
+ mutex_lock(&data->update_lock);
+ if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
+ int i, val;
+
+ for (i = 0; i < data->pages; i++) {
+ val = pmbus_read_byte_data(client, i,
+ PMBUS_STATUS_BYTE);
+ if (val < 0) {
+ ret = ERR_PTR(val);
+ goto abort;
+ }
+ data->status[PB_STATUS_BASE + i] = val;
+ }
+ if (data->status_bits & PB_HAVE_STATUS_VOUT)
+ for (i = 0; i < data->pages; i++) {
+ val = pmbus_read_byte_data(client, i,
+ PMBUS_STATUS_VOUT);
+ if (val < 0) {
+ ret = ERR_PTR(val);
+ goto abort;
+ }
+ data->status[PB_STATUS_VOUT_BASE + i] = val;
+ }
+ if (data->status_bits & PB_HAVE_STATUS_IOUT)
+ for (i = 0; i < data->pages; i++) {
+ val = pmbus_read_byte_data(client, i,
+ PMBUS_STATUS_IOUT);
+ if (val < 0) {
+ ret = ERR_PTR(val);
+ goto abort;
+ }
+ data->status[PB_STATUS_IOUT_BASE + i] = val;
+ }
+ if (data->status_bits & PB_HAVE_STATUS_INPUT) {
+ val = pmbus_read_byte_data(client, 0,
+ PMBUS_STATUS_INPUT);
+ if (val < 0) {
+ ret = ERR_PTR(val);
+ goto abort;
+ }
+ data->status[PB_STATUS_INPUT_BASE] = val;
+ }
+ if (data->status_bits & PB_HAVE_STATUS_TEMP) {
+ val = pmbus_read_byte_data(client, 0,
+ PMBUS_STATUS_TEMPERATURE);
+ if (val < 0) {
+ ret = ERR_PTR(val);
+ goto abort;
+ }
+ data->status[PB_STATUS_TEMP_BASE] = val;
+ }
+ for (i = 0; i < data->num_sensors; i++) {
+ struct pmbus_sensor *sensor = &data->sensors[i];
+
+ if (!data->valid || sensor->update) {
+ val = pmbus_read_word_data(client, sensor->page,
+ sensor->reg);
+ if (val < 0) {
+ ret = ERR_PTR(val);
+ goto abort;
+ }
+ sensor->data = val;
+ }
+ }
+ pmbus_clear_faults(client);
+ data->last_updated = jiffies;
+ data->valid = 1;
+ }
+abort:
+ mutex_unlock(&data->update_lock);
+ return ret;
+}
+
+/*
+ * Convert linear sensor values to milli- or micro-units
+ * depending on sensor type.
+ */
+static long pmbus_reg2data_linear(struct pmbus_data *data,
+ struct pmbus_sensor *sensor)
+{
+ s16 exponent, mantissa;
+ long val;
+
+ exponent = sensor->data >> 11;
+ mantissa = sensor->data & 0x07ff;
+
+ if (exponent > 0x0f)
+ exponent |= 0xffe0; /* sign extend exponent */
+ if (mantissa > 0x03ff)
+ mantissa |= 0xf800; /* sign extend mantissa */
+
+ /* scale result to milli-units */
+ val = mantissa * 1000L;
+
+ /* scale result to micro-units for power sensors */
+ if (sensor->class == PSC_POWER)
+ val = val * 1000L;
+
+ if (exponent >= 0)
+ val <<= exponent;
+ else
+ val >>= -exponent;
+
+ return val;
+}
+
+/*
+ * Convert direct sensor values to milli- or micro-units
+ * depending on sensor type.
+ */
+static long pmbus_reg2data_direct(struct pmbus_data *data,
+ struct pmbus_sensor *sensor)
+{
+ long val = (s16) sensor->data;
+ long m, b, R;
+
+ m = data->m[sensor->class];
+ b = data->b[sensor->class];
+ R = data->R[sensor->class];
+
+ if (m == 0)
+ return 0;
+
+ /* X = 1/m * (Y * 10^-R - b) */
+ R = -R + 3; /* scale result to milli-units */
+ b *= 1000; /* subtract milli-units */
+
+ /* scale result to micro-units for power sensors */
+ if (sensor->class == PSC_POWER) {
+ R += 3;
+ b *= 1000;
+ }
+
+ while (R > 0) {
+ val *= 10;
+ R--;
+ }
+ while (R < 0) {
+ val = DIV_ROUND_CLOSEST(val, 10);
+ R++;
+ }
+
+ return (val - b) / m;
+}
+
+static long pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor)
+{
+ long val;
+
+ if (data->direct)
+ val = pmbus_reg2data_direct(data, sensor);
+ else
+ val = pmbus_reg2data_linear(data, sensor);
+
+ return val;
+}
+
+#define MAX_MANTISSA (1023 * 1000)
+#define MIN_MANTISSA (511 * 1000)
+
+static u16 pmbus_data2reg_linear(struct pmbus_data *data,
+ enum pmbus_sensor_classes class, long val)
+{
+ s16 exponent = 0, mantissa = 0;
+ bool negative = false;
+
+ /* simple case */
+ if (val == 0)
+ return 0;
+
+ if (val < 0) {
+ negative = true;
+ val = -val;
+ }
+
+ /* Power is in uW. Convert to mW before converting. */
+ if (class == PSC_POWER)
+ val = DIV_ROUND_CLOSEST(val, 1000L);
+
+ /* Reduce large mantissa until it fits into 10 bit */
+ while (val >= MAX_MANTISSA && exponent < 15) {
+ exponent++;
+ val >>= 1;
+ }
+ /* Increase small mantissa to improve precision */
+ while (val < MIN_MANTISSA && exponent > -15) {
+ exponent--;
+ val <<= 1;
+ }
+
+ /* Convert mantissa from milli-units to units */
+ mantissa = DIV_ROUND_CLOSEST(val, 1000);
+
+ /* Ensure that resulting number is within range */
+ if (mantissa > 0x3ff)
+ mantissa = 0x3ff;
+
+ /* restore sign */
+ if (negative)
+ mantissa = -mantissa;
+
+ /* Convert to 5 bit exponent, 11 bit mantissa */
+ return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800);
+}
+
+static u16 pmbus_data2reg_direct(struct pmbus_data *data,
+ enum pmbus_sensor_classes class, long val)
+{
+ long m, b, R;
+
+ m = data->m[class];
+ b = data->b[class];
+ R = data->R[class];
+
+ /* Power is in uW. Adjust R. */
+ if (class == PSC_POWER)
+ R -= 3;
+
+ /* Calculate Y = (m * X + b) * 10^R */
+ R -= 3; /* Adjust R for data in milli-units */
+ val *= m;
+ val += b * 1000;
+
+ while (R > 0) {
+ val *= 10;
+ R--;
+ }
+ while (R < 0) {
+ val = DIV_ROUND_CLOSEST(val, 10);
+ R++;
+ }
+
+ return val;
+}
+
+static u16 pmbus_data2reg(struct pmbus_data *data,
+ enum pmbus_sensor_classes class, long val)
+{
+ u16 regval;
+
+ if (data->direct)
+ regval = pmbus_data2reg_direct(data, class, val);
+ else
+ regval = pmbus_data2reg_linear(data, class, val);
+
+ return regval;
+}
+
+/* Return converted value from given object */
+static long pmbus_get_sensor(struct pmbus_data *data, int index)
+{
+ return pmbus_reg2data(data, &data->sensors[index]);
+}
+
+/*
+ * Return boolean calculated from converted data.
+ * <index> defines a status register index and mask, and optionally
+ * two sensor indexes.
+ * The upper half-word references the two sensors,
+ * the lower half word references status register and mask.
+ * The function returns true if (status[reg] & mask) is true and,
+ * if specified, if v1 >= v2.
+ * To determine if an object exceeds upper limits, specify <v, limit>.
+ * To determine if an object exceeds lower limits, specify <limit, v>.
+ */
+static long pmbus_get_boolean(struct pmbus_data *data, int index)
+{
+ u8 s1 = (index >> 24) & 0xff;
+ u8 s2 = (index >> 16) & 0xff;
+ u8 reg = (index >> 8) & 0xff;
+ u8 mask = index & 0xff;
+ int rv = 0;
+ u8 regval = data->status[reg] & mask;
+
+ if (!s1 && !s2)
+ rv = !!regval;
+ else {
+ int v1, v2;
+
+ v1 = pmbus_reg2data(data, &data->sensors[s1]);
+ v2 = pmbus_reg2data(data, &data->sensors[s2]);
+ rv = !!(regval && v1 >= v2);
+ }
+ return rv;
+}
+
+#define PMBUS_SHOW_INT(what) \
+ static ssize_t pmbus_show_##what(struct device *dev, \
+ struct device_attribute *da, \
+ char *buf) \
+{ \
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); \
+ struct pmbus_data *data = pmbus_update_device(dev); \
+ long val; \
+ if (IS_ERR(data)) \
+ return PTR_ERR(data); \
+ val = pmbus_get_##what(data, attr->index); \
+ return snprintf(buf, PAGE_SIZE, "%ld\n", val); \
+}
+
+PMBUS_SHOW_INT(sensor);
+PMBUS_SHOW_INT(boolean);
+
+static ssize_t pmbus_set_sensor(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ struct i2c_client *client = to_i2c_client(dev);
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ struct pmbus_sensor *sensor = &data->sensors[attr->index];
+ ssize_t rv = count;
+ long val = 0;
+ int ret;
+ u16 regval;
+
+ if (strict_strtol(buf, 10, &val) < 0)
+ return -EINVAL;
+
+ mutex_lock(&data->update_lock);
+ regval = pmbus_data2reg(data, sensor->class, val);
+ ret = pmbus_write_word_data(client, sensor->page, sensor->reg, regval);
+ if (ret < 0)
+ rv = ret;
+ else
+ data->sensors[attr->index].data = regval;
+ mutex_unlock(&data->update_lock);
+ return rv;
+}
+
+static ssize_t pmbus_show_label(struct device *dev,
+ struct device_attribute *da,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+
+ return snprintf(buf, PAGE_SIZE, "%s\n",
+ data->labels[attr->index].label);
+}
+
+#define PMBUS_ADD_ATTR(data, _name, _idx, _mode, _type, _show, _set) \
+do { \
+ struct sensor_device_attribute *a \
+ = &data->_type##s[data->num_##_type##s].attribute; \
+ BUG_ON(data->num_attributes >= data->max_attributes); \
+ a->dev_attr.attr.name = _name; \
+ a->dev_attr.attr.mode = _mode; \
+ a->dev_attr.show = _show; \
+ a->dev_attr.store = _set; \
+ a->index = _idx; \
+ data->attributes[data->num_attributes] = &a->dev_attr.attr; \
+ data->num_attributes++; \
+} while (0)
+
+#define PMBUS_ADD_GET_ATTR(data, _name, _type, _idx) \
+ PMBUS_ADD_ATTR(data, _name, _idx, S_IRUGO, _type, \
+ pmbus_show_##_type, NULL)
+
+#define PMBUS_ADD_SET_ATTR(data, _name, _type, _idx) \
+ PMBUS_ADD_ATTR(data, _name, _idx, S_IWUSR | S_IRUGO, _type, \
+ pmbus_show_##_type, pmbus_set_##_type)
+
+static void pmbus_add_boolean(struct pmbus_data *data,
+ const char *name, const char *type, int seq,
+ int val)
+{
+ struct pmbus_boolean *boolean;
+
+ BUG_ON(data->num_booleans >= data->max_booleans);
+
+ boolean = &data->booleans[data->num_booleans];
+
+ snprintf(boolean->name, sizeof(boolean->name), "%s%d_%s",
+ name, seq, type);
+ PMBUS_ADD_GET_ATTR(data, boolean->name, boolean, val);
+ data->num_booleans++;
+}
+
+static void pmbus_add_boolean_reg(struct pmbus_data *data,
+ const char *name, const char *type,
+ int seq, int reg, int bit)
+{
+ pmbus_add_boolean(data, name, type, seq,
+ (reg << 8) | bit);
+}
+
+static void pmbus_add_boolean_cmp(struct pmbus_data *data,
+ const char *name, const char *type,
+ int seq, int i1, int i2, int reg,
+ int mask)
+{
+ pmbus_add_boolean(data, name, type, seq,
+ (i1 << 24) | (i2 << 16) | (reg << 8) | mask);
+}
+
+static void pmbus_add_sensor(struct pmbus_data *data,
+ const char *name, const char *type, int seq,
+ int page, int reg, enum pmbus_sensor_classes class,
+ bool update)
+{
+ struct pmbus_sensor *sensor;
+
+ BUG_ON(data->num_sensors >= data->max_sensors);
+
+ sensor = &data->sensors[data->num_sensors];
+ snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s",
+ name, seq, type);
+ sensor->page = page;
+ sensor->reg = reg;
+ sensor->class = class;
+ sensor->update = update;
+ if (update)
+ PMBUS_ADD_GET_ATTR(data, sensor->name, sensor,
+ data->num_sensors);
+ else
+ PMBUS_ADD_SET_ATTR(data, sensor->name, sensor,
+ data->num_sensors);
+ data->num_sensors++;
+}
+
+static void pmbus_add_label(struct pmbus_data *data,
+ const char *name, int seq,
+ const char *lstring, int index)
+{
+ struct pmbus_label *label;
+
+ BUG_ON(data->num_labels >= data->max_labels);
+
+ label = &data->labels[data->num_labels];
+ snprintf(label->name, sizeof(label->name), "%s%d_label",
+ name, seq);
+ if (!index)
+ strncpy(label->label, lstring, sizeof(label->label) - 1);
+ else
+ snprintf(label->label, sizeof(label->label), "%s%d", lstring,
+ index);
+
+ PMBUS_ADD_GET_ATTR(data, label->name, label, data->num_labels);
+ data->num_labels++;
+}
+
+/*
+ * Identify important chip parameters.
+ */
+static void pmbus_identify_generic(struct i2c_client *client,
+ struct pmbus_data *data)
+{
+ /*
+ * Check if the PAGE command is supported. If it is,
+ * keep setting the page number until it fails or until the
+ * maximum number of pages has been reached. Assume that
+ * this is the number of pages supported by the chip.
+ */
+ if (pmbus_check_byte_register(client, 0, PMBUS_PAGE)) {
+ int page;
+
+ for (page = 1; page < PMBUS_PAGES; page++) {
+ if (pmbus_set_page(client, page) < 0)
+ break;
+ }
+ pmbus_set_page(client, 0);
+ data->pages = page;
+ } else
+ data->pages = 1;
+
+ /*
+ * We should check if the COEFFICIENTS register is supported.
+ * If it is, select direct mode and read the coefficients from the
+ * chip, one set per group of sensor registers.
+ *
+ * To do this, we will need access to a chip which actually supports the
+ * COEFFICIENTS command, since the command is too complex to implement
+ * without testing it.
+ */
+}
+
+static int pmbus_temp_sensors[] = {
+ PMBUS_READ_TEMPERATURE_1,
+ PMBUS_READ_TEMPERATURE_2,
+ PMBUS_READ_TEMPERATURE_3
+};
+
+/*
+ * Determine maximum number of sensors, booleans, and labels.
+ * To keep things simple, only make a rough high estimate.
+ */
+static void pmbus_find_max_attr(struct i2c_client *client,
+ struct pmbus_data *data)
+{
+ int i, max_sensors, max_booleans, max_labels;
+
+ max_sensors = PMBUS_MAX_INPUT_SENSORS;
+ max_booleans = PMBUS_MAX_INPUT_BOOLEANS;
+ max_labels = PMBUS_MAX_INPUT_LABELS;
+
+ if (pmbus_check_word_register(client, 0, PMBUS_READ_VOUT)) {
+ max_sensors += data->pages * PMBUS_VOUT_SENSORS_PER_PAGE;
+ max_booleans += data->pages * PMBUS_VOUT_BOOLEANS_PER_PAGE;
+ max_labels += data->pages;
+ }
+ if (pmbus_check_word_register(client, 0, PMBUS_READ_IOUT)) {
+ max_sensors += data->pages * PMBUS_IOUT_SENSORS_PER_PAGE;
+ max_booleans += data->pages * PMBUS_IOUT_BOOLEANS_PER_PAGE;
+ max_labels += data->pages;
+ }
+ if (pmbus_check_word_register(client, 0, PMBUS_READ_POUT)) {
+ max_sensors += data->pages * PMBUS_POUT_SENSORS_PER_PAGE;
+ max_booleans += data->pages * PMBUS_POUT_BOOLEANS_PER_PAGE;
+ max_labels += data->pages;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(pmbus_temp_sensors); i++) {
+ if (!pmbus_check_word_register(client, 0,
+ pmbus_temp_sensors[i]))
+ break;
+ max_sensors += PMBUS_MAX_SENSORS_PER_TEMP;
+ max_booleans += PMBUS_MAX_BOOLEANS_PER_TEMP;
+ }
+ data->max_sensors = max_sensors;
+ data->max_booleans = max_booleans;
+ data->max_labels = max_labels;
+ data->max_attributes = max_sensors + max_booleans + max_labels;
+}
+
+/*
+ * Search for attributes. Allocate sensors, booleans, and labels as needed.
+ */
+static void pmbus_find_attributes(struct i2c_client *client,
+ struct pmbus_data *data)
+{
+ int i, i0, i1, in_index;
+
+ /*
+ * Input voltage sensors
+ */
+ in_index = 1;
+ if (pmbus_check_word_register(client, 0, PMBUS_READ_VIN)) {
+ i0 = data->num_sensors;
+ pmbus_add_label(data, "in", in_index, "vin", 0);
+ pmbus_add_sensor(data, "in", "input", in_index,
+ 0, PMBUS_READ_VIN, PSC_VOLTAGE, true);
+ if (pmbus_check_word_register(client, 0,
+ PMBUS_VIN_UV_WARN_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "in", "min", in_index,
+ 0, PMBUS_VIN_UV_WARN_LIMIT,
+ PSC_VOLTAGE, false);
+ if (data->status_bits & PB_HAVE_STATUS_INPUT)
+ pmbus_add_boolean_reg(data, "in", "min_alarm",
+ in_index,
+ PB_STATUS_INPUT_BASE,
+ PB_VOLTAGE_UV_WARNING);
+ }
+ if (pmbus_check_word_register(client, 0,
+ PMBUS_VIN_UV_FAULT_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "in", "lcrit", in_index,
+ 0, PMBUS_VIN_UV_FAULT_LIMIT,
+ PSC_VOLTAGE, false);
+ if (data->status_bits & PB_HAVE_STATUS_INPUT)
+ pmbus_add_boolean_reg(data, "in", "lcrit_alarm",
+ in_index,
+ PB_STATUS_INPUT_BASE,
+ PB_VOLTAGE_UV_FAULT);
+ }
+ if (pmbus_check_word_register(client, 0,
+ PMBUS_VIN_OV_WARN_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "in", "max", in_index,
+ 0, PMBUS_VIN_OV_WARN_LIMIT,
+ PSC_VOLTAGE, false);
+ if (data->status_bits & PB_HAVE_STATUS_INPUT)
+ pmbus_add_boolean_reg(data, "in", "max_alarm",
+ in_index,
+ PB_STATUS_INPUT_BASE,
+ PB_VOLTAGE_OV_WARNING);
+ }
+ if (pmbus_check_word_register(client, 0,
+ PMBUS_VIN_OV_FAULT_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "in", "crit", in_index,
+ 0, PMBUS_VIN_OV_FAULT_LIMIT,
+ PSC_VOLTAGE, false);
+ if (data->status_bits & PB_HAVE_STATUS_INPUT)
+ pmbus_add_boolean_reg(data, "in", "crit_alarm",
+ in_index,
+ PB_STATUS_INPUT_BASE,
+ PB_VOLTAGE_OV_FAULT);
+ }
+ in_index++;
+ }
+ if (pmbus_check_word_register(client, 0, PMBUS_READ_VCAP)) {
+ pmbus_add_label(data, "in", in_index, "vcap", 0);
+ pmbus_add_sensor(data, "in", "input", in_index, 0,
+ PMBUS_READ_VCAP, PSC_VOLTAGE, true);
+ in_index++;
+ }
+
+ /*
+ * Output voltage sensors
+ */
+ for (i = 0; i < data->pages; i++) {
+ if (!pmbus_check_word_register(client, i, PMBUS_READ_VOUT))
+ break;
+
+ i0 = data->num_sensors;
+ pmbus_add_label(data, "in", in_index, "vout", i+1);
+ pmbus_add_sensor(data, "in", "input", in_index, i,
+ PMBUS_READ_VOUT, PSC_VOLTAGE, true);
+ if (pmbus_check_word_register(client, i,
+ PMBUS_VOUT_UV_WARN_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "in", "min", in_index, i,
+ PMBUS_VOUT_UV_WARN_LIMIT, PSC_VOLTAGE,
+ false);
+ if (data->status_bits & PB_HAVE_STATUS_VOUT)
+ pmbus_add_boolean_reg(data, "in", "min_alarm",
+ in_index,
+ PB_STATUS_VOUT_BASE + i,
+ PB_VOLTAGE_UV_WARNING);
+ }
+ if (pmbus_check_word_register(client, i,
+ PMBUS_VOUT_UV_FAULT_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "in", "lcrit", in_index, i,
+ PMBUS_VOUT_UV_FAULT_LIMIT, PSC_VOLTAGE,
+ false);
+ if (data->status_bits & PB_HAVE_STATUS_VOUT)
+ pmbus_add_boolean_reg(data, "in", "lcrit_alarm",
+ in_index,
+ PB_STATUS_VOUT_BASE + i,
+ PB_VOLTAGE_UV_FAULT);
+ }
+ if (pmbus_check_word_register(client, i,
+ PMBUS_VOUT_OV_WARN_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "in", "max", in_index, i,
+ PMBUS_VOUT_OV_WARN_LIMIT, PSC_VOLTAGE,
+ false);
+ if (data->status_bits & PB_HAVE_STATUS_VOUT) {
+ pmbus_add_boolean_reg(data, "in", "max_alarm",
+ in_index,
+ PB_STATUS_VOUT_BASE + i,
+ PB_VOLTAGE_OV_WARNING);
+ }
+ }
+ if (pmbus_check_word_register(client, i,
+ PMBUS_VOUT_OV_FAULT_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "in", "crit", in_index, i,
+ PMBUS_VOUT_OV_FAULT_LIMIT, PSC_VOLTAGE,
+ false);
+ if (data->status_bits & PB_HAVE_STATUS_VOUT)
+ pmbus_add_boolean_reg(data, "in", "crit_alarm",
+ in_index,
+ PB_STATUS_VOUT_BASE + i,
+ PB_VOLTAGE_OV_FAULT);
+ }
+ in_index++;
+ }
+
+ /*
+ * Current sensors
+ */
+
+ /*
+ * Input current sensors
+ */
+ in_index = 1;
+ if (pmbus_check_word_register(client, 0, PMBUS_READ_IIN)) {
+ i0 = data->num_sensors;
+ pmbus_add_label(data, "curr", in_index, "iin", 0);
+ pmbus_add_sensor(data, "curr", "input", in_index,
+ 0, PMBUS_READ_IIN, PSC_CURRENT, true);
+ if (pmbus_check_word_register(client, 0,
+ PMBUS_IIN_OC_WARN_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "curr", "max", in_index,
+ 0, PMBUS_IIN_OC_WARN_LIMIT,
+ PSC_CURRENT, false);
+ if (data->status_bits & PB_HAVE_STATUS_INPUT) {
+ pmbus_add_boolean_reg(data, "curr", "alarm",
+ in_index,
+ PB_STATUS_INPUT_BASE,
+ PB_IIN_OC_WARNING);
+ }
+ }
+ if (pmbus_check_word_register(client, 0,
+ PMBUS_IIN_OC_FAULT_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "curr", "crit", in_index,
+ 0, PMBUS_IIN_OC_FAULT_LIMIT,
+ PSC_CURRENT, false);
+ if (data->status_bits & PB_HAVE_STATUS_INPUT) {
+ pmbus_add_boolean_reg(data, "curr",
+ "crit_alarm",
+ in_index,
+ PB_STATUS_INPUT_BASE,
+ PB_IIN_OC_FAULT);
+ }
+ }
+ in_index++;
+ }
+
+ /*
+ * Output Current sensors
+ */
+ for (i = 0; i < data->pages; i++) {
+ if (!pmbus_check_word_register(client, i, PMBUS_READ_IOUT))
+ break;
+
+ i0 = data->num_sensors;
+ pmbus_add_label(data, "curr", in_index, "iout", i+1);
+ pmbus_add_sensor(data, "curr", "input", in_index, i,
+ PMBUS_READ_IOUT, PSC_CURRENT, true);
+ if (pmbus_check_word_register(client, i,
+ PMBUS_IOUT_OC_WARN_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "curr", "max", in_index, i,
+ PMBUS_IOUT_OC_WARN_LIMIT, PSC_CURRENT,
+ false);
+ if (data->status_bits & PB_HAVE_STATUS_IOUT)
+ pmbus_add_boolean_reg(data, "curr", "alarm",
+ in_index,
+ PB_STATUS_IOUT_BASE + i,
+ PB_IOUT_OC_WARNING);
+ }
+ if (pmbus_check_word_register(client, i,
+ PMBUS_IOUT_UC_FAULT_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "curr", "lcrit", in_index, i,
+ PMBUS_IOUT_UC_FAULT_LIMIT, PSC_CURRENT,
+ false);
+ if (data->status_bits & PB_HAVE_STATUS_IOUT)
+ pmbus_add_boolean_reg(data, "curr",
+ "lcrit_alarm",
+ in_index,
+ PB_STATUS_IOUT_BASE + i,
+ PB_IOUT_UC_FAULT);
+ }
+ if (pmbus_check_word_register(client, i,
+ PMBUS_IOUT_OC_FAULT_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "curr", "crit", in_index, i,
+ PMBUS_IOUT_OC_FAULT_LIMIT, PSC_CURRENT,
+ false);
+ if (data->status_bits & PB_HAVE_STATUS_IOUT)
+ pmbus_add_boolean_reg(data, "curr",
+ "crit_alarm",
+ in_index,
+ PB_STATUS_IOUT_BASE + i,
+ PB_IOUT_OC_FAULT);
+ }
+ in_index++;
+ }
+
+ /*
+ * Power sensors
+ */
+ /*
+ * Input Power sensors
+ */
+ in_index = 1;
+ if (pmbus_check_word_register(client, 0, PMBUS_READ_PIN)) {
+ i0 = data->num_sensors;
+ pmbus_add_label(data, "power", in_index, "pin", 0);
+ pmbus_add_sensor(data, "power", "input", in_index,
+ 0, PMBUS_READ_PIN, PSC_POWER, true);
+ if (pmbus_check_word_register(client, 0,
+ PMBUS_PIN_OP_WARN_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "power", "max", in_index,
+ 0, PMBUS_PIN_OP_WARN_LIMIT, PSC_POWER,
+ false);
+ if (data->status_bits & PB_HAVE_STATUS_INPUT)
+ pmbus_add_boolean_reg(data, "power", "alarm",
+ in_index,
+ PB_STATUS_INPUT_BASE,
+ PB_PIN_OP_WARNING);
+ }
+ in_index++;
+ }
+
+ /*
+ * Output Power sensors
+ */
+ for (i = 0; i < data->pages; i++) {
+ bool need_alarm = false;
+
+ if (!pmbus_check_word_register(client, i, PMBUS_READ_POUT))
+ break;
+
+ i0 = data->num_sensors;
+ pmbus_add_label(data, "power", in_index, "pout", i+1);
+ pmbus_add_sensor(data, "power", "input", in_index, i,
+ PMBUS_READ_POUT, PSC_POWER, true);
+ /*
+ * Per hwmon sysfs API, power_cap is to be used to limit output
+ * power.
+ * We have two registers related to maximum output power,
+ * PMBUS_POUT_MAX and PMBUS_POUT_OP_WARN_LIMIT.
+ * PMBUS_POUT_MAX matches the powerX_cap attribute definition.
+ * There is no attribute in the API to match
+ * PMBUS_POUT_OP_WARN_LIMIT. We use powerX_max for now.
+ */
+ if (pmbus_check_word_register(client, i, PMBUS_POUT_MAX)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "power", "cap", in_index, i,
+ PMBUS_POUT_MAX, PSC_POWER, false);
+ need_alarm = true;
+ }
+ if (pmbus_check_word_register(client, i,
+ PMBUS_POUT_OP_WARN_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "power", "max", in_index, i,
+ PMBUS_POUT_OP_WARN_LIMIT, PSC_POWER,
+ false);
+ need_alarm = true;
+ }
+ if (need_alarm && (data->status_bits & PB_HAVE_STATUS_IOUT))
+ pmbus_add_boolean_reg(data, "power", "alarm",
+ in_index,
+ PB_STATUS_IOUT_BASE,
+ PB_POUT_OP_WARNING
+ | PB_POWER_LIMITING);
+
+ if (pmbus_check_word_register(client, i,
+ PMBUS_POUT_OP_FAULT_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "power", "crit", in_index, i,
+ PMBUS_POUT_OP_FAULT_LIMIT, PSC_POWER,
+ false);
+ if (data->status_bits & PB_HAVE_STATUS_IOUT)
+ pmbus_add_boolean_reg(data, "power",
+ "crit_alarm",
+ in_index,
+ PB_STATUS_IOUT_BASE,
+ PB_POUT_OP_FAULT);
+ }
+ in_index++;
+ }
+
+ /*
+ * Temperature sensors
+ */
+ in_index = 1;
+ for (i = 0; i < ARRAY_SIZE(pmbus_temp_sensors); i++) {
+ if (!pmbus_check_word_register(client, 0,
+ pmbus_temp_sensors[i]))
+ break;
+
+ i0 = data->num_sensors;
+ pmbus_add_sensor(data, "temp", "input", in_index, 0,
+ pmbus_temp_sensors[i], PSC_TEMPERATURE, true);
+
+ /*
+ * PMBus provides only one status register for all temperature
+ * sensors. Thus, we can not use the status register to
+ * determine which of the sensors actually caused an alarm.
+ * Always compare current temperature against the limit
+ * registers to determine alarm conditions for a specific
+ * sensor.
+ */
+ if (pmbus_check_word_register(client, 0, PMBUS_UT_WARN_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "temp", "min", in_index, 0,
+ PMBUS_UT_WARN_LIMIT, PSC_TEMPERATURE,
+ false);
+ if (data->status_bits & PB_HAVE_STATUS_TEMP)
+ pmbus_add_boolean_cmp(data, "temp", "min_alarm",
+ in_index, i1, i0,
+ PB_STATUS_TEMP_BASE,
+ PB_TEMP_UT_WARNING);
+ }
+ if (pmbus_check_word_register(client, 0,
+ PMBUS_UT_FAULT_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "temp", "lcrit", in_index, 0,
+ PMBUS_UT_FAULT_LIMIT, PSC_TEMPERATURE,
+ false);
+ if (data->status_bits & PB_HAVE_STATUS_TEMP)
+ pmbus_add_boolean_cmp(data, "temp", "lcrit",
+ in_index, i1, i0,
+ PB_STATUS_TEMP_BASE,
+ PB_TEMP_UT_FAULT);
+ }
+ if (pmbus_check_word_register(client, 0, PMBUS_OT_WARN_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "temp", "max", in_index, 0,
+ PMBUS_OT_WARN_LIMIT, PSC_TEMPERATURE,
+ false);
+ if (data->status_bits & PB_HAVE_STATUS_TEMP)
+ pmbus_add_boolean_cmp(data, "temp", "max_alarm",
+ in_index, i0, i1,
+ PB_STATUS_TEMP_BASE,
+ PB_TEMP_OT_WARNING);
+ }
+ if (pmbus_check_word_register(client, 0,
+ PMBUS_OT_FAULT_LIMIT)) {
+ i1 = data->num_sensors;
+ pmbus_add_sensor(data, "temp", "crit", in_index, 0,
+ PMBUS_OT_FAULT_LIMIT, PSC_TEMPERATURE,
+ false);
+ if (data->status_bits & PB_HAVE_STATUS_TEMP)
+ pmbus_add_boolean_cmp(data, "temp",
+ "crit_alarm",
+ in_index, i0, i1,
+ PB_STATUS_TEMP_BASE,
+ PB_TEMP_OT_FAULT);
+ }
+ in_index++;
+ }
+
+ pmbus_clear_faults(client);
+}
+
+static int pmbus_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct pmbus_data *data;
+ int i, ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA
+ | I2C_FUNC_SMBUS_WORD_DATA))
+ return -ENODEV;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ i2c_set_clientdata(client, data);
+ mutex_init(&data->update_lock);
+
+ data->type = id->driver_data;
+ data->direct = pmbus_config[data->type].direct;
+ data->pages = pmbus_config[data->type].pages;
+
+ /*
+ * Bail out if status register or PMBus revision register
+ * does not exist.
+ */
+ if (!pmbus_check_byte_register(client, 0, PMBUS_STATUS_BYTE)
+ || !pmbus_check_byte_register(client, 0, PMBUS_REVISION)) {
+ ret = -ENODEV;
+ goto out_data;
+ }
+
+ if (data->type == pmbus) {
+ pmbus_identify_generic(client, data);
+ } else {
+ /*
+ * Bail out if more than one page was configured, but we can not
+ * select the highest page. This is an indication that the wrong
+ * chip type was selected. Better bail out now than keep
+ * returning errors later on.
+ */
+ if (data->pages > 1 && pmbus_set_page(client,
+ data->pages-1) < 0) {
+ dev_err(&client->dev, "Failed to select page %d",
+ data->pages-1);
+ ret = -EINVAL;
+ goto out_data;
+ }
+ if (data->direct)
+ for (i = 0; i < PSC_NUM_CLASSES; i++) {
+ data->m[i] = pmbus_config[data->type].m[i];
+ data->b[i] = pmbus_config[data->type].b[i];
+ data->R[i] = pmbus_config[data->type].R[i];
+ }
+ }
+
+ /*
+ * Identify supported status registers
+ */
+ if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_VOUT))
+ data->status_bits |= PB_HAVE_STATUS_VOUT;
+ if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_IOUT))
+ data->status_bits |= PB_HAVE_STATUS_IOUT;
+ if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_INPUT))
+ data->status_bits |= PB_HAVE_STATUS_INPUT;
+ if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_TEMPERATURE))
+ data->status_bits |= PB_HAVE_STATUS_TEMP;
+
+ /* Determine maximum number of sensors, booleans, and labels */
+ pmbus_find_max_attr(client, data);
+
+ ret = -ENOMEM;
+ data->sensors = kzalloc(sizeof(struct pmbus_sensor) * data->max_sensors,
+ GFP_KERNEL);
+ if (!data->sensors)
+ goto out_data;
+
+ data->booleans = kzalloc(sizeof(struct pmbus_boolean)
+ * data->max_booleans, GFP_KERNEL);
+ if (!data->booleans)
+ goto out_sensors;
+
+ data->labels = kzalloc(sizeof(struct pmbus_label) * data->max_labels,
+ GFP_KERNEL);
+ if (!data->labels)
+ goto out_booleans;
+
+ data->attributes = kzalloc(sizeof(struct attribute *)
+ * data->max_attributes, GFP_KERNEL);
+ if (!data->attributes)
+ goto out_labels;
+
+ pmbus_find_attributes(client, data);
+
+ /*
+ * If there are no attributes, something is wrong.
+ * Bail out instead of trying to register nothing.
+ */
+ if (!data->num_attributes) {
+ ret = -ENODEV;
+ goto out_attributes;
+ }
+
+ /* Register sysfs hooks */
+ data->group.attrs = data->attributes;
+ ret = sysfs_create_group(&client->dev.kobj, &data->group);
+ if (ret)
+ goto out_attributes;
+ data->hwmon_dev = hwmon_device_register(&client->dev);
+ if (IS_ERR(data->hwmon_dev)) {
+ ret = PTR_ERR(data->hwmon_dev);
+ goto out_hwmon_device_register;
+ }
+ return 0;
+out_hwmon_device_register:
+ sysfs_remove_group(&client->dev.kobj, &data->group);
+out_attributes:
+ kfree(data->attributes);
+out_labels:
+ kfree(data->labels);
+out_booleans:
+ kfree(data->booleans);
+out_sensors:
+ kfree(data->sensors);
+out_data:
+ kfree(data);
+out:
+ return ret;
+}
+
+static int pmbus_remove(struct i2c_client *client)
+{
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ hwmon_device_unregister(data->hwmon_dev);
+ sysfs_remove_group(&client->dev.kobj, &data->group);
+ kfree(data->attributes);
+ kfree(data->labels);
+ kfree(data->booleans);
+ kfree(data->sensors);
+ kfree(data);
+ return 0;
+}
+
+static const struct i2c_device_id pmbus_id[] = {
+ {"bmr45x", pmbus},
+ {"ltc2978", ltc2978},
+ {"max16064", max16064},
+ {"max8688", max8688},
+ {"pmbus", pmbus},
+ {"ucd921x", pmbus},
+ {"ucd9220", ucd9220},
+ {"ucd9240", ucd9240},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, pmbus_id);
+
+/* This is the driver that will be inserted */
+static struct i2c_driver pmbus_driver = {
+ .driver = {
+ .name = "pmbus",
+ },
+ .probe = pmbus_probe,
+ .remove = pmbus_remove,
+ .id_table = pmbus_id,
+};
+
+static int __init pmbus_init(void)
+{
+ return i2c_add_driver(&pmbus_driver);
+}
+
+static void __exit pmbus_exit(void)
+{
+ i2c_del_driver(&pmbus_driver);
+}
+
+MODULE_AUTHOR("Guenter Roeck");
+MODULE_DESCRIPTION("PMBus driver");
+MODULE_LICENSE("GPL");
+module_init(pmbus_init);
+module_exit(pmbus_exit);
diff --git a/drivers/hwmon/pmbus.h b/drivers/hwmon/pmbus.h
new file mode 100644
index 0000000..2a8a027
--- /dev/null
+++ b/drivers/hwmon/pmbus.h
@@ -0,0 +1,209 @@
+/*
+ * pmbus.h - Common defines and structures for PMBus devices
+ *
+ * Copyright (c) 2010 Ericsson AB.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef PMBUS_H
+#define PMBUS_H
+
+/*
+ * Registers
+ */
+#define PMBUS_PAGE 0x00
+#define PMBUS_OPERATION 0x01
+#define PMBUS_ON_OFF_CONFIG 0x02
+#define PMBUS_CLEAR_FAULTS 0x03
+#define PMBUS_PHASE 0x04
+
+#define PMBUS_CAPABILITY 0x19
+#define PMBUS_QUERY 0x1A
+
+#define PMBUS_VOUT_MODE 0x20
+#define PMBUS_VOUT_COMMAND 0x21
+#define PMBUS_VOUT_TRIM 0x22
+#define PMBUS_VOUT_CAL_OFFSET 0x23
+#define PMBUS_VOUT_MAX 0x24
+#define PMBUS_VOUT_MARGIN_HIGH 0x25
+#define PMBUS_VOUT_MARGIN_LOW 0x26
+#define PMBUS_VOUT_TRANSITION_RATE 0x27
+#define PMBUS_VOUT_DROOP 0x28
+#define PMBUS_VOUT_SCALE_LOOP 0x29
+#define PMBUS_VOUT_SCALE_MONITOR 0x2A
+
+#define PMBUS_COEFFICIENTS 0x30
+#define PMBUS_POUT_MAX 0x31
+
+#define PMBUS_VOUT_OV_FAULT_LIMIT 0x40
+#define PMBUS_VOUT_OV_FAULT_RESPONSE 0x41
+#define PMBUS_VOUT_OV_WARN_LIMIT 0x42
+#define PMBUS_VOUT_UV_WARN_LIMIT 0x43
+#define PMBUS_VOUT_UV_FAULT_LIMIT 0x44
+#define PMBUS_VOUT_UV_FAULT_RESPONSE 0x45
+#define PMBUS_IOUT_OC_FAULT_LIMIT 0x46
+#define PMBUS_IOUT_OC_FAULT_RESPONSE 0x47
+#define PMBUS_IOUT_OC_LV_FAULT_LIMIT 0x48
+#define PMBUS_IOUT_OC_LV_FAULT_RESPONSE 0x49
+#define PMBUS_IOUT_OC_WARN_LIMIT 0x4A
+#define PMBUS_IOUT_UC_FAULT_LIMIT 0x4B
+#define PMBUS_IOUT_UC_FAULT_RESPONSE 0x4C
+
+#define PMBUS_OT_FAULT_LIMIT 0x4F
+#define PMBUS_OT_FAULT_RESPONSE 0x50
+#define PMBUS_OT_WARN_LIMIT 0x51
+#define PMBUS_UT_WARN_LIMIT 0x52
+#define PMBUS_UT_FAULT_LIMIT 0x53
+#define PMBUS_UT_FAULT_RESPONSE 0x54
+#define PMBUS_VIN_OV_FAULT_LIMIT 0x55
+#define PMBUS_VIN_OV_FAULT_RESPONSE 0x56
+#define PMBUS_VIN_OV_WARN_LIMIT 0x57
+#define PMBUS_VIN_UV_WARN_LIMIT 0x58
+#define PMBUS_VIN_UV_FAULT_LIMIT 0x59
+
+#define PMBUS_IIN_OC_FAULT_LIMIT 0x5B
+#define PMBUS_IIN_OC_WARN_LIMIT 0x5D
+
+#define PMBUS_POUT_OP_FAULT_LIMIT 0x68
+#define PMBUS_POUT_OP_WARN_LIMIT 0x6A
+#define PMBUS_PIN_OP_WARN_LIMIT 0x6B
+
+#define PMBUS_STATUS_BYTE 0x78
+#define PMBUS_STATUS_WORD 0x79
+#define PMBUS_STATUS_VOUT 0x7A
+#define PMBUS_STATUS_IOUT 0x7B
+#define PMBUS_STATUS_INPUT 0x7C
+#define PMBUS_STATUS_TEMPERATURE 0x7D
+#define PMBUS_STATUS_CML 0x7E
+#define PMBUS_STATUS_OTHER 0x7F
+#define PMBUS_STATUS_MFR_SPECIFIC 0x80
+#define PMBUS_STATUS_FANS_1_2 0x81
+#define PMBUS_STATUS_FANS_3_4 0x82
+
+#define PMBUS_READ_VIN 0x88
+#define PMBUS_READ_IIN 0x89
+#define PMBUS_READ_VCAP 0x8A
+#define PMBUS_READ_VOUT 0x8B
+#define PMBUS_READ_IOUT 0x8C
+#define PMBUS_READ_TEMPERATURE_1 0x8D
+#define PMBUS_READ_TEMPERATURE_2 0x8E
+#define PMBUS_READ_TEMPERATURE_3 0x8F
+#define PMBUS_READ_FAN_SPEED_1 0x90
+#define PMBUS_READ_FAN_SPEED_2 0x91
+#define PMBUS_READ_FAN_SPEED_3 0x92
+#define PMBUS_READ_FAN_SPEED_4 0x93
+#define PMBUS_READ_DUTY_CYCLE 0x94
+#define PMBUS_READ_FREQUENCY 0x95
+#define PMBUS_READ_POUT 0x96
+#define PMBUS_READ_PIN 0x97
+
+#define PMBUS_REVISION 0x98
+#define PMBUS_MFR_ID 0x99
+#define PMBUS_MFR_MODEL 0x9A
+#define PMBUS_MFR_REVISION 0x9B
+#define PMBUS_MFR_LOCATION 0x9C
+#define PMBUS_MFR_DATE 0x9D
+#define PMBUS_MFR_SERIAL 0x9E
+
+#define LTC2978_MFR_SPECIAL_ID 0xE7
+
+/*
+ * CAPABILITY
+ */
+#define PB_CAPABILITY_SMBALERT (1<<4)
+#define PB_CAPABILITY_ERROR_CHECK (1<<7)
+
+/*
+ * VOUT_MODE
+ */
+#define PB_VOUT_MODE_MODE_MASK 0xe0
+#define PB_VOUT_MODE_PARAM_MASK 0x1f
+
+#define PB_VOUT_MODE_LINEAR 0x00
+#define PB_VOUT_MODE_VID 0x20
+#define PB_VOUT_MODE_DIRECT 0x40
+
+/*
+ * STATUS_BYTE, STATUS_WORD (lower)
+ */
+#define PB_STATUS_NONE_ABOVE (1<<0)
+#define PB_STATUS_CML (1<<1)
+#define PB_STATUS_TEMPERATURE (1<<2)
+#define PB_STATUS_VIN_UV (1<<3)
+#define PB_STATUS_IOUT_OC (1<<4)
+#define PB_STATUS_VOUT_OV (1<<5)
+#define PB_STATUS_OFF (1<<6)
+#define PB_STATUS_BUSY (1<<7)
+
+/*
+ * STATUS_WORD (upper)
+ */
+#define PB_STATUS_UNKNOWN (1<<8)
+#define PB_STATUS_OTHER (1<<9)
+#define PB_STATUS_FANS (1<<10)
+#define PB_STATUS_POWER_GOOD_N (1<<11)
+#define PB_STATUS_WORD_MFR (1<<12)
+#define PB_STATUS_INPUT (1<<13)
+#define PB_STATUS_IOUT_POUT (1<<14)
+#define PB_STATUS_VOUT (1<<15)
+
+/*
+ * STATUS_IOUT
+ */
+#define PB_POUT_OP_WARNING (1<<0)
+#define PB_POUT_OP_FAULT (1<<1)
+#define PB_POWER_LIMITING (1<<2)
+#define PB_CURRENT_SHARE_FAULT (1<<3)
+#define PB_IOUT_UC_FAULT (1<<4)
+#define PB_IOUT_OC_WARNING (1<<5)
+#define PB_IOUT_OC_LV_FAULT (1<<6)
+#define PB_IOUT_OC_FAULT (1<<7)
+
+/*
+ * STATUS_VOUT, STATUS_INPUT
+ */
+#define PB_VOLTAGE_UV_FAULT (1<<4)
+#define PB_VOLTAGE_UV_WARNING (1<<5)
+#define PB_VOLTAGE_OV_WARNING (1<<6)
+#define PB_VOLTAGE_OV_FAULT (1<<7)
+
+/*
+ * STATUS_INPUT
+ */
+#define PB_PIN_OP_WARNING (1<<0)
+#define PB_IIN_OC_WARNING (1<<1)
+#define PB_IIN_OC_FAULT (1<<2)
+
+/*
+ * STATUS_TEMPERATURE
+ */
+#define PB_TEMP_UT_FAULT (1<<4)
+#define PB_TEMP_UT_WARNING (1<<5)
+#define PB_TEMP_OT_WARNING (1<<6)
+#define PB_TEMP_OT_FAULT (1<<7)
+
+/*
+ * CML_FAULT_STATUS
+ */
+#define PB_CML_FAULT_OTHER_MEM_LOGIC (1<<0)
+#define PB_CML_FAULT_OTHER_COMM (1<<1)
+#define PB_CML_FAULT_PROCESSOR (1<<3)
+#define PB_CML_FAULT_MEMORY (1<<4)
+#define PB_CML_FAULT_PACKET_ERROR (1<<5)
+#define PB_CML_FAULT_INVALID_DATA (1<<6)
+#define PB_CML_FAULT_INVALID_COMMAND (1<<7)
+
+#endif /* PB_H */
--
1.7.0.87.g0901d

2010-07-05 04:17:05

by Guenter Roeck

[permalink] [raw]
Subject: [PATCH/RFC v2 4/4] hwmon: sysfs API updates

Signed-off-by: Guenter Roeck <[email protected]>
---
Documentation/hwmon/sysfs-interface | 37 +++++++++++++++++++++++++++++-----
1 files changed, 31 insertions(+), 6 deletions(-)

diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface
index d4e2917..2dcec0f 100644
--- a/Documentation/hwmon/sysfs-interface
+++ b/Documentation/hwmon/sysfs-interface
@@ -421,11 +421,12 @@ power[1-*]_accuracy Accuracy of the power meter.
Unit: Percent
RO

-power[1-*]_alarm 1 if the system is drawing more power than the
- cap allows; 0 otherwise. A poll notification is
- sent to this file when the power use exceeds the
- cap. This file only appears if the cap is known
- to be enforced by hardware.
+power[1-*]_alarm 1 if the system is drawing more power than cap
+ or max allows; 0 otherwise. A poll notification
+ is sent to this file when the power use exceeds
+ the cap or max limit. If only cap is supported,
+ this file only appears if the cap is known to be
+ enforced by hardware.
RO

power[1-*]_cap If power use rises above this limit, the
@@ -450,6 +451,18 @@ power[1-*]_cap_min Minimum cap that can be set.
Unit: microWatt
RO

+power[1-*]_max Maximum power.
+ Unit: microWatt
+ RW
+
+power[1-*]_crit Critical maximum power.
+ If power rises to or above this limit, the
+ system will take drastic action to reduce power
+ consumption, such as a system shutdown. At the
+ very least, a power fault will be generated.
+ Unit: microWatt
+ RO
+
**********
* Energy *
**********
@@ -471,8 +484,14 @@ limit-related alarms, not both. The driver should just reflect the hardware
implementation.

in[0-*]_alarm
+in[0-*]_crit_alarm
+curr[1-*]_alarm
+curr[1-*]_crit_alarm
+power[1-*]_alarm
+power[1-*]_crit_alarm
fan[1-*]_alarm
temp[1-*]_alarm
+temp[1-*]_crit_alarm
Channel alarm
0: no alarm
1: alarm
@@ -482,10 +501,17 @@ OR

in[0-*]_min_alarm
in[0-*]_max_alarm
+in[0-*]_lcrit_alarm
+in[0-*]_crit_alarm
+curr[1-*]_lcrit_alarm
+curr[1-*]_crit_alarm
+power[1-*]_min_alarm
+power[1-*]_max_alarm
fan[1-*]_min_alarm
fan[1-*]_max_alarm
temp[1-*]_min_alarm
temp[1-*]_max_alarm
+temp[1-*]_lcrit_alarm
temp[1-*]_crit_alarm
Limit alarm
0: no alarm
@@ -497,7 +523,6 @@ to notify open diodes, unconnected fans etc. where the hardware
supports it. When this boolean has value 1, the measurement for that
channel should not be trusted.

-in[0-*]_fault
fan[1-*]_fault
temp[1-*]_fault
Input fault condition
--
1.7.0.87.g0901d

2010-07-05 04:17:18

by Guenter Roeck

[permalink] [raw]
Subject: [PATCH/RFC v2 2/4] hwmon: i2c PMBus device emulator

Signed-off-by: Guenter Roeck <[email protected]>
---
Documentation/i2c/i2c-pmbus | 41 ++
drivers/i2c/busses/Kconfig | 13 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-pmbus.c | 877 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 932 insertions(+), 0 deletions(-)
create mode 100644 Documentation/i2c/i2c-pmbus
create mode 100644 drivers/i2c/busses/i2c-pmbus.c

diff --git a/Documentation/i2c/i2c-pmbus b/Documentation/i2c/i2c-pmbus
new file mode 100644
index 0000000..4ba01fc
--- /dev/null
+++ b/Documentation/i2c/i2c-pmbus
@@ -0,0 +1,41 @@
+MODULE: i2c-pmbus
+
+DESCRIPTION:
+
+This module is a fake I2C/SMBus driver to emulate various PMBus devices.
+It implements five types of SMBus commands: write quick, (r/w) byte,
+(r/w) byte data, (r/w) word data, and (r/w) I2C block data.
+
+The driver supports various PMBus devices at fixed addresses. The following
+PMBus devices are supported.
+
+Device Address
+BMR453 0x10
+LTC2978 0x20
+MAX16064 0x30
+MAX8688 0x40
+UCD9240 0x50
+
+No hardware is needed nor associated with this module. It will accept write
+quick commands to the specified addresses; it will respond to the other
+commands (also to the specified addresses) by reading from or writing to
+arrays in memory.
+
+Once loaded, the driver randomly changes sensor readings, up to lower and upper
+fault limits. This may cause alarms or faults to be raised. This is expected
+behavior.
+
+The typical use-case is like this:
+ 1. load this module
+ 3. load the target chip driver module
+ 4. observe its behavior using the sensors command
+
+
+CAVEATS:
+
+Support for multiple pages (PMBus PAGE command) is limited. Pages can be
+selected, but there is only one set of data, causing all paged registers to
+return the same values.
+
+The emulator does not support self-modification of sensor readings for devices
+which have to be programmed in direct mode.
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index bceafbf..5ab4abc 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -731,6 +731,19 @@ config I2C_PCA_ISA
delays when I2C/SMBus chip drivers are loaded (e.g. at boot
time). If unsure, say N.

+config I2C_PMBUS
+ tristate "I2C/PMBus Chip Emulator"
+ depends on EXPERIMENTAL
+ default 'n'
+ help
+ This module emulates various PMBus devices. It may be useful to
+ developers of PMBus client drivers.
+
+ If you do build this module, be sure to read the notes and warnings
+ in <file:Documentation/i2c/i2c-pmbus>.
+
+ If you don't know what to do here, definitely say N.
+
config I2C_SIBYTE
tristate "SiByte SMBus interface"
depends on SIBYTE_SB1xxx_SOC
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 936880b..c11884e 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -70,6 +70,7 @@ obj-$(CONFIG_I2C_TINY_USB) += i2c-tiny-usb.o
obj-$(CONFIG_I2C_ACORN) += i2c-acorn.o
obj-$(CONFIG_I2C_ELEKTOR) += i2c-elektor.o
obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o
+obj-$(CONFIG_I2C_PMBUS) += i2c-pmbus.o
obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o
obj-$(CONFIG_I2C_STUB) += i2c-stub.o
obj-$(CONFIG_SCx200_ACB) += scx200_acb.o
diff --git a/drivers/i2c/busses/i2c-pmbus.c b/drivers/i2c/busses/i2c-pmbus.c
new file mode 100644
index 0000000..9716e2f
--- /dev/null
+++ b/drivers/i2c/busses/i2c-pmbus.c
@@ -0,0 +1,877 @@
+/*
+ * i2c-pmbus.c - I2C/SMBus chip emulator
+ *
+ * Copyright (C) 2010 Ericsson AB.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/random.h>
+#include "../../hwmon/pmbus.h"
+
+#define NUM_CHIPS 5
+
+enum chips { bmr453, ltc2978, max16064, max8688, ucd9240 };
+
+/*
+ * Register sizes per PMBus specification.
+ */
+static s8 pmbus_regsize[256] = {
+ 1, 1, 1, 0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 16, -1, -1, -1, -1, -1,
+ 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -1, -1, -1, -1, -1,
+ 16, 2, 2, 2, -1, 2, 2, 2, 2, 2, 1, 2, 2, 1, 2, 2,
+ 2, 1, 2, 2, 2, 1, 2, 1, 2, 1, 2, 2, 1, -1, -1, 2,
+ 1, 2, 2, 2, 1, 2, 1, 2, 2, 2, 1, 2, 1, 2, 2, 2,
+ 2, 2, 2, 1, 2, 2, 2, -1, 2, 1, 2, 2, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, 1, 2, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, -1, -1, -1, -1, -1, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 1, 16, 16, 16, 16, 16, 16, -1,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 14, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+};
+
+/*
+ * PMBus register types. 1=rw, 0=ro or undefined
+ */
+static s8 pmbus_rw[256] = {
+ 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+/*
+ * exp 0xf8 -> 2^-1; mantissa 0x01 = 0.5V
+ * exp 0xf0 -> 2^-2; mantissa 0x01 = 0.25V
+ * exp 0xd0 -> 2^-6; mantissa 0x01 = 0.025V, 0x10 = 0.25V
+ */
+#define V1P5_LINEAR 0xd060
+#define V2P5_LINEAR 0xd0a0
+#define V2P75_LINEAR 0xd0b0
+#define V3P25_LINEAR 0xd0d0
+#define V3P5_LINEAR 0xd0e0
+#define V3P75_LINEAR 0xd0f0
+#define V11P5_LINEAR 0xf817
+#define V11P25_LINEAR 0xf02d
+#define V12_LINEAR 0xf818
+#define V12P5_LINEAR 0xf032
+#define V12P75_LINEAR 0xf033
+
+static u16 pmbus_linear_data[256] = {
+ [PMBUS_PAGE] = 0,
+
+ [PMBUS_CAPABILITY] = PB_CAPABILITY_SMBALERT,
+ [PMBUS_VOUT_MODE] = (PB_VOUT_MODE_LINEAR | 0x13),
+ /* linear, -13 */
+
+ [PMBUS_VIN_OV_FAULT_LIMIT] = V12P75_LINEAR,
+ [PMBUS_VIN_OV_WARN_LIMIT] = V12P5_LINEAR,
+ [PMBUS_VIN_UV_WARN_LIMIT] = V11P5_LINEAR,
+ [PMBUS_VIN_UV_FAULT_LIMIT] = V11P25_LINEAR,
+
+ [PMBUS_VOUT_OV_FAULT_LIMIT] = V3P5_LINEAR,
+ [PMBUS_VOUT_OV_WARN_LIMIT] = V3P25_LINEAR,
+ [PMBUS_VOUT_UV_WARN_LIMIT] = V2P75_LINEAR,
+ [PMBUS_VOUT_UV_FAULT_LIMIT] = V2P5_LINEAR,
+
+ [PMBUS_IOUT_OC_FAULT_LIMIT] = 22,
+ [PMBUS_IOUT_OC_LV_FAULT_LIMIT] = 30,
+ [PMBUS_IOUT_OC_WARN_LIMIT] = 20,
+ [PMBUS_IOUT_UC_FAULT_LIMIT] = 0,
+
+ [PMBUS_IIN_OC_FAULT_LIMIT] = 20,
+ [PMBUS_IIN_OC_WARN_LIMIT] = 19,
+
+ [PMBUS_POUT_OP_FAULT_LIMIT] = 0x1000 | 50, /* 200 */
+ [PMBUS_POUT_OP_WARN_LIMIT] = 0x1000 | 45, /* 180 */
+ [PMBUS_PIN_OP_WARN_LIMIT] = 0x1000 | 60, /* 240 */
+
+ [PMBUS_OT_FAULT_LIMIT] = 100,
+ [PMBUS_OT_WARN_LIMIT] = 90,
+ [PMBUS_UT_WARN_LIMIT] = 10,
+ [PMBUS_UT_FAULT_LIMIT] = 0,
+
+ [PMBUS_READ_VIN] = V12_LINEAR,
+ [PMBUS_READ_IIN] = 2,
+ [PMBUS_READ_VCAP] = V11P5_LINEAR,
+ [PMBUS_READ_VOUT] = 3,
+ [PMBUS_READ_IOUT] = 8,
+ [PMBUS_READ_TEMPERATURE_1] = 44,
+ [PMBUS_READ_TEMPERATURE_2] = 45,
+ [PMBUS_READ_TEMPERATURE_3] = 46,
+ [PMBUS_READ_FAN_SPEED_1] = 99,
+ [PMBUS_READ_FAN_SPEED_2] = 98,
+ [PMBUS_READ_FAN_SPEED_3] = 97,
+ [PMBUS_READ_FAN_SPEED_4] = 96,
+ [PMBUS_READ_DUTY_CYCLE] = 77,
+ [PMBUS_READ_FREQUENCY] = 1234,
+ [PMBUS_READ_POUT] = 100,
+ [PMBUS_READ_PIN] = 170,
+};
+
+/*
+ * Values calculated from max16064 manual
+ */
+#define V3UF_MAXIM 0x1200
+#define V3UW_MAXIM 0x1500
+#define V3_MAXIM 0x176e
+#define V3OW_MAXIM 0x1a00
+#define V3OF_MAXIM 0x2000
+
+#define I10_MAXIM 0x0906
+#define I20_MAXIM 0x120d
+#define I25_MAXIM 0x1691
+
+#define T0_MAXIM 0
+#define T20_MAXIM 0xff68
+#define T40_MAXIM 0xfecf
+#define T80_MAXIM 0xfd9f
+#define T90_MAXIM 0xfd58
+
+static u16 pmbus_maxim_data[256] = {
+ [PMBUS_VOUT_OV_FAULT_LIMIT] = V3OF_MAXIM,
+ [PMBUS_VOUT_OV_WARN_LIMIT] = V3OW_MAXIM,
+ [PMBUS_VOUT_UV_WARN_LIMIT] = V3UW_MAXIM,
+ [PMBUS_VOUT_UV_FAULT_LIMIT] = V3UF_MAXIM,
+
+ [PMBUS_IOUT_OC_FAULT_LIMIT] = I25_MAXIM,
+ [PMBUS_IOUT_OC_WARN_LIMIT] = I20_MAXIM,
+
+ [PMBUS_OT_FAULT_LIMIT] = T90_MAXIM,
+ [PMBUS_OT_WARN_LIMIT] = T80_MAXIM,
+ [PMBUS_UT_WARN_LIMIT] = T20_MAXIM,
+ [PMBUS_UT_FAULT_LIMIT] = T0_MAXIM,
+
+ [PMBUS_READ_VOUT] = V3_MAXIM,
+ [PMBUS_READ_IOUT] = I10_MAXIM,
+ [PMBUS_READ_TEMPERATURE_1] = T40_MAXIM,
+};
+
+struct pmbus_chip {
+ char name[16];
+ u8 addr;
+ bool linear;
+ u8 pages; /* Number of pages (for PAGE register) */
+ u8 pointer;
+ s8 regsize[256];
+ u16 *initdata;
+ u8 data[256][I2C_SMBUS_BLOCK_MAX];
+};
+
+static struct pmbus_chip pmbus_chips[NUM_CHIPS] = {
+ [bmr453] = {
+ .name = "bmr453",
+ .addr = 0x10,
+ .linear = 1,
+ .regsize[PMBUS_PAGE] = -1,
+ .regsize[PMBUS_PHASE] = -1,
+ .regsize[PMBUS_CAPABILITY] = -1,
+ .regsize[PMBUS_QUERY] = -1,
+ .regsize[PMBUS_VOUT_MODE] = -1,
+ .regsize[PMBUS_COEFFICIENTS] = -1,
+ .regsize[PMBUS_POUT_MAX] = -1,
+ .regsize[PMBUS_READ_IIN] = -1,
+ .regsize[PMBUS_READ_VCAP] = -1,
+ .regsize[PMBUS_READ_TEMPERATURE_3] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_1] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_2] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_3] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_4] = -1,
+ .regsize[PMBUS_READ_POUT] = -1,
+ .regsize[PMBUS_READ_PIN] = -1,
+ .regsize[PMBUS_MFR_ID] = 11,
+ .regsize[PMBUS_MFR_MODEL] = 13,
+ .initdata = pmbus_linear_data,
+ .data[PMBUS_MFR_ID] = "Ericsson AB",
+ .data[PMBUS_MFR_MODEL] = "BMR453xxxx001",
+ },
+ [ltc2978] = {
+ .name = "ltc2978",
+ .addr = 0x20,
+ .linear = 1,
+ .pages = 8,
+ .regsize[PMBUS_PHASE] = -1,
+ .regsize[PMBUS_CAPABILITY] = -1,
+ .regsize[PMBUS_QUERY] = -1,
+ .regsize[PMBUS_COEFFICIENTS] = -1,
+ .regsize[PMBUS_POUT_MAX] = -1,
+ .regsize[PMBUS_STATUS_IOUT] = -1,
+ .regsize[PMBUS_STATUS_OTHER] = -1,
+ .regsize[PMBUS_READ_IIN] = -1,
+ .regsize[PMBUS_READ_IOUT] = -1,
+ .regsize[PMBUS_READ_VCAP] = -1,
+ .regsize[PMBUS_READ_TEMPERATURE_2] = -1,
+ .regsize[PMBUS_READ_TEMPERATURE_3] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_1] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_2] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_3] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_4] = -1,
+ .regsize[PMBUS_READ_POUT] = -1,
+ .regsize[PMBUS_READ_PIN] = -1,
+ .regsize[PMBUS_MFR_ID] = -1,
+ .regsize[LTC2978_MFR_SPECIAL_ID] = 2,
+ .initdata = pmbus_linear_data,
+ .data[LTC2978_MFR_SPECIAL_ID] = { 0x01, 0x21 },
+ },
+ [max16064] = {
+ .name = "max16064",
+ .addr = 0x30,
+ .pages = 4,
+ .regsize[PMBUS_PHASE] = -1,
+ .regsize[PMBUS_QUERY] = -1,
+ .regsize[PMBUS_VOUT_MODE] = -1,
+ .regsize[PMBUS_COEFFICIENTS] = -1,
+ .regsize[PMBUS_POUT_MAX] = -1,
+ .regsize[PMBUS_STATUS_IOUT] = -1,
+ .regsize[PMBUS_STATUS_INPUT] = -1,
+ .regsize[PMBUS_STATUS_OTHER] = -1,
+ .regsize[PMBUS_READ_VIN] = -1,
+ .regsize[PMBUS_READ_IIN] = -1,
+ .regsize[PMBUS_READ_IOUT] = -1,
+ .regsize[PMBUS_READ_VCAP] = -1,
+ .regsize[PMBUS_READ_TEMPERATURE_2] = -1,
+ .regsize[PMBUS_READ_TEMPERATURE_3] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_1] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_2] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_3] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_4] = -1,
+ .regsize[PMBUS_READ_POUT] = -1,
+ .regsize[PMBUS_READ_PIN] = -1,
+ .regsize[PMBUS_MFR_ID] = 1,
+ .regsize[PMBUS_MFR_MODEL] = 1,
+ .initdata = pmbus_maxim_data,
+ .data[PMBUS_MFR_ID] = { 0x4d },
+ .data[PMBUS_MFR_MODEL] = { 0x43 },
+ },
+ [max8688] = {
+ .name = "max8688",
+ .addr = 0x40,
+ .regsize[PMBUS_PAGE] = -1,
+ .regsize[PMBUS_PHASE] = -1,
+ .regsize[PMBUS_QUERY] = -1,
+ .regsize[PMBUS_CAPABILITY] = -1,
+ .regsize[PMBUS_VOUT_MODE] = -1,
+ .regsize[PMBUS_COEFFICIENTS] = -1,
+ .regsize[PMBUS_POUT_MAX] = -1,
+ .regsize[PMBUS_STATUS_IOUT] = -1,
+ .regsize[PMBUS_STATUS_VOUT] = -1,
+ .regsize[PMBUS_STATUS_TEMPERATURE] = -1,
+ .regsize[PMBUS_STATUS_CML] = -1,
+ .regsize[PMBUS_STATUS_INPUT] = -1,
+ .regsize[PMBUS_STATUS_OTHER] = -1,
+ .regsize[PMBUS_READ_VIN] = -1,
+ .regsize[PMBUS_READ_IIN] = -1,
+ .regsize[PMBUS_READ_VCAP] = -1,
+ .regsize[PMBUS_READ_TEMPERATURE_2] = -1,
+ .regsize[PMBUS_READ_TEMPERATURE_3] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_1] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_2] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_3] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_4] = -1,
+ .regsize[PMBUS_READ_POUT] = -1,
+ .regsize[PMBUS_READ_PIN] = -1,
+ .regsize[PMBUS_MFR_ID] = 2,
+ .regsize[PMBUS_MFR_MODEL] = 2,
+ .initdata = pmbus_maxim_data,
+ .data[PMBUS_MFR_ID] = { 0x4d, 0x01 },
+ .data[PMBUS_MFR_MODEL] = { 0x41, 0x01 },
+ },
+ [ucd9240] = {
+ .name = "ucd9240",
+ .addr = 0x50,
+ .linear = 1,
+ .pages = 4,
+ .regsize[PMBUS_QUERY] = -1,
+ .regsize[PMBUS_COEFFICIENTS] = -1,
+ .regsize[PMBUS_POUT_MAX] = -1,
+ .regsize[PMBUS_UT_WARN_LIMIT] = -1,
+ .regsize[PMBUS_UT_FAULT_LIMIT] = -1,
+ .regsize[PMBUS_UT_FAULT_RESPONSE] = -1,
+ .regsize[PMBUS_IIN_OC_FAULT_LIMIT] = -1,
+ .regsize[PMBUS_IIN_OC_WARN_LIMIT] = -1,
+ .regsize[PMBUS_READ_VCAP] = -1,
+ .regsize[PMBUS_READ_TEMPERATURE_3] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_2] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_3] = -1,
+ .regsize[PMBUS_READ_FAN_SPEED_4] = -1,
+ .initdata = pmbus_linear_data,
+ },
+};
+
+static void pmbus_set_error(struct pmbus_chip *chip, u8 sreg, u16 s1, u8 s2)
+{
+ if (pmbus_regsize[PMBUS_STATUS_BYTE] > 0
+ && (!chip->regsize[PMBUS_STATUS_BYTE]
+ || chip->regsize[PMBUS_STATUS_BYTE] > 0))
+ chip->data[PMBUS_STATUS_BYTE][0] |= s1 & 0xff;
+ if (pmbus_regsize[PMBUS_STATUS_WORD] > 0
+ && (!chip->regsize[PMBUS_STATUS_WORD]
+ || chip->regsize[PMBUS_STATUS_WORD] > 0)) {
+ chip->data[PMBUS_STATUS_WORD][0] |= s1 & 0xff;
+ chip->data[PMBUS_STATUS_WORD][1] |= (s1 >> 8) & 0xff;
+ }
+ if (pmbus_regsize[sreg] > 0
+ && (!chip->regsize[sreg] || chip->regsize[sreg] > 0))
+ chip->data[sreg][0] |= s2;
+}
+
+/* Return negative errno on error. */
+static s32 pmbus_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags,
+ char read_write, u8 command, int size,
+ union i2c_smbus_data *data)
+{
+ s32 ret;
+ int i, len, regsize;
+ struct pmbus_chip *chip = NULL;
+
+ /* Search for the right chip */
+ for (i = 0; i < NUM_CHIPS && pmbus_chips[i].addr; i++) {
+ if (addr == pmbus_chips[i].addr) {
+ chip = pmbus_chips + i;
+ break;
+ }
+ }
+ if (!chip) {
+ dev_dbg(&adap->dev, "No chip at address 0x%02x\n", addr);
+ return -ENODEV;
+ }
+
+ if (size != I2C_SMBUS_QUICK && (pmbus_regsize[command] == -1
+ || chip->regsize[command] == -1)) {
+ dev_dbg(&adap->dev, "Unsupported command 0x%02x\n", command);
+ return -EINVAL;
+ }
+
+ if (size != I2C_SMBUS_QUICK && read_write == I2C_SMBUS_WRITE
+ && !pmbus_rw[command]) {
+ dev_dbg(&adap->dev, "Command 0x%02x is r/o ", command);
+ return -EACCES;
+ }
+
+ switch (size) {
+
+ case I2C_SMBUS_QUICK:
+ dev_dbg(&adap->dev, "smbus quick - addr 0x%02x\n", addr);
+ ret = 0;
+ break;
+
+ case I2C_SMBUS_BYTE:
+ if (read_write == I2C_SMBUS_WRITE) {
+ if (command == PMBUS_CLEAR_FAULTS) {
+ chip->data[PMBUS_STATUS_BYTE][0] = 0;
+ chip->data[PMBUS_STATUS_WORD][0] = 0;
+ chip->data[PMBUS_STATUS_WORD][1] = 0;
+ chip->data[PMBUS_STATUS_CML][0] = 0;
+ chip->data[PMBUS_STATUS_VOUT][0] = 0;
+ chip->data[PMBUS_STATUS_IOUT][0] = 0;
+ chip->data[PMBUS_STATUS_INPUT][0] = 0;
+ chip->data[PMBUS_STATUS_TEMPERATURE][0] = 0;
+ chip->data[PMBUS_STATUS_OTHER][0] = 0;
+ chip->data[PMBUS_STATUS_FANS_1_2][0] = 0;
+ chip->data[PMBUS_STATUS_FANS_3_4][0] = 0;
+ }
+ chip->pointer = command;
+ dev_dbg(&adap->dev, "smbus byte - addr 0x%02x, "
+ "wrote 0x%02x.\n", addr, command);
+ } else {
+ data->byte = chip->data[chip->pointer][0];
+ dev_dbg(&adap->dev, "smbus byte - addr 0x%02x, "
+ "read 0x%02x.\n", addr, data->byte);
+ }
+
+ ret = 0;
+ break;
+
+ case I2C_SMBUS_BYTE_DATA:
+ if (pmbus_regsize[command] < 1) {
+ pmbus_set_error(chip, PMBUS_STATUS_CML, PB_STATUS_CML,
+ PB_CML_FAULT_OTHER_COMM);
+ ret = -EINVAL;
+ break;
+ }
+ if (read_write == I2C_SMBUS_WRITE) {
+ if (command == PMBUS_PAGE
+ && data->byte > chip->pages) {
+ pmbus_set_error(chip, PMBUS_STATUS_CML,
+ PB_STATUS_CML,
+ PB_CML_FAULT_OTHER_COMM);
+ ret = -EINVAL;
+ break;
+ }
+ chip->data[command][0] = data->byte;
+ chip->data[command][1] = 0;
+ dev_dbg(&adap->dev, "smbus byte data - addr 0x%02x, "
+ "wrote 0x%02x at 0x%02x.\n",
+ addr, data->byte, command);
+ } else {
+ data->byte = chip->data[command][0];
+ dev_dbg(&adap->dev, "smbus byte data - addr 0x%02x, "
+ "read 0x%02x at 0x%02x.\n",
+ addr, data->byte, command);
+ }
+
+ ret = 0;
+ break;
+
+ case I2C_SMBUS_WORD_DATA:
+ if (pmbus_regsize[command] < 2) {
+ pmbus_set_error(chip, PMBUS_STATUS_CML, PB_STATUS_CML,
+ PB_CML_FAULT_OTHER_COMM);
+ ret = -EINVAL;
+ break;
+ }
+ if (read_write == I2C_SMBUS_WRITE) {
+ chip->data[command][0] = data->word & 0xff;
+ chip->data[command][1] = (data->word >> 8) & 0xff;
+ dev_dbg(&adap->dev, "smbus word data - addr 0x%02x, "
+ "wrote 0x%04x at 0x%02x.\n",
+ addr, data->word, command);
+ } else {
+ data->word = (chip->data[command][0]
+ | (chip->data[command][1] << 8));
+ dev_dbg(&adap->dev, "smbus word data - addr 0x%02x, "
+ "read 0x%04x at 0x%02x.\n",
+ addr, data->word, command);
+ }
+
+ ret = 0;
+ break;
+
+ case I2C_SMBUS_BLOCK_DATA:
+ len = data->block[0];
+ regsize = pmbus_regsize[command];
+ if (chip->regsize[command])
+ regsize = chip->regsize[command];
+ if (len <= 0 || len > regsize) {
+ pmbus_set_error(chip, PMBUS_STATUS_CML, PB_STATUS_CML,
+ PB_CML_FAULT_OTHER_COMM);
+ ret = -EINVAL;
+ break;
+ }
+ if (read_write == I2C_SMBUS_WRITE) {
+ for (i = 1; i <= len; i++)
+ chip->data[command][i-1] = data->block[i];
+ } else {
+ data->block[0] = regsize;
+ for (i = 0; i < regsize; i++)
+ data->block[i+1] = chip->data[command][i];
+ }
+
+ ret = 0;
+ break;
+
+ default:
+ dev_dbg(&adap->dev, "Unsupported I2C/SMBus command\n");
+ ret = -EOPNOTSUPP;
+ break;
+ } /* switch (size) */
+
+ return ret;
+}
+
+static u32 pmbus_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
+ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
+ I2C_SMBUS_BLOCK_DATA;
+}
+
+static const struct i2c_algorithm smbus_algorithm = {
+ .functionality = pmbus_func,
+ .smbus_xfer = pmbus_xfer,
+};
+
+static struct i2c_adapter pmbus_adapter = {
+ .owner = THIS_MODULE,
+ .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
+ .algo = &smbus_algorithm,
+ .nr = 2,
+ .name = "SMBus pmbus driver",
+};
+
+struct boundaries {
+ u8 reg;
+ u8 warn_low;
+ u8 warn_high;
+ u8 fault_low;
+ u8 fault_high;
+ u8 status_reg;
+ u8 warn_status_low;
+ u8 warn_status_high;
+ u8 fault_status_low;
+ u8 fault_status_high;
+ u8 g_status_bit_low;
+ u8 g_status_bit_high;
+ int min, max;
+};
+
+static struct boundaries boundaries[] = {
+ {
+ .reg = PMBUS_READ_VIN,
+ .warn_low = PMBUS_VIN_UV_WARN_LIMIT,
+ .warn_high = PMBUS_VIN_OV_WARN_LIMIT,
+ .fault_low = PMBUS_VIN_UV_FAULT_LIMIT,
+ .fault_high = PMBUS_VIN_OV_FAULT_LIMIT,
+ .status_reg = PMBUS_STATUS_INPUT,
+ .warn_status_low = PB_VOLTAGE_UV_WARNING,
+ .warn_status_high = PB_VOLTAGE_OV_WARNING,
+ .fault_status_low = PB_VOLTAGE_UV_FAULT,
+ .fault_status_high = PB_VOLTAGE_OV_FAULT,
+ .g_status_bit_low = PB_STATUS_VIN_UV,
+ .min = 0,
+ .max = 50000,
+ },
+ {
+ .reg = PMBUS_READ_VOUT,
+ .warn_low = PMBUS_VOUT_UV_WARN_LIMIT,
+ .warn_high = PMBUS_VOUT_OV_WARN_LIMIT,
+ .fault_low = PMBUS_VOUT_UV_FAULT_LIMIT,
+ .fault_high = PMBUS_VOUT_OV_FAULT_LIMIT,
+ .status_reg = PMBUS_STATUS_VOUT,
+ .warn_status_low = PB_VOLTAGE_UV_WARNING,
+ .warn_status_high = PB_VOLTAGE_OV_WARNING,
+ .fault_status_low = PB_VOLTAGE_UV_FAULT,
+ .fault_status_high = PB_VOLTAGE_OV_FAULT,
+ .g_status_bit_high = PB_STATUS_VOUT_OV,
+ .min = 0,
+ .max = 50000,
+ },
+ {
+ .reg = PMBUS_READ_PIN,
+ .warn_high = PMBUS_PIN_OP_WARN_LIMIT,
+ .status_reg = PMBUS_STATUS_INPUT,
+ .warn_status_high = PB_PIN_OP_WARNING,
+ .min = 0,
+ .max = 1000000,
+ },
+ {
+ .reg = PMBUS_READ_POUT,
+ .warn_high = PMBUS_POUT_OP_WARN_LIMIT,
+ .fault_high = PMBUS_POUT_OP_FAULT_LIMIT,
+ .status_reg = PMBUS_STATUS_IOUT,
+ .warn_status_high = PB_POUT_OP_WARNING,
+ .fault_status_high = PB_POUT_OP_FAULT,
+ .min = 0,
+ .max = 1000000,
+ },
+ {
+ .reg = PMBUS_READ_IIN,
+ .warn_high = PMBUS_IIN_OC_WARN_LIMIT,
+ .fault_high = PMBUS_IIN_OC_FAULT_LIMIT,
+ .status_reg = PMBUS_STATUS_INPUT,
+ .warn_status_high = PB_IIN_OC_WARNING,
+ .fault_status_high = PB_IIN_OC_FAULT,
+ .min = 0,
+ .max = 10000,
+ },
+ {
+ .reg = PMBUS_READ_IOUT,
+ .warn_high = PMBUS_IOUT_OC_WARN_LIMIT,
+ .fault_low = PMBUS_IOUT_UC_FAULT_LIMIT,
+ .fault_high = PMBUS_IOUT_OC_FAULT_LIMIT,
+ .status_reg = PMBUS_STATUS_IOUT,
+ .warn_status_high = PB_IOUT_OC_WARNING,
+ .fault_status_low = PB_IOUT_UC_FAULT,
+ .fault_status_high = PB_IOUT_OC_FAULT,
+ .g_status_bit_high = PB_STATUS_IOUT_OC,
+ .min = 0,
+ .max = 10000,
+ },
+ {
+ .reg = PMBUS_READ_TEMPERATURE_1,
+ .warn_low = PMBUS_UT_WARN_LIMIT,
+ .warn_high = PMBUS_OT_WARN_LIMIT,
+ .fault_low = PMBUS_UT_FAULT_LIMIT,
+ .fault_high = PMBUS_OT_FAULT_LIMIT,
+ .status_reg = PMBUS_STATUS_TEMPERATURE,
+ .warn_status_low = PB_TEMP_UT_WARNING,
+ .warn_status_high = PB_TEMP_OT_WARNING,
+ .fault_status_low = PB_TEMP_UT_FAULT,
+ .fault_status_high = PB_TEMP_OT_FAULT,
+ .g_status_bit_low = PB_STATUS_TEMPERATURE,
+ .g_status_bit_high = PB_STATUS_TEMPERATURE,
+ .min = 0,
+ .max = 100,
+ },
+ {
+ .reg = PMBUS_READ_TEMPERATURE_2,
+ .warn_low = PMBUS_UT_WARN_LIMIT,
+ .warn_high = PMBUS_OT_WARN_LIMIT,
+ .fault_low = PMBUS_UT_FAULT_LIMIT,
+ .fault_high = PMBUS_OT_FAULT_LIMIT,
+ .status_reg = PMBUS_STATUS_TEMPERATURE,
+ .warn_status_low = PB_TEMP_UT_WARNING,
+ .warn_status_high = PB_TEMP_OT_WARNING,
+ .fault_status_low = PB_TEMP_UT_FAULT,
+ .fault_status_high = PB_TEMP_OT_FAULT,
+ .g_status_bit_low = PB_STATUS_TEMPERATURE,
+ .g_status_bit_high = PB_STATUS_TEMPERATURE,
+ .min = 0,
+ .max = 100,
+ },
+ {
+ .reg = PMBUS_READ_TEMPERATURE_3,
+ .warn_low = PMBUS_UT_WARN_LIMIT,
+ .warn_high = PMBUS_OT_WARN_LIMIT,
+ .fault_low = PMBUS_UT_FAULT_LIMIT,
+ .fault_high = PMBUS_OT_FAULT_LIMIT,
+ .status_reg = PMBUS_STATUS_TEMPERATURE,
+ .warn_status_low = PB_TEMP_UT_WARNING,
+ .warn_status_high = PB_TEMP_OT_WARNING,
+ .fault_status_low = PB_TEMP_UT_FAULT,
+ .fault_status_high = PB_TEMP_OT_FAULT,
+ .g_status_bit_low = PB_STATUS_TEMPERATURE,
+ .g_status_bit_high = PB_STATUS_TEMPERATURE,
+ .min = 0,
+ .max = 100,
+ },
+};
+
+static int lintoval(u16 adc)
+{
+ s16 exponent, mantissa;
+ int val;
+
+ exponent = adc >> 11;
+ mantissa = adc & 0x07ff;
+
+ if (exponent > 0x0f)
+ exponent |= 0xffe0; /* sign extend exponent */
+ if (mantissa > 0x03ff)
+ mantissa |= 0xf800; /* sign extend mantissa */
+
+ /* scale result to milli-units */
+ val = mantissa * 1000;
+
+ if (exponent > 0)
+ val <<= exponent;
+ else if (exponent < 0)
+ val >>= -exponent;
+
+ return val;
+}
+
+static u16 valtolin(int val)
+{
+ s16 exponent = 0, mantissa = 0;
+
+ if (val < 0) {
+ while (val < -1024 * 512) {
+ exponent++;
+ val /= 2;
+ }
+ while (val > -1024 * 256) {
+ exponent--;
+ val *= 2;
+ }
+ } else if (val > 0) {
+ while (val > 1024 * 512) {
+ exponent++;
+ val /= 2;
+ }
+ while (val < 1024 * 256) {
+ exponent--;
+ val *= 2;
+ }
+ }
+ mantissa = (val + 500) / 1000;
+
+ return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800);
+}
+
+static void i2c_pmbus_update_chip(struct pmbus_chip *chip)
+{
+ int i, reg;
+
+ for (reg = PMBUS_READ_VIN; reg <= PMBUS_READ_PIN; reg++) {
+ u16 regval;
+ s8 offset;
+ int val, lf = 0, lw = 0, uf = 0, uw = 0, factor;
+ struct boundaries *b;
+
+ if (pmbus_regsize[reg] == -1 || chip->regsize[reg] == -1)
+ continue;
+ for (i = 0; i < ARRAY_SIZE(boundaries); i++) {
+ if (boundaries[i].reg == reg)
+ break;
+ }
+ if (i >= ARRAY_SIZE(boundaries))
+ continue;
+ b = &boundaries[i];
+
+ /* Randomly increase or decrease value up to
+ * critical limit. If a limit is exceeded, set
+ * warning/fault flag as appropriate
+ */
+ regval = (chip->data[reg][0] | (chip->data[reg][1] << 8));
+ val = lintoval(regval);
+ get_random_bytes(&offset, 1);
+ /* Change value up to approximately 1% */
+ factor = val >> 14;
+ if (factor < 0)
+ factor = -factor;
+ if (factor == 0)
+ factor = 1;
+ val += offset * factor;
+ if (b->fault_low) {
+ lf = lintoval(chip->data[b->fault_low][0]
+ | (chip->data[b->fault_low][1] << 8));
+ if (val < lf)
+ val = lf - 1;
+ }
+ if (b->warn_low) {
+ lw = lintoval(chip->data[b->warn_low][0]
+ | (chip->data[b->warn_low][1] << 8));
+ if (!b->fault_low && val < lw)
+ val = lw - 1;
+ }
+ if (!b->fault_low && !b->warn_low && val < b->min)
+ val = b->min;
+ if (b->fault_high) {
+ uf = lintoval(chip->data[b->fault_high][0]
+ | (chip->data[b->fault_high][1] << 8));
+ if (val > uf)
+ val = uf + 1;
+ }
+ if (b->warn_high) {
+ uw = lintoval(chip->data[b->warn_high][0]
+ | (chip->data[b->warn_high][1] << 8));
+ if (!b->fault_high && val > uw)
+ val = uw + 1;
+ }
+ if (!b->fault_high && !b->warn_high && val > b->max)
+ val = b->max;
+ if (b->status_reg && b->fault_status_low && b->fault_low) {
+ if (val < lf) {
+ chip->data[b->status_reg][0]
+ |= b->fault_status_low;
+ chip->data[PMBUS_STATUS_BYTE][0] |=
+ b->g_status_bit_low;
+ }
+ }
+ if (b->status_reg && b->fault_status_high && b->fault_high) {
+ if (val > uf) {
+ chip->data[b->status_reg][0] |=
+ b->fault_status_high;
+ chip->data[PMBUS_STATUS_BYTE][0] |=
+ b->g_status_bit_high;
+ }
+ }
+ if (b->status_reg && b->warn_status_low && b->warn_low) {
+ if (val < lw) {
+ chip->data[b->status_reg][0] |=
+ b->warn_status_low;
+ chip->data[PMBUS_STATUS_BYTE][0] |=
+ b->g_status_bit_low;
+ }
+ }
+ if (b->status_reg && b->warn_status_high && b->warn_high) {
+ if (val > uw) {
+ chip->data[b->status_reg][0] |=
+ b->warn_status_high;
+ chip->data[PMBUS_STATUS_BYTE][0] |=
+ b->g_status_bit_high;
+ }
+ }
+ regval = valtolin(val);
+ chip->data[reg][0] = regval & 0xff;
+ chip->data[reg][1] = (regval >> 8) & 0xff;
+ }
+}
+
+static int i2c_pmbus_update_thread(void *p)
+{
+ while (!kthread_should_stop()) {
+ int i;
+
+ for (i = 0; i < NUM_CHIPS; i++) {
+ struct pmbus_chip *chip = &pmbus_chips[i];
+
+ if (chip->addr && chip->linear)
+ i2c_pmbus_update_chip(chip);
+ }
+ if (kthread_should_stop())
+ break;
+ msleep_interruptible(1000);
+ }
+ return 0;
+}
+
+static struct task_struct *i2c_pmbus_kthread;
+
+static int __init i2c_pmbus_init(void)
+{
+ int i, j, ret;
+
+ for (i = 0; i < NUM_CHIPS; i++) {
+ if (pmbus_chips[i].addr) {
+ u16 *initdata = pmbus_chips[i].initdata;
+ if (initdata)
+ for (j = 0; j < 256; j++)
+ if (initdata[j]) {
+ pmbus_chips[i].data[j][0]
+ = initdata[j] & 0xff;
+ pmbus_chips[i].data[j][1]
+ = (initdata[j] >> 8) & 0xff;
+ }
+ printk(KERN_INFO "i2c-pmbus: Virtual %s at 0x%02x\n",
+ pmbus_chips[i].name, pmbus_chips[i].addr);
+ }
+ }
+
+ i2c_pmbus_kthread = kthread_run(i2c_pmbus_update_thread, NULL,
+ "pmbus_update");
+
+ ret = i2c_add_numbered_adapter(&pmbus_adapter);
+ return ret;
+}
+
+static void __exit i2c_pmbus_exit(void)
+{
+ if (i2c_pmbus_kthread)
+ kthread_stop(i2c_pmbus_kthread);
+ i2c_del_adapter(&pmbus_adapter);
+}
+
+MODULE_AUTHOR("Guenter Roeck <[email protected]>");
+MODULE_DESCRIPTION("I2C PMBus chip emulator");
+MODULE_LICENSE("GPL");
+
+module_init(i2c_pmbus_init);
+module_exit(i2c_pmbus_exit);
--
1.7.0.87.g0901d

2010-07-05 04:17:51

by Guenter Roeck

[permalink] [raw]
Subject: [PATCH/RFC v2 3/4] hwmon: pmbus driver documentation

Signed-off-by: Guenter Roeck <[email protected]>
---
Documentation/hwmon/pmbus | 139 +++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 139 insertions(+), 0 deletions(-)
create mode 100644 Documentation/hwmon/pmbus

diff --git a/Documentation/hwmon/pmbus b/Documentation/hwmon/pmbus
new file mode 100644
index 0000000..f5c256b
--- /dev/null
+++ b/Documentation/hwmon/pmbus
@@ -0,0 +1,139 @@
+Kernel driver pmbus
+====================
+
+Supported chips:
+ * Ericsson BMR45X series
+ DC/DC Converter
+ Prefix: 'bmr45x'
+ Addresses scanned: -
+ Datasheet:
+ http://archive.ericsson.net/service/internet/picov/get?DocNo=28701-EN/LZT146395
+ * Linear Technology LTC2978
+ Octal PMBus Power Supply Monitor and Controller
+ Prefix: 'ltc2978'
+ Addresses scanned: -
+ Datasheet: http://cds.linear.com/docs/Datasheet/2978fa.pdf
+ * Maxim MAX16064
+ Quad Power-Supply Controller
+ Prefix: 'max16064'
+ Addresses scanned: -
+ Datasheet: http://datasheets.maxim-ic.com/en/ds/MAX16064.pdf
+ * Maxim MAX8688
+ Digital Power-Supply Controller/Monitor
+ Prefix: 'max8688'
+ Addresses scanned: -
+ Datasheet: http://datasheets.maxim-ic.com/en/ds/MAX8688.pdf
+ * Texas Instruments UCD92xx
+ Digital PWM System Controller
+ Prefix: 'ucd921x', 'ucd9220', 'ucd9240'
+ Addresses scanned: -
+ Datasheet: http://focus.ti.com/lit/ds/symlink/ucd9240.pdf
+ * Generic PMBus devices
+ Prefix: 'pmbus'
+ Addresses scanned: -
+ Datasheet: n.a.
+
+Author: Guenter Roeck <[email protected]>
+
+
+Description
+-----------
+
+This driver supports hardware montoring for various PMBus compliant devices.
+It supports voltage, current, power, and temperature sensors as supported
+by the device.
+
+Each monitored channel has its own high and low limits, plus a critical
+limit.
+
+
+Usage Notes
+-----------
+
+This driver does not probe for PMBus devices, since there is no register
+which can be safely used to identify the chip (The MFG_ID register is not
+supported by all chips), and since there is no well defined address range for
+PMBus devices. You will have to instantiate the devices explicitly.
+
+Example: the following will load the driver for an LTC2978 at address 0x60
+on I2C bus #1:
+$ modprobe pmbus
+$ echo ltc2978 0x60 > /sys/bus/i2c/devices/i2c-1/new_device
+
+
+Sysfs entries
+-------------
+
+When probing the chip, the driver identifies which PMBus registers are
+supported, and determines available sensors from this information.
+Attribute files only exist if respective sensors are suported by the chip.
+Labels are provided to inform the user about the sensor associated with
+a given sysfs entry.
+
+The following attributes are supported. Limits are read-write; all other
+attributes are read-only.
+
+inX_input Measured voltage. From READ_VIN or READ_VOUT register.
+inX_min Minumum Voltage.
+ From VIN_UV_WARN_LIMIT or VOUT_UV_WARN_LIMIT register.
+inX_max Maximum voltage.
+ From VIN_OV_WARN_LIMIT or VOUT_OV_WARN_LIMIT register.
+inX_lcrit Critical minumum Voltage.
+ From VIN_UV_FAULT_LIMIT or VOUT_UV_FAULT_LIMIT register.
+inX_crit Critical maximum voltage.
+ From VIN_OV_FAULT_LIMIT or VOUT_OV_FAULT_LIMIT register.
+inX_min_alarm Voltage low alarm. From VOLTAGE_UV_WARNING status.
+inX_max_alarm Voltage high alarm. From VOLTAGE_OV_WARNING status.
+inX_lcrit_alarm Voltage critical low alarm.
+ From VOLTAGE_UV_FAULT status.
+inX_crit_alarm Voltage critical high alarm.
+ From VOLTAGE_OV_FAULT status.
+inX_label "vin", "vcap", or "voutY"
+
+currX_input Measured current. From READ_IIN or READ_IOUT register.
+currX_max Maximum current.
+ From IIN_OC_WARN_LIMIT or IOUT_OC_WARN_LIMIT register.
+currX_lcrit Critical minumum output current.
+ From IOUT_UC_FAULT_LIMIT register.
+currX_crit Critical maximum current.
+ From IIN_OC_FAULT_LIMIT or IOUT_OC_FAULT_LIMIT register.
+currX_alarm Current high alarm.
+ From IIN_OC_WARNING or IOUT_OC_WARNING status.
+currX_lcrit_alarm Output current critical low alarm.
+ From IOUT_UC_FAULT status.
+currX_crit_alarm Current critical high alarm.
+ From IIN_OC_FAULT or IOUT_OC_FAULT status.
+currX_label "iin" or "vinY"
+
+powerX_input Measured power. From READ_PIN or READ_POUT register.
+powerX_cap Output power cap. From POUT_MAX register.
+powerX_max Power limit. From PIN_OP_WARN_LIMIT or
+ POUT_OP_WARN_LIMIT register.
+powerX_crit Critical output power limit.
+ From POUT_OP_FAULT_LIMIT register.
+powerX_alarm Power high alarm.
+ From PIN_OP_WARNING or POUT_OP_WARNING status.
+powerX_crit_alarm Output power critical high alarm.
+ From POUT_OP_FAULT status.
+powerX_label "pin" or "poutY"
+
+tempX_input Measured tempererature.
+ From READ_TEMPERATURE_X register.
+tempX_min Mimimum tempererature. From UT_WARN_LIMIT register.
+tempX_max Maximum tempererature. From OT_WARN_LIMIT register.
+tempX_lcrit Critical low tempererature.
+ From UT_FAULT_LIMIT register.
+tempX_crit Critical high tempererature.
+ From OT_FAULT_LIMIT register.
+tempX_min_alarm Chip temperature low alarm. Set by comparing
+ READ_TEMPERATURE_X with UT_WARN_LIMIT if
+ TEMP_UT_WARNING status is set.
+tempX_max_alarm Chip temperature high alarm. Set by comparing
+ READ_TEMPERATURE_X with OT_WARN_LIMIT if
+ TEMP_OT_WARNING status is set.
+tempX_lcrit_alarm Chip temperature critical low alarm. Set by comparing
+ READ_TEMPERATURE_X with UT_FAULT_LIMIT if
+ TEMP_UT_FAULT status is set.
+tempX_crit_alarm Chip temperature critical high alarm. Set by comparing
+ READ_TEMPERATURE_X with OT_FAULT_LIMIT if
+ TEMP_OT_FAULT status is set.
--
1.7.0.87.g0901d

2010-07-05 07:19:07

by Jean Delvare

[permalink] [raw]
Subject: Re: [PATCH/RFC v2 4/4] hwmon: sysfs API updates

Hi Guenter,

On Sun, 4 Jul 2010 21:10:18 -0700, Guenter Roeck wrote:
> Signed-off-by: Guenter Roeck <[email protected]>
> ---
> Documentation/hwmon/sysfs-interface | 37 +++++++++++++++++++++++++++++-----
> 1 files changed, 31 insertions(+), 6 deletions(-)

As usual, I don't have the time to review the code, but I'd like to at
least comment on the sysfs interface changes:

>
> diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface
> index d4e2917..2dcec0f 100644
> --- a/Documentation/hwmon/sysfs-interface
> +++ b/Documentation/hwmon/sysfs-interface
> @@ -421,11 +421,12 @@ power[1-*]_accuracy Accuracy of the power meter.
> Unit: Percent
> RO
>
> -power[1-*]_alarm 1 if the system is drawing more power than the
> - cap allows; 0 otherwise. A poll notification is
> - sent to this file when the power use exceeds the
> - cap. This file only appears if the cap is known
> - to be enforced by hardware.
> +power[1-*]_alarm 1 if the system is drawing more power than cap
> + or max allows; 0 otherwise. A poll notification
> + is sent to this file when the power use exceeds
> + the cap or max limit. If only cap is supported,
> + this file only appears if the cap is known to be
> + enforced by hardware.
> RO
>
> power[1-*]_cap If power use rises above this limit, the
> @@ -450,6 +451,18 @@ power[1-*]_cap_min Minimum cap that can be set.
> Unit: microWatt
> RO
>
> +power[1-*]_max Maximum power.
> + Unit: microWatt
> + RW
> +
> +power[1-*]_crit Critical maximum power.
> + If power rises to or above this limit, the
> + system will take drastic action to reduce power
> + consumption, such as a system shutdown. At the
> + very least, a power fault will be generated.
> + Unit: microWatt
> + RO

Why RO and not RW as every other limit file?

> +
> **********
> * Energy *
> **********
> @@ -471,8 +484,14 @@ limit-related alarms, not both. The driver should just reflect the hardware
> implementation.
>
> in[0-*]_alarm
> +in[0-*]_crit_alarm
> +curr[1-*]_alarm
> +curr[1-*]_crit_alarm
> +power[1-*]_alarm
> +power[1-*]_crit_alarm
> fan[1-*]_alarm
> temp[1-*]_alarm
> +temp[1-*]_crit_alarm
> Channel alarm
> 0: no alarm
> 1: alarm

The limit-specific alarms (*_crit_alarm) go in the second section,
below. And as a matter of fact, you've already added some of them
there...

> @@ -482,10 +501,17 @@ OR
>
> in[0-*]_min_alarm
> in[0-*]_max_alarm
> +in[0-*]_lcrit_alarm
> +in[0-*]_crit_alarm
> +curr[1-*]_lcrit_alarm
> +curr[1-*]_crit_alarm

No _min and _max alarm for curr?

> +power[1-*]_min_alarm
> +power[1-*]_max_alarm
> fan[1-*]_min_alarm
> fan[1-*]_max_alarm
> temp[1-*]_min_alarm
> temp[1-*]_max_alarm
> +temp[1-*]_lcrit_alarm
> temp[1-*]_crit_alarm
> Limit alarm
> 0: no alarm
> @@ -497,7 +523,6 @@ to notify open diodes, unconnected fans etc. where the hardware
> supports it. When this boolean has value 1, the measurement for that
> channel should not be trusted.
>
> -in[0-*]_fault

I've removed it already in a separate patch, so your patch won't apply
if you try to remove it again.

> fan[1-*]_fault
> temp[1-*]_fault
> Input fault condition

In general, I'm happy with the proposed changes.

--
Jean Delvare

2010-07-05 21:41:47

by Guenter Roeck

[permalink] [raw]
Subject: Re: [PATCH/RFC v2 4/4] hwmon: sysfs API updates

Hi Jean,

I noticed I did not copy the list with my reply, so here are are again,
with a couple of additional comments.

On Mon, Jul 05, 2010 at 03:18:57AM -0400, Jean Delvare wrote:
> Hi Guenter,
>
> On Sun, 4 Jul 2010 21:10:18 -0700, Guenter Roeck wrote:
> > Signed-off-by: Guenter Roeck <[email protected]>
> > ---
> > Documentation/hwmon/sysfs-interface | 37 +++++++++++++++++++++++++++++-----
> > 1 files changed, 31 insertions(+), 6 deletions(-)
>
> As usual, I don't have the time to review the code, but I'd like to at
> least comment on the sysfs interface changes:
>
I appreciate any feedback I can get.

> >
> > diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface
> > index d4e2917..2dcec0f 100644
> > --- a/Documentation/hwmon/sysfs-interface
> > +++ b/Documentation/hwmon/sysfs-interface
> > @@ -421,11 +421,12 @@ power[1-*]_accuracy Accuracy of the power meter.
> > Unit: Percent
> > RO
> >
> > -power[1-*]_alarm 1 if the system is drawing more power than the
> > - cap allows; 0 otherwise. A poll notification is
> > - sent to this file when the power use exceeds the
> > - cap. This file only appears if the cap is known
> > - to be enforced by hardware.
> > +power[1-*]_alarm 1 if the system is drawing more power than cap
> > + or max allows; 0 otherwise. A poll notification
> > + is sent to this file when the power use exceeds
> > + the cap or max limit. If only cap is supported,
> > + this file only appears if the cap is known to be
> > + enforced by hardware.
> > RO
> >
> > power[1-*]_cap If power use rises above this limit, the
> > @@ -450,6 +451,18 @@ power[1-*]_cap_min Minimum cap that can be set.
> > Unit: microWatt
> > RO
> >
> > +power[1-*]_max Maximum power.
> > + Unit: microWatt
> > + RW
> > +
> > +power[1-*]_crit Critical maximum power.
> > + If power rises to or above this limit, the
> > + system will take drastic action to reduce power
> > + consumption, such as a system shutdown. At the
> > + very least, a power fault will be generated.
> > + Unit: microWatt
> > + RO
>
> Why RO and not RW as every other limit file?
>
Cut-and-paste error. I'll fix it.

> > +
> > **********
> > * Energy *
> > **********
> > @@ -471,8 +484,14 @@ limit-related alarms, not both. The driver should just reflect the hardware
> > implementation.
> >
> > in[0-*]_alarm
> > +in[0-*]_crit_alarm
> > +curr[1-*]_alarm
> > +curr[1-*]_crit_alarm
> > +power[1-*]_alarm
> > +power[1-*]_crit_alarm
> > fan[1-*]_alarm
> > temp[1-*]_alarm
> > +temp[1-*]_crit_alarm
> > Channel alarm
> > 0: no alarm
> > 1: alarm
>
> The limit-specific alarms (*_crit_alarm) go in the second section,
> below. And as a matter of fact, you've already added some of them
> there...
>
Ok, makes sense.

> > @@ -482,10 +501,17 @@ OR
> >
> > in[0-*]_min_alarm
> > in[0-*]_max_alarm
> > +in[0-*]_lcrit_alarm
> > +in[0-*]_crit_alarm
> > +curr[1-*]_lcrit_alarm
> > +curr[1-*]_crit_alarm
>
> No _min and _max alarm for curr?
>
Oversight. pmbus devices don't support currX_min and currX_min_alarm, only
currX_lcrit and currX_lcrit_alarm. The ltc4245 driver already supports
currX_max_alarm, though, so I'll add both for consistency.

The ltc4245 driver only supports currX_max_alarm, not currX_min_alarm, though.
Wonder if that should be changed to currX_alarm, to more closely follow the API.
Let me know and I'll submit a patch if needed.

> > +power[1-*]_min_alarm
> > +power[1-*]_max_alarm
> > fan[1-*]_min_alarm
> > fan[1-*]_max_alarm
> > temp[1-*]_min_alarm
> > temp[1-*]_max_alarm
> > +temp[1-*]_lcrit_alarm
> > temp[1-*]_crit_alarm
> > Limit alarm
> > 0: no alarm
> > @@ -497,7 +523,6 @@ to notify open diodes, unconnected fans etc. where the hardware
> > supports it. When this boolean has value 1, the measurement for that
> > channel should not be trusted.
> >
> > -in[0-*]_fault
>
> I've removed it already in a separate patch, so your patch won't apply
> if you try to remove it again.
>
Ok, good point. I'll take it out.

> > fan[1-*]_fault
> > temp[1-*]_fault
> > Input fault condition
>
> In general, I'm happy with the proposed changes.
>
Thanks a lot for your time!

Guenter

2010-07-06 07:21:15

by Jean Delvare

[permalink] [raw]
Subject: Re: [PATCH/RFC v2 4/4] hwmon: sysfs API updates

On Mon, 5 Jul 2010 14:39:53 -0700, Guenter Roeck wrote:
> Hi Jean,
>
> I noticed I did not copy the list with my reply, so here are are again,
> with a couple of additional comments.
>
> On Mon, Jul 05, 2010 at 03:18:57AM -0400, Jean Delvare wrote:
> > On Sun, 4 Jul 2010 21:10:18 -0700, Guenter Roeck wrote:
> > > @@ -482,10 +501,17 @@ OR
> > >
> > > in[0-*]_min_alarm
> > > in[0-*]_max_alarm
> > > +in[0-*]_lcrit_alarm
> > > +in[0-*]_crit_alarm
> > > +curr[1-*]_lcrit_alarm
> > > +curr[1-*]_crit_alarm
> >
> > No _min and _max alarm for curr?
> >
> Oversight. pmbus devices don't support currX_min and currX_min_alarm, only
> currX_lcrit and currX_lcrit_alarm. The ltc4245 driver already supports
> currX_max_alarm, though, so I'll add both for consistency.
>
> The ltc4245 driver only supports currX_max_alarm, not currX_min_alarm, though.
> Wonder if that should be changed to currX_alarm, to more closely follow the API.
> Let me know and I'll submit a patch if needed.

Both should be defined and supported, and the driver author chooses
what fits his/her chip best.

--
Jean Delvare