aboutsummaryrefslogtreecommitdiff
path: root/drivers/hwmon/pmbus/zl6100.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hwmon/pmbus/zl6100.c')
-rw-r--r--drivers/hwmon/pmbus/zl6100.c240
1 files changed, 187 insertions, 53 deletions
diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c
index 48c7b4a716a..81964412125 100644
--- a/drivers/hwmon/pmbus/zl6100.c
+++ b/drivers/hwmon/pmbus/zl6100.c
@@ -2,6 +2,7 @@
* Hardware monitoring driver for ZL6100 and compatibles
*
* Copyright (c) 2011 Ericsson AB.
+ * Copyright (c) 2012 Guenter Roeck
*
* 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
@@ -28,11 +29,13 @@
#include <linux/delay.h>
#include "pmbus.h"
-enum chips { zl2004, zl2005, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105 };
+enum chips { zl2004, zl2005, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105,
+ zl9101, zl9117 };
struct zl6100_data {
int id;
ktime_t access; /* chip access time */
+ int delay; /* Delay between chip accesses in uS */
struct pmbus_driver_info info;
};
@@ -43,19 +46,94 @@ struct zl6100_data {
#define ZL6100_MFR_XTEMP_ENABLE (1 << 7)
+#define MFR_VMON_OV_FAULT_LIMIT 0xf5
+#define MFR_VMON_UV_FAULT_LIMIT 0xf6
+#define MFR_READ_VMON 0xf7
+
+#define VMON_UV_WARNING (1 << 5)
+#define VMON_OV_WARNING (1 << 4)
+#define VMON_UV_FAULT (1 << 1)
+#define VMON_OV_FAULT (1 << 0)
+
#define ZL6100_WAIT_TIME 1000 /* uS */
static ushort delay = ZL6100_WAIT_TIME;
module_param(delay, ushort, 0644);
MODULE_PARM_DESC(delay, "Delay between chip accesses in uS");
+/* Convert linear sensor value to milli-units */
+static long zl6100_l2d(s16 l)
+{
+ s16 exponent;
+ s32 mantissa;
+ long val;
+
+ exponent = l >> 11;
+ mantissa = ((s16)((l & 0x7ff) << 5)) >> 5;
+
+ val = mantissa;
+
+ /* scale result to milli-units */
+ val = val * 1000L;
+
+ if (exponent >= 0)
+ val <<= exponent;
+ else
+ val >>= -exponent;
+
+ return val;
+}
+
+#define MAX_MANTISSA (1023 * 1000)
+#define MIN_MANTISSA (511 * 1000)
+
+static u16 zl6100_d2l(long val)
+{
+ s16 exponent = 0, mantissa;
+ bool negative = false;
+
+ /* simple case */
+ if (val == 0)
+ return 0;
+
+ if (val < 0) {
+ negative = true;
+ val = -val;
+ }
+
+ /* 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);
+}
+
/* Some chips need a delay between accesses */
static inline void zl6100_wait(const struct zl6100_data *data)
{
- if (delay) {
+ if (data->delay) {
s64 delta = ktime_us_delta(ktime_get(), data->access);
- if (delta < delay)
- udelay(delay - delta);
+ if (delta < data->delay)
+ udelay(data->delay - delta);
}
}
@@ -63,9 +141,9 @@ static int zl6100_read_word_data(struct i2c_client *client, int page, int reg)
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct zl6100_data *data = to_zl6100_data(info);
- int ret;
+ int ret, vreg;
- if (page || reg >= PMBUS_VIRT_BASE)
+ if (page > 0)
return -ENXIO;
if (data->id == zl2005) {
@@ -81,9 +159,39 @@ static int zl6100_read_word_data(struct i2c_client *client, int page, int reg)
}
}
+ switch (reg) {
+ case PMBUS_VIRT_READ_VMON:
+ vreg = MFR_READ_VMON;
+ break;
+ case PMBUS_VIRT_VMON_OV_WARN_LIMIT:
+ case PMBUS_VIRT_VMON_OV_FAULT_LIMIT:
+ vreg = MFR_VMON_OV_FAULT_LIMIT;
+ break;
+ case PMBUS_VIRT_VMON_UV_WARN_LIMIT:
+ case PMBUS_VIRT_VMON_UV_FAULT_LIMIT:
+ vreg = MFR_VMON_UV_FAULT_LIMIT;
+ break;
+ default:
+ if (reg >= PMBUS_VIRT_BASE)
+ return -ENXIO;
+ vreg = reg;
+ break;
+ }
+
zl6100_wait(data);
- ret = pmbus_read_word_data(client, page, reg);
+ ret = pmbus_read_word_data(client, page, vreg);
data->access = ktime_get();
+ if (ret < 0)
+ return ret;
+
+ switch (reg) {
+ case PMBUS_VIRT_VMON_OV_WARN_LIMIT:
+ ret = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(ret) * 9, 10));
+ break;
+ case PMBUS_VIRT_VMON_UV_WARN_LIMIT:
+ ret = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(ret) * 11, 10));
+ break;
+ }
return ret;
}
@@ -92,13 +200,35 @@ static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg)
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct zl6100_data *data = to_zl6100_data(info);
- int ret;
+ int ret, status;
if (page > 0)
return -ENXIO;
zl6100_wait(data);
- ret = pmbus_read_byte_data(client, page, reg);
+
+ switch (reg) {
+ case PMBUS_VIRT_STATUS_VMON:
+ ret = pmbus_read_byte_data(client, 0,
+ PMBUS_STATUS_MFR_SPECIFIC);
+ if (ret < 0)
+ break;
+
+ status = 0;
+ if (ret & VMON_UV_WARNING)
+ status |= PB_VOLTAGE_UV_WARNING;
+ if (ret & VMON_OV_WARNING)
+ status |= PB_VOLTAGE_OV_WARNING;
+ if (ret & VMON_UV_FAULT)
+ status |= PB_VOLTAGE_UV_FAULT;
+ if (ret & VMON_OV_FAULT)
+ status |= PB_VOLTAGE_OV_FAULT;
+ ret = status;
+ break;
+ default:
+ ret = pmbus_read_byte_data(client, page, reg);
+ break;
+ }
data->access = ktime_get();
return ret;
@@ -109,13 +239,38 @@ static int zl6100_write_word_data(struct i2c_client *client, int page, int reg,
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct zl6100_data *data = to_zl6100_data(info);
- int ret;
+ int ret, vreg;
- if (page || reg >= PMBUS_VIRT_BASE)
+ if (page > 0)
return -ENXIO;
+ switch (reg) {
+ case PMBUS_VIRT_VMON_OV_WARN_LIMIT:
+ word = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(word) * 10, 9));
+ vreg = MFR_VMON_OV_FAULT_LIMIT;
+ pmbus_clear_cache(client);
+ break;
+ case PMBUS_VIRT_VMON_OV_FAULT_LIMIT:
+ vreg = MFR_VMON_OV_FAULT_LIMIT;
+ pmbus_clear_cache(client);
+ break;
+ case PMBUS_VIRT_VMON_UV_WARN_LIMIT:
+ word = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(word) * 10, 11));
+ vreg = MFR_VMON_UV_FAULT_LIMIT;
+ pmbus_clear_cache(client);
+ break;
+ case PMBUS_VIRT_VMON_UV_FAULT_LIMIT:
+ vreg = MFR_VMON_UV_FAULT_LIMIT;
+ pmbus_clear_cache(client);
+ break;
+ default:
+ if (reg >= PMBUS_VIRT_BASE)
+ return -ENXIO;
+ vreg = reg;
+ }
+
zl6100_wait(data);
- ret = pmbus_write_word_data(client, page, reg, word);
+ ret = pmbus_write_word_data(client, page, vreg, word);
data->access = ktime_get();
return ret;
@@ -151,6 +306,8 @@ static const struct i2c_device_id zl6100_id[] = {
{"zl2106", zl2106},
{"zl6100", zl6100},
{"zl6105", zl6105},
+ {"zl9101", zl9101},
+ {"zl9117", zl9117},
{ }
};
MODULE_DEVICE_TABLE(i2c, zl6100_id);
@@ -192,23 +349,19 @@ static int zl6100_probe(struct i2c_client *client,
"Device mismatch: Configured %s, detected %s\n",
id->name, mid->name);
- data = kzalloc(sizeof(struct zl6100_data), GFP_KERNEL);
+ data = devm_kzalloc(&client->dev, sizeof(struct zl6100_data),
+ GFP_KERNEL);
if (!data)
return -ENOMEM;
data->id = mid->driver_data;
/*
- * ZL2005, ZL2008, ZL2105, and ZL6100 are known to require a wait time
- * between I2C accesses. ZL2004 and ZL6105 are known to be safe.
- * Other chips have not yet been tested.
- *
- * Only clear the wait time for chips known to be safe. The wait time
- * can be cleared later for additional chips if tests show that it
- * is not needed (in other words, better be safe than sorry).
+ * According to information from the chip vendor, all currently
+ * supported chips are known to require a wait time between I2C
+ * accesses.
*/
- if (data->id == zl2004 || data->id == zl6105)
- delay = 0;
+ data->delay = delay;
/*
* Since there was a direct I2C device access above, wait before
@@ -225,9 +378,17 @@ static int zl6100_probe(struct i2c_client *client,
| PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
| PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
+ /*
+ * ZL2004, ZL9101M, and ZL9117M support monitoring an extra voltage
+ * (VMON for ZL2004, VDRV for ZL9101M and ZL9117M). Report it as vmon.
+ */
+ if (data->id == zl2004 || data->id == zl9101 || data->id == zl9117)
+ info->func[0] |= PMBUS_HAVE_VMON | PMBUS_HAVE_STATUS_VMON;
+
ret = i2c_smbus_read_word_data(client, ZL6100_MFR_CONFIG);
if (ret < 0)
- goto err_mem;
+ return ret;
+
if (ret & ZL6100_MFR_XTEMP_ENABLE)
info->func[0] |= PMBUS_HAVE_TEMP2;
@@ -239,24 +400,7 @@ static int zl6100_probe(struct i2c_client *client,
info->write_word_data = zl6100_write_word_data;
info->write_byte = zl6100_write_byte;
- ret = pmbus_do_probe(client, mid, info);
- if (ret)
- goto err_mem;
- return 0;
-
-err_mem:
- kfree(data);
- return ret;
-}
-
-static int zl6100_remove(struct i2c_client *client)
-{
- const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
- const struct zl6100_data *data = to_zl6100_data(info);
-
- pmbus_do_remove(client);
- kfree(data);
- return 0;
+ return pmbus_do_probe(client, mid, info);
}
static struct i2c_driver zl6100_driver = {
@@ -264,22 +408,12 @@ static struct i2c_driver zl6100_driver = {
.name = "zl6100",
},
.probe = zl6100_probe,
- .remove = zl6100_remove,
+ .remove = pmbus_do_remove,
.id_table = zl6100_id,
};
-static int __init zl6100_init(void)
-{
- return i2c_add_driver(&zl6100_driver);
-}
-
-static void __exit zl6100_exit(void)
-{
- i2c_del_driver(&zl6100_driver);
-}
+module_i2c_driver(zl6100_driver);
MODULE_AUTHOR("Guenter Roeck");
MODULE_DESCRIPTION("PMBus driver for ZL6100 and compatibles");
MODULE_LICENSE("GPL");
-module_init(zl6100_init);
-module_exit(zl6100_exit);