aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hwmon/pmbus/pmbus_core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hwmon/pmbus/pmbus_core.c')
-rw-r--r--drivers/hwmon/pmbus/pmbus_core.c1472
1 files changed, 1166 insertions, 306 deletions
diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c
index d9c17feb7b4a..7ec04934747e 100644
--- a/drivers/hwmon/pmbus/pmbus_core.c
+++ b/drivers/hwmon/pmbus/pmbus_core.c
@@ -16,10 +16,11 @@
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
-#include <linux/jiffies.h>
#include <linux/pmbus.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
+#include <linux/of.h>
+#include <linux/thermal.h>
#include "pmbus.h"
/*
@@ -27,21 +28,6 @@
* with each call to krealloc
*/
#define PMBUS_ATTR_ALLOC_SIZE 32
-
-/*
- * 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_FAN_BASE (PB_STATUS_IOUT_BASE + PMBUS_PAGES)
-#define PB_STATUS_FAN34_BASE (PB_STATUS_FAN_BASE + PMBUS_PAGES)
-#define PB_STATUS_TEMP_BASE (PB_STATUS_FAN34_BASE + PMBUS_PAGES)
-#define PB_STATUS_INPUT_BASE (PB_STATUS_TEMP_BASE + PMBUS_PAGES)
-#define PB_STATUS_VMON_BASE (PB_STATUS_INPUT_BASE + 1)
-
-#define PB_NUM_STATUS_REG (PB_STATUS_VMON_BASE + 1)
-
#define PMBUS_NAME_SIZE 24
struct pmbus_sensor {
@@ -49,6 +35,7 @@ struct pmbus_sensor {
char name[PMBUS_NAME_SIZE]; /* sysfs sensor name */
struct device_attribute attribute;
u8 page; /* page number */
+ u8 phase; /* phase number, 0xff for all phases */
u16 reg; /* register */
enum pmbus_sensor_classes class; /* sensor class */
bool update; /* runtime sensor update needed */
@@ -76,6 +63,21 @@ struct pmbus_label {
#define to_pmbus_label(_attr) \
container_of(_attr, struct pmbus_label, attribute)
+/* Macros for converting between sensor index and register/page/status mask */
+
+#define PB_STATUS_MASK 0xffff
+#define PB_REG_SHIFT 16
+#define PB_REG_MASK 0x3ff
+#define PB_PAGE_SHIFT 26
+#define PB_PAGE_MASK 0x3f
+
+#define pb_reg_to_index(page, reg, mask) (((page) << PB_PAGE_SHIFT) | \
+ ((reg) << PB_REG_SHIFT) | (mask))
+
+#define pb_index_to_page(index) (((index) >> PB_PAGE_SHIFT) & PB_PAGE_MASK)
+#define pb_index_to_reg(index) (((index) >> PB_REG_SHIFT) & PB_REG_MASK)
+#define pb_index_to_mask(index) ((index) & PB_STATUS_MASK)
+
struct pmbus_data {
struct device *dev;
struct device *hwmon_dev;
@@ -96,19 +98,15 @@ struct pmbus_data {
struct pmbus_sensor *sensors;
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.
- */
- u16 status[PB_NUM_STATUS_REG];
bool has_status_word; /* device uses STATUS_WORD register */
int (*read_status)(struct i2c_client *client, int page);
- u8 currpage;
+ s16 currpage; /* current page, -1 for unknown/unset */
+ s16 currphase; /* current phase, 0xff for all, -1 for unknown/unset */
+
+ int vout_low[PMBUS_PAGES]; /* voltage low margin */
+ int vout_high[PMBUS_PAGES]; /* voltage high margin */
};
struct pmbus_debugfs_entry {
@@ -141,20 +139,34 @@ static const int pmbus_fan_command_registers[] = {
void pmbus_clear_cache(struct i2c_client *client)
{
struct pmbus_data *data = i2c_get_clientdata(client);
+ struct pmbus_sensor *sensor;
+
+ for (sensor = data->sensors; sensor; sensor = sensor->next)
+ sensor->data = -ENODATA;
+}
+EXPORT_SYMBOL_NS_GPL(pmbus_clear_cache, PMBUS);
+
+void pmbus_set_update(struct i2c_client *client, u8 reg, bool update)
+{
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ struct pmbus_sensor *sensor;
- data->valid = false;
+ for (sensor = data->sensors; sensor; sensor = sensor->next)
+ if (sensor->reg == reg)
+ sensor->update = update;
}
-EXPORT_SYMBOL_GPL(pmbus_clear_cache);
+EXPORT_SYMBOL_NS_GPL(pmbus_set_update, PMBUS);
-int pmbus_set_page(struct i2c_client *client, int page)
+int pmbus_set_page(struct i2c_client *client, int page, int phase)
{
struct pmbus_data *data = i2c_get_clientdata(client);
int rv;
- if (page < 0 || page == data->currpage)
+ if (page < 0)
return 0;
- if (!(data->info->func[page] & PMBUS_PAGE_VIRTUAL)) {
+ if (!(data->info->func[page] & PMBUS_PAGE_VIRTUAL) &&
+ data->info->pages > 1 && page != data->currpage) {
rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
if (rv < 0)
return rv;
@@ -166,24 +178,32 @@ int pmbus_set_page(struct i2c_client *client, int page)
if (rv != page)
return -EIO;
}
-
data->currpage = page;
+ if (data->info->phases[page] && data->currphase != phase &&
+ !(data->info->func[page] & PMBUS_PHASE_VIRTUAL)) {
+ rv = i2c_smbus_write_byte_data(client, PMBUS_PHASE,
+ phase);
+ if (rv)
+ return rv;
+ }
+ data->currphase = phase;
+
return 0;
}
-EXPORT_SYMBOL_GPL(pmbus_set_page);
+EXPORT_SYMBOL_NS_GPL(pmbus_set_page, PMBUS);
int pmbus_write_byte(struct i2c_client *client, int page, u8 value)
{
int rv;
- rv = pmbus_set_page(client, page);
+ rv = pmbus_set_page(client, page, 0xff);
if (rv < 0)
return rv;
return i2c_smbus_write_byte(client, value);
}
-EXPORT_SYMBOL_GPL(pmbus_write_byte);
+EXPORT_SYMBOL_NS_GPL(pmbus_write_byte, PMBUS);
/*
* _pmbus_write_byte() is similar to pmbus_write_byte(), but checks if
@@ -208,13 +228,13 @@ int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg,
{
int rv;
- rv = pmbus_set_page(client, page);
+ rv = pmbus_set_page(client, page, 0xff);
if (rv < 0)
return rv;
return i2c_smbus_write_word_data(client, reg, word);
}
-EXPORT_SYMBOL_GPL(pmbus_write_word_data);
+EXPORT_SYMBOL_NS_GPL(pmbus_write_word_data, PMBUS);
static int pmbus_write_virt_reg(struct i2c_client *client, int page, int reg,
@@ -261,6 +281,42 @@ static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg,
return pmbus_write_word_data(client, page, reg, word);
}
+/*
+ * _pmbus_write_byte_data() is similar to pmbus_write_byte_data(), but checks if
+ * a device specific mapping function exists and calls it if necessary.
+ */
+static int _pmbus_write_byte_data(struct i2c_client *client, int page, int reg, u8 value)
+{
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ const struct pmbus_driver_info *info = data->info;
+ int status;
+
+ if (info->write_byte_data) {
+ status = info->write_byte_data(client, page, reg, value);
+ if (status != -ENODATA)
+ return status;
+ }
+ return pmbus_write_byte_data(client, page, reg, value);
+}
+
+/*
+ * _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if
+ * a device specific mapping function exists and calls it if necessary.
+ */
+static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ const struct pmbus_driver_info *info = data->info;
+ int status;
+
+ if (info->read_byte_data) {
+ status = info->read_byte_data(client, page, reg);
+ if (status != -ENODATA)
+ return status;
+ }
+ return pmbus_read_byte_data(client, page, reg);
+}
+
int pmbus_update_fan(struct i2c_client *client, int page, int id,
u8 config, u8 mask, u16 command)
{
@@ -268,14 +324,14 @@ int pmbus_update_fan(struct i2c_client *client, int page, int id,
int rv;
u8 to;
- from = pmbus_read_byte_data(client, page,
+ from = _pmbus_read_byte_data(client, page,
pmbus_fan_config_registers[id]);
if (from < 0)
return from;
to = (from & ~mask) | (config & mask);
if (to != from) {
- rv = pmbus_write_byte_data(client, page,
+ rv = _pmbus_write_byte_data(client, page,
pmbus_fan_config_registers[id], to);
if (rv < 0)
return rv;
@@ -284,19 +340,19 @@ int pmbus_update_fan(struct i2c_client *client, int page, int id,
return _pmbus_write_word_data(client, page,
pmbus_fan_command_registers[id], command);
}
-EXPORT_SYMBOL_GPL(pmbus_update_fan);
+EXPORT_SYMBOL_NS_GPL(pmbus_update_fan, PMBUS);
-int pmbus_read_word_data(struct i2c_client *client, int page, u8 reg)
+int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg)
{
int rv;
- rv = pmbus_set_page(client, page);
+ rv = pmbus_set_page(client, page, phase);
if (rv < 0)
return rv;
return i2c_smbus_read_word_data(client, reg);
}
-EXPORT_SYMBOL_GPL(pmbus_read_word_data);
+EXPORT_SYMBOL_NS_GPL(pmbus_read_word_data, PMBUS);
static int pmbus_read_virt_reg(struct i2c_client *client, int page, int reg)
{
@@ -320,14 +376,15 @@ static int pmbus_read_virt_reg(struct i2c_client *client, int page, int reg)
* _pmbus_read_word_data() is similar to pmbus_read_word_data(), but checks if
* a device specific mapping function exists and calls it if necessary.
*/
-static int _pmbus_read_word_data(struct i2c_client *client, int page, int reg)
+static int _pmbus_read_word_data(struct i2c_client *client, int page,
+ int phase, int reg)
{
struct pmbus_data *data = i2c_get_clientdata(client);
const struct pmbus_driver_info *info = data->info;
int status;
if (info->read_word_data) {
- status = info->read_word_data(client, page, reg);
+ status = info->read_word_data(client, page, phase, reg);
if (status != -ENODATA)
return status;
}
@@ -335,32 +392,38 @@ static int _pmbus_read_word_data(struct i2c_client *client, int page, int reg)
if (reg >= PMBUS_VIRT_BASE)
return pmbus_read_virt_reg(client, page, reg);
- return pmbus_read_word_data(client, page, reg);
+ return pmbus_read_word_data(client, page, phase, reg);
+}
+
+/* Same as above, but without phase parameter, for use in check functions */
+static int __pmbus_read_word_data(struct i2c_client *client, int page, int reg)
+{
+ return _pmbus_read_word_data(client, page, 0xff, reg);
}
int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg)
{
int rv;
- rv = pmbus_set_page(client, page);
+ rv = pmbus_set_page(client, page, 0xff);
if (rv < 0)
return rv;
return i2c_smbus_read_byte_data(client, reg);
}
-EXPORT_SYMBOL_GPL(pmbus_read_byte_data);
+EXPORT_SYMBOL_NS_GPL(pmbus_read_byte_data, PMBUS);
int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, u8 value)
{
int rv;
- rv = pmbus_set_page(client, page);
+ rv = pmbus_set_page(client, page, 0xff);
if (rv < 0)
return rv;
return i2c_smbus_write_byte_data(client, reg, value);
}
-EXPORT_SYMBOL_GPL(pmbus_write_byte_data);
+EXPORT_SYMBOL_NS_GPL(pmbus_write_byte_data, PMBUS);
int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg,
u8 mask, u8 value)
@@ -368,35 +431,29 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg,
unsigned int tmp;
int rv;
- rv = pmbus_read_byte_data(client, page, reg);
+ rv = _pmbus_read_byte_data(client, page, reg);
if (rv < 0)
return rv;
tmp = (rv & ~mask) | (value & mask);
if (tmp != rv)
- rv = pmbus_write_byte_data(client, page, reg, tmp);
+ rv = _pmbus_write_byte_data(client, page, reg, tmp);
return rv;
}
-EXPORT_SYMBOL_GPL(pmbus_update_byte_data);
+EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, PMBUS);
-/*
- * _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if
- * a device specific mapping function exists and calls it if necessary.
- */
-static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg)
+static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg,
+ char *data_buf)
{
- struct pmbus_data *data = i2c_get_clientdata(client);
- const struct pmbus_driver_info *info = data->info;
- int status;
+ int rv;
- if (info->read_byte_data) {
- status = info->read_byte_data(client, page, reg);
- if (status != -ENODATA)
- return status;
- }
- return pmbus_read_byte_data(client, page, reg);
+ rv = pmbus_set_page(client, page, 0xff);
+ if (rv < 0)
+ return rv;
+
+ return i2c_smbus_read_block_data(client, reg, data_buf);
}
static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page,
@@ -433,14 +490,14 @@ static int pmbus_get_fan_rate(struct i2c_client *client, int page, int id,
return s->data;
}
- config = pmbus_read_byte_data(client, page,
+ config = _pmbus_read_byte_data(client, page,
pmbus_fan_config_registers[id]);
if (config < 0)
return config;
have_rpm = !!(config & pmbus_fan_rpm_mask[id]);
if (want_rpm == have_rpm)
- return pmbus_read_word_data(client, page,
+ return pmbus_read_word_data(client, page, 0xff,
pmbus_fan_command_registers[id]);
/* Can't sensibly map between RPM and PWM, just return zero */
@@ -452,14 +509,14 @@ int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id,
{
return pmbus_get_fan_rate(client, page, id, mode, false);
}
-EXPORT_SYMBOL_GPL(pmbus_get_fan_rate_device);
+EXPORT_SYMBOL_NS_GPL(pmbus_get_fan_rate_device, PMBUS);
int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id,
enum pmbus_fan_mode mode)
{
return pmbus_get_fan_rate(client, page, id, mode, true);
}
-EXPORT_SYMBOL_GPL(pmbus_get_fan_rate_cached);
+EXPORT_SYMBOL_NS_GPL(pmbus_get_fan_rate_cached, PMBUS);
static void pmbus_clear_fault_page(struct i2c_client *client, int page)
{
@@ -474,7 +531,7 @@ void pmbus_clear_faults(struct i2c_client *client)
for (i = 0; i < data->info->pages; i++)
pmbus_clear_fault_page(client, i);
}
-EXPORT_SYMBOL_GPL(pmbus_clear_faults);
+EXPORT_SYMBOL_NS_GPL(pmbus_clear_faults, PMBUS);
static int pmbus_check_status_cml(struct i2c_client *client)
{
@@ -501,6 +558,8 @@ static bool pmbus_check_register(struct i2c_client *client,
rv = func(client, page, reg);
if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK))
rv = pmbus_check_status_cml(client);
+ if (rv < 0 && (data->flags & PMBUS_READ_STATUS_AFTER_FAILED_CHECK))
+ data->read_status(client, -1);
pmbus_clear_fault_page(client, -1);
return rv >= 0;
}
@@ -526,13 +585,29 @@ bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg)
{
return pmbus_check_register(client, _pmbus_read_byte_data, page, reg);
}
-EXPORT_SYMBOL_GPL(pmbus_check_byte_register);
+EXPORT_SYMBOL_NS_GPL(pmbus_check_byte_register, PMBUS);
bool pmbus_check_word_register(struct i2c_client *client, int page, int reg)
{
- return pmbus_check_register(client, _pmbus_read_word_data, page, reg);
+ return pmbus_check_register(client, __pmbus_read_word_data, page, reg);
+}
+EXPORT_SYMBOL_NS_GPL(pmbus_check_word_register, PMBUS);
+
+static bool __maybe_unused pmbus_check_block_register(struct i2c_client *client,
+ int page, int reg)
+{
+ int rv;
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ char data_buf[I2C_SMBUS_BLOCK_MAX + 2];
+
+ rv = pmbus_read_block_data(client, page, reg, data_buf);
+ if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK))
+ rv = pmbus_check_status_cml(client);
+ if (rv < 0 && (data->flags & PMBUS_READ_STATUS_AFTER_FAILED_CHECK))
+ data->read_status(client, -1);
+ pmbus_clear_fault_page(client, -1);
+ return rv >= 0;
}
-EXPORT_SYMBOL_GPL(pmbus_check_word_register);
const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client)
{
@@ -540,81 +615,103 @@ const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client)
return data->info;
}
-EXPORT_SYMBOL_GPL(pmbus_get_driver_info);
-
-static struct _pmbus_status {
- u32 func;
- u16 base;
- u16 reg;
-} pmbus_status[] = {
- { PMBUS_HAVE_STATUS_VOUT, PB_STATUS_VOUT_BASE, PMBUS_STATUS_VOUT },
- { PMBUS_HAVE_STATUS_IOUT, PB_STATUS_IOUT_BASE, PMBUS_STATUS_IOUT },
- { PMBUS_HAVE_STATUS_TEMP, PB_STATUS_TEMP_BASE,
- PMBUS_STATUS_TEMPERATURE },
- { PMBUS_HAVE_STATUS_FAN12, PB_STATUS_FAN_BASE, PMBUS_STATUS_FAN_12 },
- { PMBUS_HAVE_STATUS_FAN34, PB_STATUS_FAN34_BASE, PMBUS_STATUS_FAN_34 },
-};
+EXPORT_SYMBOL_NS_GPL(pmbus_get_driver_info, PMBUS);
-static struct pmbus_data *pmbus_update_device(struct device *dev)
+static int pmbus_get_status(struct i2c_client *client, int page, int reg)
{
- struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_data *data = i2c_get_clientdata(client);
- const struct pmbus_driver_info *info = data->info;
- struct pmbus_sensor *sensor;
-
- mutex_lock(&data->update_lock);
- if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
- int i, j;
-
- for (i = 0; i < info->pages; i++) {
- data->status[PB_STATUS_BASE + i]
- = data->read_status(client, i);
- for (j = 0; j < ARRAY_SIZE(pmbus_status); j++) {
- struct _pmbus_status *s = &pmbus_status[j];
-
- if (!(info->func[i] & s->func))
- continue;
- data->status[s->base + i]
- = _pmbus_read_byte_data(client, i,
- s->reg);
- }
- }
+ int status;
- if (info->func[0] & PMBUS_HAVE_STATUS_INPUT)
- data->status[PB_STATUS_INPUT_BASE]
- = _pmbus_read_byte_data(client, 0,
- PMBUS_STATUS_INPUT);
-
- if (info->func[0] & PMBUS_HAVE_STATUS_VMON)
- data->status[PB_STATUS_VMON_BASE]
- = _pmbus_read_byte_data(client, 0,
- PMBUS_VIRT_STATUS_VMON);
-
- for (sensor = data->sensors; sensor; sensor = sensor->next) {
- if (!data->valid || sensor->update)
- sensor->data
- = _pmbus_read_word_data(client,
- sensor->page,
- sensor->reg);
- }
+ switch (reg) {
+ case PMBUS_STATUS_WORD:
+ status = data->read_status(client, page);
+ break;
+ default:
+ status = _pmbus_read_byte_data(client, page, reg);
+ break;
+ }
+ if (status < 0)
pmbus_clear_faults(client);
- data->last_updated = jiffies;
- data->valid = 1;
+ return status;
+}
+
+static void pmbus_update_sensor_data(struct i2c_client *client, struct pmbus_sensor *sensor)
+{
+ if (sensor->data < 0 || sensor->update)
+ sensor->data = _pmbus_read_word_data(client, sensor->page,
+ sensor->phase, sensor->reg);
+}
+
+/*
+ * Convert ieee754 sensor values to milli- or micro-units
+ * depending on sensor type.
+ *
+ * ieee754 data format:
+ * bit 15: sign
+ * bit 10..14: exponent
+ * bit 0..9: mantissa
+ * exponent=0:
+ * v=(−1)^signbit * 2^(−14) * 0.significantbits
+ * exponent=1..30:
+ * v=(−1)^signbit * 2^(exponent - 15) * 1.significantbits
+ * exponent=31:
+ * v=NaN
+ *
+ * Add the number mantissa bits into the calculations for simplicity.
+ * To do that, add '10' to the exponent. By doing that, we can just add
+ * 0x400 to normal values and get the expected result.
+ */
+static long pmbus_reg2data_ieee754(struct pmbus_data *data,
+ struct pmbus_sensor *sensor)
+{
+ int exponent;
+ bool sign;
+ long val;
+
+ /* only support half precision for now */
+ sign = sensor->data & 0x8000;
+ exponent = (sensor->data >> 10) & 0x1f;
+ val = sensor->data & 0x3ff;
+
+ if (exponent == 0) { /* subnormal */
+ exponent = -(14 + 10);
+ } else if (exponent == 0x1f) { /* NaN, convert to min/max */
+ exponent = 0;
+ val = 65504;
+ } else {
+ exponent -= (15 + 10); /* normal */
+ val |= 0x400;
}
- mutex_unlock(&data->update_lock);
- return data;
+
+ /* scale result to milli-units for all sensors except fans */
+ if (sensor->class != PSC_FAN)
+ val = val * 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;
+
+ if (sign)
+ val = -val;
+
+ return val;
}
/*
* 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)
+static s64 pmbus_reg2data_linear(struct pmbus_data *data,
+ struct pmbus_sensor *sensor)
{
s16 exponent;
s32 mantissa;
- long val;
+ s64 val;
if (sensor->class == PSC_VOLTAGE_OUT) { /* LINEAR16 */
exponent = data->exponent[sensor->page];
@@ -628,11 +725,11 @@ static long pmbus_reg2data_linear(struct pmbus_data *data,
/* scale result to milli-units for all sensors except fans */
if (sensor->class != PSC_FAN)
- val = val * 1000L;
+ val = val * 1000LL;
/* scale result to micro-units for power sensors */
if (sensor->class == PSC_POWER)
- val = val * 1000L;
+ val = val * 1000LL;
if (exponent >= 0)
val <<= exponent;
@@ -646,8 +743,8 @@ static long pmbus_reg2data_linear(struct pmbus_data *data,
* 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)
+static s64 pmbus_reg2data_direct(struct pmbus_data *data,
+ struct pmbus_sensor *sensor)
{
s64 b, val = (s16)sensor->data;
s32 m, R;
@@ -683,15 +780,15 @@ static long pmbus_reg2data_direct(struct pmbus_data *data,
}
val = div_s64(val - b, m);
- return clamp_val(val, LONG_MIN, LONG_MAX);
+ return val;
}
/*
* Convert VID sensor values to milli- or micro-units
* depending on sensor type.
*/
-static long pmbus_reg2data_vid(struct pmbus_data *data,
- struct pmbus_sensor *sensor)
+static s64 pmbus_reg2data_vid(struct pmbus_data *data,
+ struct pmbus_sensor *sensor)
{
long val = sensor->data;
long rv = 0;
@@ -721,9 +818,9 @@ static long pmbus_reg2data_vid(struct pmbus_data *data,
return rv;
}
-static long pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor)
+static s64 pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor)
{
- long val;
+ s64 val;
if (!sensor->convert)
return sensor->data;
@@ -735,6 +832,9 @@ static long pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor)
case vid:
val = pmbus_reg2data_vid(data, sensor);
break;
+ case ieee754:
+ val = pmbus_reg2data_ieee754(data, sensor);
+ break;
case linear:
default:
val = pmbus_reg2data_linear(data, sensor);
@@ -743,11 +843,75 @@ static long pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor)
return val;
}
-#define MAX_MANTISSA (1023 * 1000)
-#define MIN_MANTISSA (511 * 1000)
+#define MAX_IEEE_MANTISSA (0x7ff * 1000)
+#define MIN_IEEE_MANTISSA (0x400 * 1000)
+
+static u16 pmbus_data2reg_ieee754(struct pmbus_data *data,
+ struct pmbus_sensor *sensor, long val)
+{
+ u16 exponent = (15 + 10);
+ long mantissa;
+ u16 sign = 0;
+
+ /* simple case */
+ if (val == 0)
+ return 0;
+
+ if (val < 0) {
+ sign = 0x8000;
+ val = -val;
+ }
+
+ /* Power is in uW. Convert to mW before converting. */
+ if (sensor->class == PSC_POWER)
+ val = DIV_ROUND_CLOSEST(val, 1000L);
+
+ /*
+ * For simplicity, convert fan data to milli-units
+ * before calculating the exponent.
+ */
+ if (sensor->class == PSC_FAN)
+ val = val * 1000;
+
+ /* Reduce large mantissa until it fits into 10 bit */
+ while (val > MAX_IEEE_MANTISSA && exponent < 30) {
+ exponent++;
+ val >>= 1;
+ }
+ /*
+ * Increase small mantissa to generate valid 'normal'
+ * number
+ */
+ while (val < MIN_IEEE_MANTISSA && exponent > 1) {
+ exponent--;
+ val <<= 1;
+ }
+
+ /* Convert mantissa from milli-units to units */
+ mantissa = DIV_ROUND_CLOSEST(val, 1000);
+
+ /*
+ * Ensure that the resulting number is within range.
+ * Valid range is 0x400..0x7ff, where bit 10 reflects
+ * the implied high bit in normalized ieee754 numbers.
+ * Set the range to 0x400..0x7ff to reflect this.
+ * The upper bit is then removed by the mask against
+ * 0x3ff in the final assignment.
+ */
+ if (mantissa > 0x7ff)
+ mantissa = 0x7ff;
+ else if (mantissa < 0x400)
+ mantissa = 0x400;
+
+ /* Convert to sign, 5 bit exponent, 10 bit mantissa */
+ return sign | (mantissa & 0x3ff) | ((exponent << 10) & 0x7c00);
+}
+
+#define MAX_LIN_MANTISSA (1023 * 1000)
+#define MIN_LIN_MANTISSA (511 * 1000)
static u16 pmbus_data2reg_linear(struct pmbus_data *data,
- struct pmbus_sensor *sensor, long val)
+ struct pmbus_sensor *sensor, s64 val)
{
s16 exponent = 0, mantissa;
bool negative = false;
@@ -769,8 +933,8 @@ static u16 pmbus_data2reg_linear(struct pmbus_data *data,
val <<= -data->exponent[sensor->page];
else
val >>= data->exponent[sensor->page];
- val = DIV_ROUND_CLOSEST(val, 1000);
- return val & 0xffff;
+ val = DIV_ROUND_CLOSEST_ULL(val, 1000);
+ return clamp_val(val, 0, 0xffff);
}
if (val < 0) {
@@ -780,32 +944,28 @@ static u16 pmbus_data2reg_linear(struct pmbus_data *data,
/* Power is in uW. Convert to mW before converting. */
if (sensor->class == PSC_POWER)
- val = DIV_ROUND_CLOSEST(val, 1000L);
+ val = DIV_ROUND_CLOSEST_ULL(val, 1000);
/*
* For simplicity, convert fan data to milli-units
* before calculating the exponent.
*/
if (sensor->class == PSC_FAN)
- val = val * 1000;
+ val = val * 1000LL;
/* Reduce large mantissa until it fits into 10 bit */
- while (val >= MAX_MANTISSA && exponent < 15) {
+ while (val >= MAX_LIN_MANTISSA && exponent < 15) {
exponent++;
val >>= 1;
}
/* Increase small mantissa to improve precision */
- while (val < MIN_MANTISSA && exponent > -15) {
+ while (val < MIN_LIN_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;
+ mantissa = clamp_val(DIV_ROUND_CLOSEST_ULL(val, 1000), 0, 0x3ff);
/* restore sign */
if (negative)
@@ -816,9 +976,9 @@ static u16 pmbus_data2reg_linear(struct pmbus_data *data,
}
static u16 pmbus_data2reg_direct(struct pmbus_data *data,
- struct pmbus_sensor *sensor, long val)
+ struct pmbus_sensor *sensor, s64 val)
{
- s64 b, val64 = val;
+ s64 b;
s32 m, R;
m = data->info->m[sensor->class];
@@ -836,30 +996,30 @@ static u16 pmbus_data2reg_direct(struct pmbus_data *data,
R -= 3; /* Adjust R and b for data in milli-units */
b *= 1000;
}
- val64 = val64 * m + b;
+ val = val * m + b;
while (R > 0) {
- val64 *= 10;
+ val *= 10;
R--;
}
while (R < 0) {
- val64 = div_s64(val64 + 5LL, 10L); /* round closest */
+ val = div_s64(val + 5LL, 10L); /* round closest */
R++;
}
- return (u16)clamp_val(val64, S16_MIN, S16_MAX);
+ return (u16)clamp_val(val, S16_MIN, S16_MAX);
}
static u16 pmbus_data2reg_vid(struct pmbus_data *data,
- struct pmbus_sensor *sensor, long val)
+ struct pmbus_sensor *sensor, s64 val)
{
val = clamp_val(val, 500, 1600);
- return 2 + DIV_ROUND_CLOSEST((1600 - val) * 100, 625);
+ return 2 + DIV_ROUND_CLOSEST_ULL((1600LL - val) * 100LL, 625);
}
static u16 pmbus_data2reg(struct pmbus_data *data,
- struct pmbus_sensor *sensor, long val)
+ struct pmbus_sensor *sensor, s64 val)
{
u16 regval;
@@ -873,6 +1033,9 @@ static u16 pmbus_data2reg(struct pmbus_data *data,
case vid:
regval = pmbus_data2reg_vid(data, sensor, val);
break;
+ case ieee754:
+ regval = pmbus_data2reg_ieee754(data, sensor, val);
+ break;
case linear:
default:
regval = pmbus_data2reg_linear(data, sensor, val);
@@ -904,38 +1067,56 @@ static u16 pmbus_data2reg(struct pmbus_data *data,
* If a negative value is stored in any of the referenced registers, this value
* reflects an error code which will be returned.
*/
-static int pmbus_get_boolean(struct pmbus_data *data, struct pmbus_boolean *b,
+static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b,
int index)
{
+ struct pmbus_data *data = i2c_get_clientdata(client);
struct pmbus_sensor *s1 = b->s1;
struct pmbus_sensor *s2 = b->s2;
- u16 reg = (index >> 16) & 0xffff;
- u16 mask = index & 0xffff;
+ u16 mask = pb_index_to_mask(index);
+ u8 page = pb_index_to_page(index);
+ u16 reg = pb_index_to_reg(index);
int ret, status;
u16 regval;
- status = data->status[reg];
- if (status < 0)
- return status;
+ mutex_lock(&data->update_lock);
+ status = pmbus_get_status(client, page, reg);
+ if (status < 0) {
+ ret = status;
+ goto unlock;
+ }
+
+ if (s1)
+ pmbus_update_sensor_data(client, s1);
+ if (s2)
+ pmbus_update_sensor_data(client, s2);
regval = status & mask;
- if (!s1 && !s2) {
- ret = !!regval;
- } else if (!s1 || !s2) {
- WARN(1, "Bad boolean descriptor %p: s1=%p, s2=%p\n", b, s1, s2);
- return 0;
- } else {
- long v1, v2;
+ if (regval) {
+ ret = _pmbus_write_byte_data(client, page, reg, regval);
+ if (ret)
+ goto unlock;
+ }
+ if (s1 && s2) {
+ s64 v1, v2;
- if (s1->data < 0)
- return s1->data;
- if (s2->data < 0)
- return s2->data;
+ if (s1->data < 0) {
+ ret = s1->data;
+ goto unlock;
+ }
+ if (s2->data < 0) {
+ ret = s2->data;
+ goto unlock;
+ }
v1 = pmbus_reg2data(data, s1);
v2 = pmbus_reg2data(data, s2);
ret = !!(regval && v1 >= v2);
+ } else {
+ ret = !!regval;
}
+unlock:
+ mutex_unlock(&data->update_lock);
return ret;
}
@@ -944,25 +1125,31 @@ static ssize_t pmbus_show_boolean(struct device *dev,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
struct pmbus_boolean *boolean = to_pmbus_boolean(attr);
- struct pmbus_data *data = pmbus_update_device(dev);
+ struct i2c_client *client = to_i2c_client(dev->parent);
int val;
- val = pmbus_get_boolean(data, boolean, attr->index);
+ val = pmbus_get_boolean(client, boolean, attr->index);
if (val < 0)
return val;
- return snprintf(buf, PAGE_SIZE, "%d\n", val);
+ return sysfs_emit(buf, "%d\n", val);
}
static ssize_t pmbus_show_sensor(struct device *dev,
struct device_attribute *devattr, char *buf)
{
- struct pmbus_data *data = pmbus_update_device(dev);
+ struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_sensor *sensor = to_pmbus_sensor(devattr);
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ ssize_t ret;
+ mutex_lock(&data->update_lock);
+ pmbus_update_sensor_data(client, sensor);
if (sensor->data < 0)
- return sensor->data;
-
- return snprintf(buf, PAGE_SIZE, "%ld\n", pmbus_reg2data(data, sensor));
+ ret = sensor->data;
+ else
+ ret = sysfs_emit(buf, "%lld\n", pmbus_reg2data(data, sensor));
+ mutex_unlock(&data->update_lock);
+ return ret;
}
static ssize_t pmbus_set_sensor(struct device *dev,
@@ -973,11 +1160,11 @@ static ssize_t pmbus_set_sensor(struct device *dev,
struct pmbus_data *data = i2c_get_clientdata(client);
struct pmbus_sensor *sensor = to_pmbus_sensor(devattr);
ssize_t rv = count;
- long val = 0;
+ s64 val;
int ret;
u16 regval;
- if (kstrtol(buf, 10, &val) < 0)
+ if (kstrtos64(buf, 10, &val) < 0)
return -EINVAL;
mutex_lock(&data->update_lock);
@@ -986,7 +1173,7 @@ static ssize_t pmbus_set_sensor(struct device *dev,
if (ret < 0)
rv = ret;
else
- sensor->data = regval;
+ sensor->data = -ENODATA;
mutex_unlock(&data->update_lock);
return rv;
}
@@ -996,16 +1183,16 @@ static ssize_t pmbus_show_label(struct device *dev,
{
struct pmbus_label *label = to_pmbus_label(da);
- return snprintf(buf, PAGE_SIZE, "%s\n", label->label);
+ return sysfs_emit(buf, "%s\n", label->label);
}
static int pmbus_add_attribute(struct pmbus_data *data, struct attribute *attr)
{
if (data->num_attributes >= data->max_attributes - 1) {
int new_max_attrs = data->max_attributes + PMBUS_ATTR_ALLOC_SIZE;
- void *new_attrs = krealloc(data->group.attrs,
- new_max_attrs * sizeof(void *),
- GFP_KERNEL);
+ void *new_attrs = devm_krealloc(data->dev, data->group.attrs,
+ new_max_attrs * sizeof(void *),
+ GFP_KERNEL);
if (!new_attrs)
return -ENOMEM;
data->group.attrs = new_attrs;
@@ -1053,11 +1240,14 @@ static int pmbus_add_boolean(struct pmbus_data *data,
const char *name, const char *type, int seq,
struct pmbus_sensor *s1,
struct pmbus_sensor *s2,
- u16 reg, u16 mask)
+ u8 page, u16 reg, u16 mask)
{
struct pmbus_boolean *boolean;
struct sensor_device_attribute *a;
+ if (WARN((s1 && !s2) || (!s1 && s2), "Bad s1/s2 parameters\n"))
+ return -EINVAL;
+
boolean = devm_kzalloc(data->dev, sizeof(*boolean), GFP_KERNEL);
if (!boolean)
return -ENOMEM;
@@ -1069,14 +1259,77 @@ static int pmbus_add_boolean(struct pmbus_data *data,
boolean->s1 = s1;
boolean->s2 = s2;
pmbus_attr_init(a, boolean->name, 0444, pmbus_show_boolean, NULL,
- (reg << 16) | mask);
+ pb_reg_to_index(page, reg, mask));
return pmbus_add_attribute(data, &a->dev_attr.attr);
}
+/* of thermal for pmbus temperature sensors */
+struct pmbus_thermal_data {
+ struct pmbus_data *pmbus_data;
+ struct pmbus_sensor *sensor;
+};
+
+static int pmbus_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct pmbus_thermal_data *tdata = tz->devdata;
+ struct pmbus_sensor *sensor = tdata->sensor;
+ struct pmbus_data *pmbus_data = tdata->pmbus_data;
+ struct i2c_client *client = to_i2c_client(pmbus_data->dev);
+ struct device *dev = pmbus_data->hwmon_dev;
+ int ret = 0;
+
+ if (!dev) {
+ /* May not even get to hwmon yet */
+ *temp = 0;
+ return 0;
+ }
+
+ mutex_lock(&pmbus_data->update_lock);
+ pmbus_update_sensor_data(client, sensor);
+ if (sensor->data < 0)
+ ret = sensor->data;
+ else
+ *temp = (int)pmbus_reg2data(pmbus_data, sensor);
+ mutex_unlock(&pmbus_data->update_lock);
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops pmbus_thermal_ops = {
+ .get_temp = pmbus_thermal_get_temp,
+};
+
+static int pmbus_thermal_add_sensor(struct pmbus_data *pmbus_data,
+ struct pmbus_sensor *sensor, int index)
+{
+ struct device *dev = pmbus_data->dev;
+ struct pmbus_thermal_data *tdata;
+ struct thermal_zone_device *tzd;
+
+ tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL);
+ if (!tdata)
+ return -ENOMEM;
+
+ tdata->sensor = sensor;
+ tdata->pmbus_data = pmbus_data;
+
+ tzd = devm_thermal_of_zone_register(dev, index, tdata,
+ &pmbus_thermal_ops);
+ /*
+ * If CONFIG_THERMAL_OF is disabled, this returns -ENODEV,
+ * so ignore that error but forward any other error.
+ */
+ if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV))
+ return PTR_ERR(tzd);
+
+ return 0;
+}
+
static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data,
const char *name, const char *type,
- int seq, int page, int reg,
+ int seq, int page, int phase,
+ int reg,
enum pmbus_sensor_classes class,
bool update, bool readonly,
bool convert)
@@ -1100,10 +1353,12 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data,
readonly = true;
sensor->page = page;
+ sensor->phase = phase;
sensor->reg = reg;
sensor->class = class;
sensor->update = update;
sensor->convert = convert;
+ sensor->data = -ENODATA;
pmbus_dev_attr_init(a, sensor->name,
readonly ? 0444 : 0644,
pmbus_show_sensor, pmbus_set_sensor);
@@ -1114,12 +1369,16 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data,
sensor->next = data->sensors;
data->sensors = sensor;
+ /* temperature sensors with _input values are registered with thermal */
+ if (class == PSC_TEMPERATURE && strcmp(type, "input") == 0)
+ pmbus_thermal_add_sensor(data, sensor, seq);
+
return sensor;
}
static int pmbus_add_label(struct pmbus_data *data,
const char *name, int seq,
- const char *lstring, int index)
+ const char *lstring, int index, int phase)
{
struct pmbus_label *label;
struct device_attribute *a;
@@ -1131,11 +1390,21 @@ static int pmbus_add_label(struct pmbus_data *data,
a = &label->attribute;
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);
+ if (!index) {
+ if (phase == 0xff)
+ strncpy(label->label, lstring,
+ sizeof(label->label) - 1);
+ else
+ snprintf(label->label, sizeof(label->label), "%s.%d",
+ lstring, phase);
+ } else {
+ if (phase == 0xff)
+ snprintf(label->label, sizeof(label->label), "%s%d",
+ lstring, index);
+ else
+ snprintf(label->label, sizeof(label->label), "%s%d.%d",
+ lstring, index, phase);
+ }
pmbus_dev_attr_init(a, label->name, 0444, pmbus_show_label, NULL);
return pmbus_add_attribute(data, &a->attr);
@@ -1174,7 +1443,7 @@ struct pmbus_sensor_attr {
bool compare; /* true if compare function needed */
u32 func; /* sensor mask */
u32 sfunc; /* sensor status mask */
- int sbase; /* status base register */
+ int sreg; /* status register */
const struct pmbus_limit_attr *limit;/* limit registers */
};
@@ -1200,7 +1469,7 @@ static int pmbus_add_limit_attrs(struct i2c_client *client,
for (i = 0; i < nlimit; i++) {
if (pmbus_check_word_register(client, page, l->reg)) {
curr = pmbus_add_sensor(data, name, l->attr, index,
- page, l->reg, attr->class,
+ page, 0xff, l->reg, attr->class,
attr->update || l->update,
false, true);
if (!curr)
@@ -1212,7 +1481,7 @@ static int pmbus_add_limit_attrs(struct i2c_client *client,
: NULL,
attr->compare ? l->low ? base : curr
: NULL,
- attr->sbase + page, l->sbit);
+ page, attr->sreg, l->sbit);
if (ret)
return ret;
have_alarm = 1;
@@ -1227,7 +1496,7 @@ static int pmbus_add_sensor_attrs_one(struct i2c_client *client,
struct pmbus_data *data,
const struct pmbus_driver_info *info,
const char *name,
- int index, int page,
+ int index, int page, int phase,
const struct pmbus_sensor_attr *attr,
bool paged)
{
@@ -1237,15 +1506,16 @@ static int pmbus_add_sensor_attrs_one(struct i2c_client *client,
if (attr->label) {
ret = pmbus_add_label(data, name, index, attr->label,
- paged ? page + 1 : 0);
+ paged ? page + 1 : 0, phase);
if (ret)
return ret;
}
- base = pmbus_add_sensor(data, name, "input", index, page, attr->reg,
- attr->class, true, true, true);
+ base = pmbus_add_sensor(data, name, "input", index, page, phase,
+ attr->reg, attr->class, true, true, true);
if (!base)
return -ENOMEM;
- if (attr->sfunc) {
+ /* No limit and alarm attributes for phase specific sensors */
+ if (attr->sfunc && phase == 0xff) {
ret = pmbus_add_limit_attrs(client, data, info, name,
index, page, base, attr);
if (ret < 0)
@@ -1257,11 +1527,11 @@ static int pmbus_add_sensor_attrs_one(struct i2c_client *client,
* which global bit is set) for this page is accessible.
*/
if (!ret && attr->gbit &&
- (!upper || (upper && data->has_status_word)) &&
+ (!upper || data->has_status_word) &&
pmbus_check_status_register(client, page)) {
ret = pmbus_add_boolean(data, name, "alarm", index,
NULL, NULL,
- PB_STATUS_BASE + page,
+ page, PMBUS_STATUS_WORD,
attr->gbit);
if (ret)
return ret;
@@ -1311,14 +1581,29 @@ static int pmbus_add_sensor_attrs(struct i2c_client *client,
pages = paged ? info->pages : 1;
for (page = 0; page < pages; page++) {
- if (!(info->func[page] & attrs->func))
- continue;
- ret = pmbus_add_sensor_attrs_one(client, data, info,
- name, index, page,
- attrs, paged);
- if (ret)
- return ret;
- index++;
+ if (info->func[page] & attrs->func) {
+ ret = pmbus_add_sensor_attrs_one(client, data, info,
+ name, index, page,
+ 0xff, attrs, paged);
+ if (ret)
+ return ret;
+ index++;
+ }
+ if (info->phases[page]) {
+ int phase;
+
+ for (phase = 0; phase < info->phases[page];
+ phase++) {
+ if (!(info->pfunc[phase] & attrs->func))
+ continue;
+ ret = pmbus_add_sensor_attrs_one(client,
+ data, info, name, index, page,
+ phase, attrs, paged);
+ if (ret)
+ return ret;
+ index++;
+ }
+ }
}
attrs++;
}
@@ -1335,7 +1620,7 @@ static const struct pmbus_limit_attr vin_limit_attrs[] = {
.reg = PMBUS_VIN_UV_FAULT_LIMIT,
.attr = "lcrit",
.alarm = "lcrit_alarm",
- .sbit = PB_VOLTAGE_UV_FAULT,
+ .sbit = PB_VOLTAGE_UV_FAULT | PB_VOLTAGE_VIN_OFF,
}, {
.reg = PMBUS_VIN_OV_WARN_LIMIT,
.attr = "max",
@@ -1361,6 +1646,12 @@ static const struct pmbus_limit_attr vin_limit_attrs[] = {
}, {
.reg = PMBUS_VIRT_RESET_VIN_HISTORY,
.attr = "reset_history",
+ }, {
+ .reg = PMBUS_MFR_VIN_MIN,
+ .attr = "rated_min",
+ }, {
+ .reg = PMBUS_MFR_VIN_MAX,
+ .attr = "rated_max",
},
};
@@ -1424,7 +1715,13 @@ static const struct pmbus_limit_attr vout_limit_attrs[] = {
}, {
.reg = PMBUS_VIRT_RESET_VOUT_HISTORY,
.attr = "reset_history",
- }
+ }, {
+ .reg = PMBUS_MFR_VOUT_MIN,
+ .attr = "rated_min",
+ }, {
+ .reg = PMBUS_MFR_VOUT_MAX,
+ .attr = "rated_max",
+ },
};
static const struct pmbus_sensor_attr voltage_attributes[] = {
@@ -1434,7 +1731,7 @@ static const struct pmbus_sensor_attr voltage_attributes[] = {
.label = "vin",
.func = PMBUS_HAVE_VIN,
.sfunc = PMBUS_HAVE_STATUS_INPUT,
- .sbase = PB_STATUS_INPUT_BASE,
+ .sreg = PMBUS_STATUS_INPUT,
.gbit = PB_STATUS_VIN_UV,
.limit = vin_limit_attrs,
.nlimit = ARRAY_SIZE(vin_limit_attrs),
@@ -1444,7 +1741,7 @@ static const struct pmbus_sensor_attr voltage_attributes[] = {
.label = "vmon",
.func = PMBUS_HAVE_VMON,
.sfunc = PMBUS_HAVE_STATUS_VMON,
- .sbase = PB_STATUS_VMON_BASE,
+ .sreg = PMBUS_VIRT_STATUS_VMON,
.limit = vmon_limit_attrs,
.nlimit = ARRAY_SIZE(vmon_limit_attrs),
}, {
@@ -1459,7 +1756,7 @@ static const struct pmbus_sensor_attr voltage_attributes[] = {
.paged = true,
.func = PMBUS_HAVE_VOUT,
.sfunc = PMBUS_HAVE_STATUS_VOUT,
- .sbase = PB_STATUS_VOUT_BASE,
+ .sreg = PMBUS_STATUS_VOUT,
.gbit = PB_STATUS_VOUT_OV,
.limit = vout_limit_attrs,
.nlimit = ARRAY_SIZE(vout_limit_attrs),
@@ -1494,7 +1791,10 @@ static const struct pmbus_limit_attr iin_limit_attrs[] = {
}, {
.reg = PMBUS_VIRT_RESET_IIN_HISTORY,
.attr = "reset_history",
- }
+ }, {
+ .reg = PMBUS_MFR_IIN_MAX,
+ .attr = "rated_max",
+ },
};
static const struct pmbus_limit_attr iout_limit_attrs[] = {
@@ -1528,7 +1828,10 @@ static const struct pmbus_limit_attr iout_limit_attrs[] = {
}, {
.reg = PMBUS_VIRT_RESET_IOUT_HISTORY,
.attr = "reset_history",
- }
+ }, {
+ .reg = PMBUS_MFR_IOUT_MAX,
+ .attr = "rated_max",
+ },
};
static const struct pmbus_sensor_attr current_attributes[] = {
@@ -1538,7 +1841,7 @@ static const struct pmbus_sensor_attr current_attributes[] = {
.label = "iin",
.func = PMBUS_HAVE_IIN,
.sfunc = PMBUS_HAVE_STATUS_INPUT,
- .sbase = PB_STATUS_INPUT_BASE,
+ .sreg = PMBUS_STATUS_INPUT,
.gbit = PB_STATUS_INPUT,
.limit = iin_limit_attrs,
.nlimit = ARRAY_SIZE(iin_limit_attrs),
@@ -1549,7 +1852,7 @@ static const struct pmbus_sensor_attr current_attributes[] = {
.paged = true,
.func = PMBUS_HAVE_IOUT,
.sfunc = PMBUS_HAVE_STATUS_IOUT,
- .sbase = PB_STATUS_IOUT_BASE,
+ .sreg = PMBUS_STATUS_IOUT,
.gbit = PB_STATUS_IOUT_OC,
.limit = iout_limit_attrs,
.nlimit = ARRAY_SIZE(iout_limit_attrs),
@@ -1579,7 +1882,10 @@ static const struct pmbus_limit_attr pin_limit_attrs[] = {
}, {
.reg = PMBUS_VIRT_RESET_PIN_HISTORY,
.attr = "reset_history",
- }
+ }, {
+ .reg = PMBUS_MFR_PIN_MAX,
+ .attr = "rated_max",
+ },
};
static const struct pmbus_limit_attr pout_limit_attrs[] = {
@@ -1613,7 +1919,10 @@ static const struct pmbus_limit_attr pout_limit_attrs[] = {
}, {
.reg = PMBUS_VIRT_RESET_POUT_HISTORY,
.attr = "reset_history",
- }
+ }, {
+ .reg = PMBUS_MFR_POUT_MAX,
+ .attr = "rated_max",
+ },
};
static const struct pmbus_sensor_attr power_attributes[] = {
@@ -1623,7 +1932,7 @@ static const struct pmbus_sensor_attr power_attributes[] = {
.label = "pin",
.func = PMBUS_HAVE_PIN,
.sfunc = PMBUS_HAVE_STATUS_INPUT,
- .sbase = PB_STATUS_INPUT_BASE,
+ .sreg = PMBUS_STATUS_INPUT,
.gbit = PB_STATUS_INPUT,
.limit = pin_limit_attrs,
.nlimit = ARRAY_SIZE(pin_limit_attrs),
@@ -1634,7 +1943,7 @@ static const struct pmbus_sensor_attr power_attributes[] = {
.paged = true,
.func = PMBUS_HAVE_POUT,
.sfunc = PMBUS_HAVE_STATUS_IOUT,
- .sbase = PB_STATUS_IOUT_BASE,
+ .sreg = PMBUS_STATUS_IOUT,
.limit = pout_limit_attrs,
.nlimit = ARRAY_SIZE(pout_limit_attrs),
}
@@ -1677,7 +1986,10 @@ static const struct pmbus_limit_attr temp_limit_attrs[] = {
}, {
.reg = PMBUS_VIRT_RESET_TEMP_HISTORY,
.attr = "reset_history",
- }
+ }, {
+ .reg = PMBUS_MFR_MAX_TEMP_1,
+ .attr = "rated_max",
+ },
};
static const struct pmbus_limit_attr temp_limit_attrs2[] = {
@@ -1715,7 +2027,10 @@ static const struct pmbus_limit_attr temp_limit_attrs2[] = {
}, {
.reg = PMBUS_VIRT_RESET_TEMP2_HISTORY,
.attr = "reset_history",
- }
+ }, {
+ .reg = PMBUS_MFR_MAX_TEMP_2,
+ .attr = "rated_max",
+ },
};
static const struct pmbus_limit_attr temp_limit_attrs3[] = {
@@ -1741,7 +2056,10 @@ static const struct pmbus_limit_attr temp_limit_attrs3[] = {
.attr = "crit",
.alarm = "crit_alarm",
.sbit = PB_TEMP_OT_FAULT,
- }
+ }, {
+ .reg = PMBUS_MFR_MAX_TEMP_3,
+ .attr = "rated_max",
+ },
};
static const struct pmbus_sensor_attr temp_attributes[] = {
@@ -1753,7 +2071,7 @@ static const struct pmbus_sensor_attr temp_attributes[] = {
.compare = true,
.func = PMBUS_HAVE_TEMP,
.sfunc = PMBUS_HAVE_STATUS_TEMP,
- .sbase = PB_STATUS_TEMP_BASE,
+ .sreg = PMBUS_STATUS_TEMPERATURE,
.gbit = PB_STATUS_TEMPERATURE,
.limit = temp_limit_attrs,
.nlimit = ARRAY_SIZE(temp_limit_attrs),
@@ -1765,7 +2083,7 @@ static const struct pmbus_sensor_attr temp_attributes[] = {
.compare = true,
.func = PMBUS_HAVE_TEMP2,
.sfunc = PMBUS_HAVE_STATUS_TEMP,
- .sbase = PB_STATUS_TEMP_BASE,
+ .sreg = PMBUS_STATUS_TEMPERATURE,
.gbit = PB_STATUS_TEMPERATURE,
.limit = temp_limit_attrs2,
.nlimit = ARRAY_SIZE(temp_limit_attrs2),
@@ -1777,7 +2095,7 @@ static const struct pmbus_sensor_attr temp_attributes[] = {
.compare = true,
.func = PMBUS_HAVE_TEMP3,
.sfunc = PMBUS_HAVE_STATUS_TEMP,
- .sbase = PB_STATUS_TEMP_BASE,
+ .sreg = PMBUS_STATUS_TEMPERATURE,
.gbit = PB_STATUS_TEMPERATURE,
.limit = temp_limit_attrs3,
.nlimit = ARRAY_SIZE(temp_limit_attrs3),
@@ -1822,7 +2140,7 @@ static int pmbus_add_fan_ctrl(struct i2c_client *client,
struct pmbus_sensor *sensor;
sensor = pmbus_add_sensor(data, "fan", "target", index, page,
- PMBUS_VIRT_FAN_TARGET_1 + id, PSC_FAN,
+ 0xff, PMBUS_VIRT_FAN_TARGET_1 + id, PSC_FAN,
false, false, true);
if (!sensor)
@@ -1833,14 +2151,14 @@ static int pmbus_add_fan_ctrl(struct i2c_client *client,
return 0;
sensor = pmbus_add_sensor(data, "pwm", NULL, index, page,
- PMBUS_VIRT_PWM_1 + id, PSC_PWM,
+ 0xff, PMBUS_VIRT_PWM_1 + id, PSC_PWM,
false, false, true);
if (!sensor)
return -ENOMEM;
sensor = pmbus_add_sensor(data, "pwm", "enable", index, page,
- PMBUS_VIRT_PWM_ENABLE_1 + id, PSC_PWM,
+ 0xff, PMBUS_VIRT_PWM_ENABLE_1 + id, PSC_PWM,
true, false, false);
if (!sensor)
@@ -1882,7 +2200,7 @@ static int pmbus_add_fan_attributes(struct i2c_client *client,
continue;
if (pmbus_add_sensor(data, "fan", "input", index,
- page, pmbus_fan_registers[f],
+ page, 0xff, pmbus_fan_registers[f],
PSC_FAN, true, true, true) == NULL)
return -ENOMEM;
@@ -1902,19 +2220,19 @@ static int pmbus_add_fan_attributes(struct i2c_client *client,
if ((info->func[page] & pmbus_fan_status_flags[f]) &&
pmbus_check_byte_register(client,
page, pmbus_fan_status_registers[f])) {
- int base;
+ int reg;
if (f > 1) /* fan 3, 4 */
- base = PB_STATUS_FAN34_BASE + page;
+ reg = PMBUS_STATUS_FAN_34;
else
- base = PB_STATUS_FAN_BASE + page;
+ reg = PMBUS_STATUS_FAN_12;
ret = pmbus_add_boolean(data, "fan",
- "alarm", index, NULL, NULL, base,
+ "alarm", index, NULL, NULL, page, reg,
PB_FAN_FAN1_WARNING >> (f & 1));
if (ret)
return ret;
ret = pmbus_add_boolean(data, "fan",
- "fault", index, NULL, NULL, base,
+ "fault", index, NULL, NULL, page, reg,
PB_FAN_FAN1_FAULT >> (f & 1));
if (ret)
return ret;
@@ -1963,12 +2281,15 @@ static ssize_t pmbus_show_samples(struct device *dev,
int val;
struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_samples_reg *reg = to_samples_reg(devattr);
+ struct pmbus_data *data = i2c_get_clientdata(client);
- val = _pmbus_read_word_data(client, reg->page, reg->attr->reg);
+ mutex_lock(&data->update_lock);
+ val = _pmbus_read_word_data(client, reg->page, 0xff, reg->attr->reg);
+ mutex_unlock(&data->update_lock);
if (val < 0)
return val;
- return snprintf(buf, PAGE_SIZE, "%d\n", val);
+ return sysfs_emit(buf, "%d\n", val);
}
static ssize_t pmbus_set_samples(struct device *dev,
@@ -2073,6 +2394,111 @@ static int pmbus_find_attributes(struct i2c_client *client,
}
/*
+ * The pmbus_class_attr_map structure maps one sensor class to
+ * it's corresponding sensor attributes array.
+ */
+struct pmbus_class_attr_map {
+ enum pmbus_sensor_classes class;
+ int nattr;
+ const struct pmbus_sensor_attr *attr;
+};
+
+static const struct pmbus_class_attr_map class_attr_map[] = {
+ {
+ .class = PSC_VOLTAGE_IN,
+ .attr = voltage_attributes,
+ .nattr = ARRAY_SIZE(voltage_attributes),
+ }, {
+ .class = PSC_VOLTAGE_OUT,
+ .attr = voltage_attributes,
+ .nattr = ARRAY_SIZE(voltage_attributes),
+ }, {
+ .class = PSC_CURRENT_IN,
+ .attr = current_attributes,
+ .nattr = ARRAY_SIZE(current_attributes),
+ }, {
+ .class = PSC_CURRENT_OUT,
+ .attr = current_attributes,
+ .nattr = ARRAY_SIZE(current_attributes),
+ }, {
+ .class = PSC_POWER,
+ .attr = power_attributes,
+ .nattr = ARRAY_SIZE(power_attributes),
+ }, {
+ .class = PSC_TEMPERATURE,
+ .attr = temp_attributes,
+ .nattr = ARRAY_SIZE(temp_attributes),
+ }
+};
+
+/*
+ * Read the coefficients for direct mode.
+ */
+static int pmbus_read_coefficients(struct i2c_client *client,
+ struct pmbus_driver_info *info,
+ const struct pmbus_sensor_attr *attr)
+{
+ int rv;
+ union i2c_smbus_data data;
+ enum pmbus_sensor_classes class = attr->class;
+ s8 R;
+ s16 m, b;
+
+ data.block[0] = 2;
+ data.block[1] = attr->reg;
+ data.block[2] = 0x01;
+
+ rv = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
+ I2C_SMBUS_WRITE, PMBUS_COEFFICIENTS,
+ I2C_SMBUS_BLOCK_PROC_CALL, &data);
+
+ if (rv < 0)
+ return rv;
+
+ if (data.block[0] != 5)
+ return -EIO;
+
+ m = data.block[1] | (data.block[2] << 8);
+ b = data.block[3] | (data.block[4] << 8);
+ R = data.block[5];
+ info->m[class] = m;
+ info->b[class] = b;
+ info->R[class] = R;
+
+ return rv;
+}
+
+static int pmbus_init_coefficients(struct i2c_client *client,
+ struct pmbus_driver_info *info)
+{
+ int i, n, ret = -EINVAL;
+ const struct pmbus_class_attr_map *map;
+ const struct pmbus_sensor_attr *attr;
+
+ for (i = 0; i < ARRAY_SIZE(class_attr_map); i++) {
+ map = &class_attr_map[i];
+ if (info->format[map->class] != direct)
+ continue;
+ for (n = 0; n < map->nattr; n++) {
+ attr = &map->attr[n];
+ if (map->class != attr->class)
+ continue;
+ ret = pmbus_read_coefficients(client, info, attr);
+ if (ret >= 0)
+ break;
+ }
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "No coefficients found for sensor class %d\n",
+ map->class);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*
* Identify chip parameters.
* This function is called for all chips.
*/
@@ -2104,6 +2530,10 @@ static int pmbus_identify_common(struct i2c_client *client,
if (data->info->format[PSC_VOLTAGE_OUT] != direct)
return -ENODEV;
break;
+ case 3: /* ieee 754 half precision */
+ if (data->info->format[PSC_VOLTAGE_OUT] != ieee754)
+ return -ENODEV;
+ break;
default:
return -ENODEV;
}
@@ -2120,7 +2550,43 @@ static int pmbus_read_status_byte(struct i2c_client *client, int page)
static int pmbus_read_status_word(struct i2c_client *client, int page)
{
- return _pmbus_read_word_data(client, page, PMBUS_STATUS_WORD);
+ return _pmbus_read_word_data(client, page, 0xff, PMBUS_STATUS_WORD);
+}
+
+/* PEC attribute support */
+
+static ssize_t pec_show(struct device *dev, struct device_attribute *dummy,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return sysfs_emit(buf, "%d\n", !!(client->flags & I2C_CLIENT_PEC));
+}
+
+static ssize_t pec_store(struct device *dev, struct device_attribute *dummy,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ bool enable;
+ int err;
+
+ err = kstrtobool(buf, &enable);
+ if (err < 0)
+ return err;
+
+ if (enable)
+ client->flags |= I2C_CLIENT_PEC;
+ else
+ client->flags &= ~I2C_CLIENT_PEC;
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(pec);
+
+static void pmbus_remove_pec(void *dev)
+{
+ device_remove_file(dev, &dev_attr_pec);
}
static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
@@ -2130,6 +2596,21 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
int page, ret;
/*
+ * Figure out if PEC is enabled before accessing any other register.
+ * Make sure PEC is disabled, will be enabled later if needed.
+ */
+ client->flags &= ~I2C_CLIENT_PEC;
+
+ /* Enable PEC if the controller and bus supports it */
+ if (!(data->flags & PMBUS_NO_CAPABILITY)) {
+ ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY);
+ if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) {
+ if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC))
+ client->flags |= I2C_CLIENT_PEC;
+ }
+ }
+
+ /*
* Some PMBus chips don't support PMBUS_STATUS_WORD, so try
* to use PMBUS_STATUS_BYTE instead if that is the case.
* Bail out if both registers are not supported.
@@ -2147,19 +2628,16 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
data->has_status_word = true;
}
- /* Enable PEC if the controller supports it */
- ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY);
- if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK))
- client->flags |= I2C_CLIENT_PEC;
-
/*
* Check if the chip is write protected. If it is, we can not clear
* faults, and we should not try it. Also, in that case, writes into
* limit registers need to be disabled.
*/
- ret = i2c_smbus_read_byte_data(client, PMBUS_WRITE_PROTECT);
- if (ret > 0 && (ret & PB_WP_ANY))
- data->flags |= PMBUS_WRITE_PROTECTED | PMBUS_SKIP_STATUS_CHECK;
+ if (!(data->flags & PMBUS_NO_WRITE_PROTECT)) {
+ ret = i2c_smbus_read_byte_data(client, PMBUS_WRITE_PROTECT);
+ if (ret > 0 && (ret & PB_WP_ANY))
+ data->flags |= PMBUS_WRITE_PROTECTED | PMBUS_SKIP_STATUS_CHECK;
+ }
if (data->info->pages)
pmbus_clear_faults(client);
@@ -2186,6 +2664,31 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
return ret;
}
}
+
+ if (data->flags & PMBUS_USE_COEFFICIENTS_CMD) {
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BLOCK_PROC_CALL))
+ return -ENODEV;
+
+ ret = pmbus_init_coefficients(client, info);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (client->flags & I2C_CLIENT_PEC) {
+ /*
+ * If I2C_CLIENT_PEC is set here, both the I2C adapter and the
+ * chip support PEC. Add 'pec' attribute to client device to let
+ * the user control it.
+ */
+ ret = device_create_file(dev, &dev_attr_pec);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, pmbus_remove_pec, dev);
+ if (ret)
+ return ret;
+ }
+
return 0;
}
@@ -2194,10 +2697,14 @@ static int pmbus_regulator_is_enabled(struct regulator_dev *rdev)
{
struct device *dev = rdev_get_dev(rdev);
struct i2c_client *client = to_i2c_client(dev->parent);
+ struct pmbus_data *data = i2c_get_clientdata(client);
u8 page = rdev_get_id(rdev);
int ret;
- ret = pmbus_read_byte_data(client, page, PMBUS_OPERATION);
+ mutex_lock(&data->update_lock);
+ ret = _pmbus_read_byte_data(client, page, PMBUS_OPERATION);
+ mutex_unlock(&data->update_lock);
+
if (ret < 0)
return ret;
@@ -2208,11 +2715,17 @@ static int _pmbus_regulator_on_off(struct regulator_dev *rdev, bool enable)
{
struct device *dev = rdev_get_dev(rdev);
struct i2c_client *client = to_i2c_client(dev->parent);
+ struct pmbus_data *data = i2c_get_clientdata(client);
u8 page = rdev_get_id(rdev);
+ int ret;
- return pmbus_update_byte_data(client, page, PMBUS_OPERATION,
- PB_OPERATION_CONTROL_ON,
- enable ? PB_OPERATION_CONTROL_ON : 0);
+ mutex_lock(&data->update_lock);
+ ret = pmbus_update_byte_data(client, page, PMBUS_OPERATION,
+ PB_OPERATION_CONTROL_ON,
+ enable ? PB_OPERATION_CONTROL_ON : 0);
+ mutex_unlock(&data->update_lock);
+
+ return ret;
}
static int pmbus_regulator_enable(struct regulator_dev *rdev)
@@ -2225,12 +2738,264 @@ static int pmbus_regulator_disable(struct regulator_dev *rdev)
return _pmbus_regulator_on_off(rdev, 0);
}
+/* A PMBus status flag and the corresponding REGULATOR_ERROR_* flag */
+struct pmbus_regulator_status_assoc {
+ int pflag, rflag;
+};
+
+/* PMBus->regulator bit mappings for a PMBus status register */
+struct pmbus_regulator_status_category {
+ int func;
+ int reg;
+ const struct pmbus_regulator_status_assoc *bits; /* zero-terminated */
+};
+
+static const struct pmbus_regulator_status_category pmbus_regulator_flag_map[] = {
+ {
+ .func = PMBUS_HAVE_STATUS_VOUT,
+ .reg = PMBUS_STATUS_VOUT,
+ .bits = (const struct pmbus_regulator_status_assoc[]) {
+ { PB_VOLTAGE_UV_WARNING, REGULATOR_ERROR_UNDER_VOLTAGE_WARN },
+ { PB_VOLTAGE_UV_FAULT, REGULATOR_ERROR_UNDER_VOLTAGE },
+ { PB_VOLTAGE_OV_WARNING, REGULATOR_ERROR_OVER_VOLTAGE_WARN },
+ { PB_VOLTAGE_OV_FAULT, REGULATOR_ERROR_REGULATION_OUT },
+ { },
+ },
+ }, {
+ .func = PMBUS_HAVE_STATUS_IOUT,
+ .reg = PMBUS_STATUS_IOUT,
+ .bits = (const struct pmbus_regulator_status_assoc[]) {
+ { PB_IOUT_OC_WARNING, REGULATOR_ERROR_OVER_CURRENT_WARN },
+ { PB_IOUT_OC_FAULT, REGULATOR_ERROR_OVER_CURRENT },
+ { PB_IOUT_OC_LV_FAULT, REGULATOR_ERROR_OVER_CURRENT },
+ { },
+ },
+ }, {
+ .func = PMBUS_HAVE_STATUS_TEMP,
+ .reg = PMBUS_STATUS_TEMPERATURE,
+ .bits = (const struct pmbus_regulator_status_assoc[]) {
+ { PB_TEMP_OT_WARNING, REGULATOR_ERROR_OVER_TEMP_WARN },
+ { PB_TEMP_OT_FAULT, REGULATOR_ERROR_OVER_TEMP },
+ { },
+ },
+ },
+};
+
+static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned int *flags)
+{
+ int i, status;
+ const struct pmbus_regulator_status_category *cat;
+ const struct pmbus_regulator_status_assoc *bit;
+ struct device *dev = rdev_get_dev(rdev);
+ struct i2c_client *client = to_i2c_client(dev->parent);
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ u8 page = rdev_get_id(rdev);
+ int func = data->info->func[page];
+
+ *flags = 0;
+
+ mutex_lock(&data->update_lock);
+
+ for (i = 0; i < ARRAY_SIZE(pmbus_regulator_flag_map); i++) {
+ cat = &pmbus_regulator_flag_map[i];
+ if (!(func & cat->func))
+ continue;
+
+ status = _pmbus_read_byte_data(client, page, cat->reg);
+ if (status < 0) {
+ mutex_unlock(&data->update_lock);
+ return status;
+ }
+
+ for (bit = cat->bits; bit->pflag; bit++) {
+ if (status & bit->pflag)
+ *flags |= bit->rflag;
+ }
+ }
+
+ /*
+ * Map what bits of STATUS_{WORD,BYTE} we can to REGULATOR_ERROR_*
+ * bits. Some of the other bits are tempting (especially for cases
+ * where we don't have the relevant PMBUS_HAVE_STATUS_*
+ * functionality), but there's an unfortunate ambiguity in that
+ * they're defined as indicating a fault *or* a warning, so we can't
+ * easily determine whether to report REGULATOR_ERROR_<foo> or
+ * REGULATOR_ERROR_<foo>_WARN.
+ */
+ status = pmbus_get_status(client, page, PMBUS_STATUS_WORD);
+ mutex_unlock(&data->update_lock);
+ if (status < 0)
+ return status;
+
+ if (pmbus_regulator_is_enabled(rdev) && (status & PB_STATUS_OFF))
+ *flags |= REGULATOR_ERROR_FAIL;
+
+ /*
+ * Unlike most other status bits, PB_STATUS_{IOUT_OC,VOUT_OV} are
+ * defined strictly as fault indicators (not warnings).
+ */
+ if (status & PB_STATUS_IOUT_OC)
+ *flags |= REGULATOR_ERROR_OVER_CURRENT;
+ if (status & PB_STATUS_VOUT_OV)
+ *flags |= REGULATOR_ERROR_REGULATION_OUT;
+
+ /*
+ * If we haven't discovered any thermal faults or warnings via
+ * PMBUS_STATUS_TEMPERATURE, map PB_STATUS_TEMPERATURE to a warning as
+ * a (conservative) best-effort interpretation.
+ */
+ if (!(*flags & (REGULATOR_ERROR_OVER_TEMP | REGULATOR_ERROR_OVER_TEMP_WARN)) &&
+ (status & PB_STATUS_TEMPERATURE))
+ *flags |= REGULATOR_ERROR_OVER_TEMP_WARN;
+
+ return 0;
+}
+
+static int pmbus_regulator_get_low_margin(struct i2c_client *client, int page)
+{
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ struct pmbus_sensor s = {
+ .page = page,
+ .class = PSC_VOLTAGE_OUT,
+ .convert = true,
+ .data = -1,
+ };
+
+ if (data->vout_low[page] < 0) {
+ if (pmbus_check_word_register(client, page, PMBUS_MFR_VOUT_MIN))
+ s.data = _pmbus_read_word_data(client, page, 0xff,
+ PMBUS_MFR_VOUT_MIN);
+ if (s.data < 0) {
+ s.data = _pmbus_read_word_data(client, page, 0xff,
+ PMBUS_VOUT_MARGIN_LOW);
+ if (s.data < 0)
+ return s.data;
+ }
+ data->vout_low[page] = pmbus_reg2data(data, &s);
+ }
+
+ return data->vout_low[page];
+}
+
+static int pmbus_regulator_get_high_margin(struct i2c_client *client, int page)
+{
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ struct pmbus_sensor s = {
+ .page = page,
+ .class = PSC_VOLTAGE_OUT,
+ .convert = true,
+ .data = -1,
+ };
+
+ if (data->vout_high[page] < 0) {
+ if (pmbus_check_word_register(client, page, PMBUS_MFR_VOUT_MAX))
+ s.data = _pmbus_read_word_data(client, page, 0xff,
+ PMBUS_MFR_VOUT_MAX);
+ if (s.data < 0) {
+ s.data = _pmbus_read_word_data(client, page, 0xff,
+ PMBUS_VOUT_MARGIN_HIGH);
+ if (s.data < 0)
+ return s.data;
+ }
+ data->vout_high[page] = pmbus_reg2data(data, &s);
+ }
+
+ return data->vout_high[page];
+}
+
+static int pmbus_regulator_get_voltage(struct regulator_dev *rdev)
+{
+ struct device *dev = rdev_get_dev(rdev);
+ struct i2c_client *client = to_i2c_client(dev->parent);
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ struct pmbus_sensor s = {
+ .page = rdev_get_id(rdev),
+ .class = PSC_VOLTAGE_OUT,
+ .convert = true,
+ };
+
+ s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT);
+ if (s.data < 0)
+ return s.data;
+
+ return (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */
+}
+
+static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
+ int max_uv, unsigned int *selector)
+{
+ struct device *dev = rdev_get_dev(rdev);
+ struct i2c_client *client = to_i2c_client(dev->parent);
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ struct pmbus_sensor s = {
+ .page = rdev_get_id(rdev),
+ .class = PSC_VOLTAGE_OUT,
+ .convert = true,
+ .data = -1,
+ };
+ int val = DIV_ROUND_CLOSEST(min_uv, 1000); /* convert to mV */
+ int low, high;
+
+ *selector = 0;
+
+ low = pmbus_regulator_get_low_margin(client, s.page);
+ if (low < 0)
+ return low;
+
+ high = pmbus_regulator_get_high_margin(client, s.page);
+ if (high < 0)
+ return high;
+
+ /* Make sure we are within margins */
+ if (low > val)
+ val = low;
+ if (high < val)
+ val = high;
+
+ val = pmbus_data2reg(data, &s, val);
+
+ return _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val);
+}
+
+static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ struct device *dev = rdev_get_dev(rdev);
+ struct i2c_client *client = to_i2c_client(dev->parent);
+ int val, low, high;
+
+ if (selector >= rdev->desc->n_voltages ||
+ selector < rdev->desc->linear_min_sel)
+ return -EINVAL;
+
+ selector -= rdev->desc->linear_min_sel;
+ val = DIV_ROUND_CLOSEST(rdev->desc->min_uV +
+ (rdev->desc->uV_step * selector), 1000); /* convert to mV */
+
+ low = pmbus_regulator_get_low_margin(client, rdev_get_id(rdev));
+ if (low < 0)
+ return low;
+
+ high = pmbus_regulator_get_high_margin(client, rdev_get_id(rdev));
+ if (high < 0)
+ return high;
+
+ if (val >= low && val <= high)
+ return val * 1000; /* unit is uV */
+
+ return 0;
+}
+
const struct regulator_ops pmbus_regulator_ops = {
.enable = pmbus_regulator_enable,
.disable = pmbus_regulator_disable,
.is_enabled = pmbus_regulator_is_enabled,
+ .get_error_flags = pmbus_regulator_get_error_flags,
+ .get_voltage = pmbus_regulator_get_voltage,
+ .set_voltage = pmbus_regulator_set_voltage,
+ .list_voltage = pmbus_regulator_list_voltage,
};
-EXPORT_SYMBOL_GPL(pmbus_regulator_ops);
+EXPORT_SYMBOL_NS_GPL(pmbus_regulator_ops, PMBUS);
static int pmbus_regulator_register(struct pmbus_data *data)
{
@@ -2251,11 +3016,10 @@ static int pmbus_regulator_register(struct pmbus_data *data)
rdev = devm_regulator_register(dev, &info->reg_desc[i],
&config);
- if (IS_ERR(rdev)) {
- dev_err(dev, "Failed to register %s regulator\n",
- info->reg_desc[i].name);
- return PTR_ERR(rdev);
- }
+ if (IS_ERR(rdev))
+ return dev_err_probe(dev, PTR_ERR(rdev),
+ "Failed to register %s regulator\n",
+ info->reg_desc[i].name);
}
return 0;
@@ -2303,6 +3067,41 @@ static int pmbus_debugfs_get_status(void *data, u64 *val)
DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops_status, pmbus_debugfs_get_status,
NULL, "0x%04llx\n");
+static ssize_t pmbus_debugfs_mfr_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int rc;
+ struct pmbus_debugfs_entry *entry = file->private_data;
+ char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 };
+
+ rc = pmbus_read_block_data(entry->client, entry->page, entry->reg,
+ data);
+ if (rc < 0)
+ return rc;
+
+ /* Add newline at the end of a read data */
+ data[rc] = '\n';
+
+ /* Include newline into the length */
+ rc += 1;
+
+ return simple_read_from_buffer(buf, count, ppos, data, rc);
+}
+
+static const struct file_operations pmbus_debugfs_ops_mfr = {
+ .llseek = noop_llseek,
+ .read = pmbus_debugfs_mfr_read,
+ .write = NULL,
+ .open = simple_open,
+};
+
+static void pmbus_remove_debugfs(void *data)
+{
+ struct dentry *entry = data;
+
+ debugfs_remove_recursive(entry);
+}
+
static int pmbus_init_debugfs(struct i2c_client *client,
struct pmbus_data *data)
{
@@ -2324,13 +3123,80 @@ static int pmbus_init_debugfs(struct i2c_client *client,
return -ENODEV;
}
- /* Allocate the max possible entries we need. */
+ /*
+ * Allocate the max possible entries we need.
+ * 6 entries device-specific
+ * 10 entries page-specific
+ */
entries = devm_kcalloc(data->dev,
- data->info->pages * 10, sizeof(*entries),
+ 6 + data->info->pages * 10, sizeof(*entries),
GFP_KERNEL);
if (!entries)
return -ENOMEM;
+ /*
+ * Add device-specific entries.
+ * Please note that the PMBUS standard allows all registers to be
+ * page-specific.
+ * To reduce the number of debugfs entries for devices with many pages
+ * assume that values of the following registers are the same for all
+ * pages and report values only for page 0.
+ */
+ if (pmbus_check_block_register(client, 0, PMBUS_MFR_ID)) {
+ entries[idx].client = client;
+ entries[idx].page = 0;
+ entries[idx].reg = PMBUS_MFR_ID;
+ debugfs_create_file("mfr_id", 0444, data->debugfs,
+ &entries[idx++],
+ &pmbus_debugfs_ops_mfr);
+ }
+
+ if (pmbus_check_block_register(client, 0, PMBUS_MFR_MODEL)) {
+ entries[idx].client = client;
+ entries[idx].page = 0;
+ entries[idx].reg = PMBUS_MFR_MODEL;
+ debugfs_create_file("mfr_model", 0444, data->debugfs,
+ &entries[idx++],
+ &pmbus_debugfs_ops_mfr);
+ }
+
+ if (pmbus_check_block_register(client, 0, PMBUS_MFR_REVISION)) {
+ entries[idx].client = client;
+ entries[idx].page = 0;
+ entries[idx].reg = PMBUS_MFR_REVISION;
+ debugfs_create_file("mfr_revision", 0444, data->debugfs,
+ &entries[idx++],
+ &pmbus_debugfs_ops_mfr);
+ }
+
+ if (pmbus_check_block_register(client, 0, PMBUS_MFR_LOCATION)) {
+ entries[idx].client = client;
+ entries[idx].page = 0;
+ entries[idx].reg = PMBUS_MFR_LOCATION;
+ debugfs_create_file("mfr_location", 0444, data->debugfs,
+ &entries[idx++],
+ &pmbus_debugfs_ops_mfr);
+ }
+
+ if (pmbus_check_block_register(client, 0, PMBUS_MFR_DATE)) {
+ entries[idx].client = client;
+ entries[idx].page = 0;
+ entries[idx].reg = PMBUS_MFR_DATE;
+ debugfs_create_file("mfr_date", 0444, data->debugfs,
+ &entries[idx++],
+ &pmbus_debugfs_ops_mfr);
+ }
+
+ if (pmbus_check_block_register(client, 0, PMBUS_MFR_SERIAL)) {
+ entries[idx].client = client;
+ entries[idx].page = 0;
+ entries[idx].reg = PMBUS_MFR_SERIAL;
+ debugfs_create_file("mfr_serial", 0444, data->debugfs,
+ &entries[idx++],
+ &pmbus_debugfs_ops_mfr);
+ }
+
+ /* Add page specific entries */
for (i = 0; i < data->info->pages; ++i) {
/* Check accessibility of status register if it's not page 0 */
if (!i || pmbus_check_status_register(client, i)) {
@@ -2435,7 +3301,8 @@ static int pmbus_init_debugfs(struct i2c_client *client,
}
}
- return 0;
+ return devm_add_action_or_reset(data->dev,
+ pmbus_remove_debugfs, data->debugfs);
}
#else
static int pmbus_init_debugfs(struct i2c_client *client,
@@ -2445,14 +3312,15 @@ static int pmbus_init_debugfs(struct i2c_client *client,
}
#endif /* IS_ENABLED(CONFIG_DEBUG_FS) */
-int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
- struct pmbus_driver_info *info)
+int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info)
{
struct device *dev = &client->dev;
const struct pmbus_platform_data *pdata = dev_get_platdata(dev);
struct pmbus_data *data;
size_t groups_num = 0;
int ret;
+ int i;
+ char *name;
if (!info)
return -ENODEV;
@@ -2482,6 +3350,13 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
if (pdata)
data->flags = pdata->flags;
data->info = info;
+ data->currpage = -1;
+ data->currphase = -1;
+
+ for (i = 0; i < ARRAY_SIZE(data->vout_low); i++) {
+ data->vout_low[i] = -1;
+ data->vout_high[i] = -1;
+ }
ret = pmbus_init_common(client, data, info);
if (ret < 0)
@@ -2489,7 +3364,7 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
ret = pmbus_find_attributes(client, data);
if (ret)
- goto out_kfree;
+ return ret;
/*
* If there are no attributes, something is wrong.
@@ -2497,49 +3372,34 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
*/
if (!data->num_attributes) {
dev_err(dev, "No attributes found\n");
- ret = -ENODEV;
- goto out_kfree;
+ return -ENODEV;
}
+ name = devm_kstrdup(dev, client->name, GFP_KERNEL);
+ if (!name)
+ return -ENOMEM;
+ strreplace(name, '-', '_');
+
data->groups[0] = &data->group;
memcpy(data->groups + 1, info->groups, sizeof(void *) * groups_num);
- data->hwmon_dev = hwmon_device_register_with_groups(dev, client->name,
- data, data->groups);
+ data->hwmon_dev = devm_hwmon_device_register_with_groups(dev,
+ name, data, data->groups);
if (IS_ERR(data->hwmon_dev)) {
- ret = PTR_ERR(data->hwmon_dev);
dev_err(dev, "Failed to register hwmon device\n");
- goto out_kfree;
+ return PTR_ERR(data->hwmon_dev);
}
ret = pmbus_regulator_register(data);
if (ret)
- goto out_unregister;
+ return ret;
ret = pmbus_init_debugfs(client, data);
if (ret)
dev_warn(dev, "Failed to register debugfs\n");
return 0;
-
-out_unregister:
- hwmon_device_unregister(data->hwmon_dev);
-out_kfree:
- kfree(data->group.attrs);
- return ret;
-}
-EXPORT_SYMBOL_GPL(pmbus_do_probe);
-
-int pmbus_do_remove(struct i2c_client *client)
-{
- struct pmbus_data *data = i2c_get_clientdata(client);
-
- debugfs_remove_recursive(data->debugfs);
-
- hwmon_device_unregister(data->hwmon_dev);
- kfree(data->group.attrs);
- return 0;
}
-EXPORT_SYMBOL_GPL(pmbus_do_remove);
+EXPORT_SYMBOL_NS_GPL(pmbus_do_probe, PMBUS);
struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client)
{
@@ -2547,7 +3407,7 @@ struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client)
return data->debugfs;
}
-EXPORT_SYMBOL_GPL(pmbus_get_debugfs_dir);
+EXPORT_SYMBOL_NS_GPL(pmbus_get_debugfs_dir, PMBUS);
static int __init pmbus_core_init(void)
{