|
@@ -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
|
|
@@ -45,12 +46,87 @@ 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)
|
|
|
{
|
|
@@ -65,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) {
|
|
@@ -83,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;
|
|
|
}
|
|
@@ -94,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;
|
|
@@ -111,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;
|
|
@@ -225,6 +378,13 @@ 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)
|
|
|
return ret;
|