aboutsummaryrefslogtreecommitdiff
path: root/drivers/hwmon/pmbus
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hwmon/pmbus')
-rw-r--r--drivers/hwmon/pmbus/Kconfig9
-rw-r--r--drivers/hwmon/pmbus/Makefile1
-rw-r--r--drivers/hwmon/pmbus/lt7182s.c195
-rw-r--r--drivers/hwmon/pmbus/ltc2978.c44
-rw-r--r--drivers/hwmon/pmbus/pmbus.h10
-rw-r--r--drivers/hwmon/pmbus/pmbus_core.c446
6 files changed, 646 insertions, 59 deletions
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index dfae76db65ae..951e4a9ff2d6 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -181,6 +181,15 @@ config SENSORS_LM25066_REGULATOR
If you say yes here you get regulator support for National
Semiconductor LM25066, LM5064, and LM5066.
+config SENSORS_LT7182S
+ tristate "Analog Devices LT7182S"
+ help
+ If you say yes here you get hardware monitoring support for Analog
+ Devices LT7182S.
+
+ This driver can also be built as a module. If so, the module will
+ be called lt7182s.
+
config SENSORS_LTC2978
tristate "Linear Technologies LTC2978 and compatibles"
help
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index 4678fba5012c..e2fe86f98965 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_SENSORS_IR38064) += ir38064.o
obj-$(CONFIG_SENSORS_IRPS5401) += irps5401.o
obj-$(CONFIG_SENSORS_ISL68137) += isl68137.o
obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
+obj-$(CONFIG_SENSORS_LT7182S) += lt7182s.o
obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o
obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o
obj-$(CONFIG_SENSORS_MAX15301) += max15301.o
diff --git a/drivers/hwmon/pmbus/lt7182s.c b/drivers/hwmon/pmbus/lt7182s.c
new file mode 100644
index 000000000000..4cfe476fc92d
--- /dev/null
+++ b/drivers/hwmon/pmbus/lt7182s.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Hardware monitoring driver for Analog Devices LT7182S
+ *
+ * Copyright (c) 2022 Guenter Roeck
+ *
+ */
+
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include "pmbus.h"
+
+#define LT7182S_NUM_PAGES 2
+
+#define MFR_READ_EXTVCC 0xcd
+#define MFR_READ_ITH 0xce
+#define MFR_CONFIG_ALL_LT7182S 0xd1
+#define MFR_IOUT_PEAK 0xd7
+#define MFR_ADC_CONTROL_LT7182S 0xd8
+
+#define MFR_DEBUG_TELEMETRY BIT(0)
+
+#define MFR_VOUT_PEAK 0xdd
+#define MFR_VIN_PEAK 0xde
+#define MFR_TEMPERATURE_1_PEAK 0xdf
+#define MFR_CLEAR_PEAKS 0xe3
+
+#define MFR_CONFIG_IEEE BIT(8)
+
+static int lt7182s_read_word_data(struct i2c_client *client, int page, int phase, int reg)
+{
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VIRT_READ_VMON:
+ if (page == 0 || page == 1)
+ ret = pmbus_read_word_data(client, page, phase, MFR_READ_ITH);
+ else
+ ret = pmbus_read_word_data(client, 0, phase, MFR_READ_EXTVCC);
+ break;
+ case PMBUS_VIRT_READ_IOUT_MAX:
+ ret = pmbus_read_word_data(client, page, phase, MFR_IOUT_PEAK);
+ break;
+ case PMBUS_VIRT_READ_VOUT_MAX:
+ ret = pmbus_read_word_data(client, page, phase, MFR_VOUT_PEAK);
+ break;
+ case PMBUS_VIRT_READ_VIN_MAX:
+ ret = pmbus_read_word_data(client, page, phase, MFR_VIN_PEAK);
+ break;
+ case PMBUS_VIRT_READ_TEMP_MAX:
+ ret = pmbus_read_word_data(client, page, phase, MFR_TEMPERATURE_1_PEAK);
+ break;
+ case PMBUS_VIRT_RESET_VIN_HISTORY:
+ ret = (page == 0) ? 0 : -ENODATA;
+ break;
+ default:
+ ret = -ENODATA;
+ break;
+ }
+ return ret;
+}
+
+static int lt7182s_write_word_data(struct i2c_client *client, int page, int reg, u16 word)
+{
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VIRT_RESET_VIN_HISTORY:
+ ret = pmbus_write_byte(client, 0, MFR_CLEAR_PEAKS);
+ break;
+ default:
+ ret = -ENODATA;
+ break;
+ }
+ return ret;
+}
+
+static struct pmbus_driver_info lt7182s_info = {
+ .pages = LT7182S_NUM_PAGES,
+ .format[PSC_VOLTAGE_IN] = linear,
+ .format[PSC_VOLTAGE_OUT] = linear,
+ .format[PSC_CURRENT_IN] = linear,
+ .format[PSC_CURRENT_OUT] = linear,
+ .format[PSC_TEMPERATURE] = linear,
+ .format[PSC_POWER] = linear,
+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT |
+ PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT |
+ PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
+ .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT |
+ PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT |
+ PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT |
+ PMBUS_HAVE_STATUS_INPUT,
+ .read_word_data = lt7182s_read_word_data,
+ .write_word_data = lt7182s_write_word_data,
+};
+
+static int lt7182s_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct pmbus_driver_info *info;
+ u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_BYTE_DATA |
+ I2C_FUNC_SMBUS_READ_WORD_DATA |
+ I2C_FUNC_SMBUS_READ_BLOCK_DATA))
+ return -ENODEV;
+
+ ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read PMBUS_MFR_ID\n");
+ return ret;
+ }
+ if (ret != 3 || strncmp(buf, "ADI", 3)) {
+ buf[ret] = '\0';
+ dev_err(dev, "Manufacturer '%s' not supported\n", buf);
+ return -ENODEV;
+ }
+
+ ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read PMBUS_MFR_MODEL\n");
+ return ret;
+ }
+ if (ret != 7 || strncmp(buf, "LT7182S", 7)) {
+ buf[ret] = '\0';
+ dev_err(dev, "Model '%s' not supported\n", buf);
+ return -ENODEV;
+ }
+
+ info = devm_kmemdup(dev, &lt7182s_info,
+ sizeof(struct pmbus_driver_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ /* Set data format to IEEE754 if configured */
+ ret = i2c_smbus_read_word_data(client, MFR_CONFIG_ALL_LT7182S);
+ if (ret < 0)
+ return ret;
+ if (ret & MFR_CONFIG_IEEE) {
+ info->format[PSC_VOLTAGE_IN] = ieee754;
+ info->format[PSC_VOLTAGE_OUT] = ieee754;
+ info->format[PSC_CURRENT_IN] = ieee754;
+ info->format[PSC_CURRENT_OUT] = ieee754;
+ info->format[PSC_TEMPERATURE] = ieee754;
+ info->format[PSC_POWER] = ieee754;
+ }
+
+ /* Enable VMON output if configured */
+ ret = i2c_smbus_read_byte_data(client, MFR_ADC_CONTROL_LT7182S);
+ if (ret < 0)
+ return ret;
+ if (ret & MFR_DEBUG_TELEMETRY) {
+ info->pages = 3;
+ info->func[0] |= PMBUS_HAVE_VMON;
+ info->func[1] |= PMBUS_HAVE_VMON;
+ info->func[2] = PMBUS_HAVE_VMON;
+ }
+
+ return pmbus_do_probe(client, info);
+}
+
+static const struct i2c_device_id lt7182s_id[] = {
+ { "lt7182s", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, lt7182s_id);
+
+static const struct of_device_id __maybe_unused lt7182s_of_match[] = {
+ { .compatible = "adi,lt7182s" },
+ {}
+};
+
+static struct i2c_driver lt7182s_driver = {
+ .driver = {
+ .name = "lt7182s",
+ .of_match_table = of_match_ptr(lt7182s_of_match),
+ },
+ .probe_new = lt7182s_probe,
+ .id_table = lt7182s_id,
+};
+
+module_i2c_driver(lt7182s_driver);
+
+MODULE_AUTHOR("Guenter Roeck <[email protected]>");
+MODULE_DESCRIPTION("PMBus driver for Analog Devices LT7182S");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PMBUS);
diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c
index 531aa674a928..6d2592731ba3 100644
--- a/drivers/hwmon/pmbus/ltc2978.c
+++ b/drivers/hwmon/pmbus/ltc2978.c
@@ -562,7 +562,24 @@ static const struct i2c_device_id ltc2978_id[] = {
MODULE_DEVICE_TABLE(i2c, ltc2978_id);
#if IS_ENABLED(CONFIG_SENSORS_LTC2978_REGULATOR)
+#define LTC2978_ADC_RES 0xFFFF
+#define LTC2978_N_ADC 122
+#define LTC2978_MAX_UV (LTC2978_ADC_RES * LTC2978_N_ADC)
+#define LTC2978_UV_STEP 1000
+#define LTC2978_N_VOLTAGES ((LTC2978_MAX_UV / LTC2978_UV_STEP) + 1)
+
static const struct regulator_desc ltc2978_reg_desc[] = {
+ PMBUS_REGULATOR_STEP("vout", 0, LTC2978_N_VOLTAGES, LTC2978_UV_STEP),
+ PMBUS_REGULATOR_STEP("vout", 1, LTC2978_N_VOLTAGES, LTC2978_UV_STEP),
+ PMBUS_REGULATOR_STEP("vout", 2, LTC2978_N_VOLTAGES, LTC2978_UV_STEP),
+ PMBUS_REGULATOR_STEP("vout", 3, LTC2978_N_VOLTAGES, LTC2978_UV_STEP),
+ PMBUS_REGULATOR_STEP("vout", 4, LTC2978_N_VOLTAGES, LTC2978_UV_STEP),
+ PMBUS_REGULATOR_STEP("vout", 5, LTC2978_N_VOLTAGES, LTC2978_UV_STEP),
+ PMBUS_REGULATOR_STEP("vout", 6, LTC2978_N_VOLTAGES, LTC2978_UV_STEP),
+ PMBUS_REGULATOR_STEP("vout", 7, LTC2978_N_VOLTAGES, LTC2978_UV_STEP),
+};
+
+static const struct regulator_desc ltc2978_reg_desc_default[] = {
PMBUS_REGULATOR("vout", 0),
PMBUS_REGULATOR("vout", 1),
PMBUS_REGULATOR("vout", 2),
@@ -839,10 +856,29 @@ static int ltc2978_probe(struct i2c_client *client)
#if IS_ENABLED(CONFIG_SENSORS_LTC2978_REGULATOR)
info->num_regulators = info->pages;
- info->reg_desc = ltc2978_reg_desc;
- if (info->num_regulators > ARRAY_SIZE(ltc2978_reg_desc)) {
- dev_err(&client->dev, "num_regulators too large!");
- info->num_regulators = ARRAY_SIZE(ltc2978_reg_desc);
+ switch (data->id) {
+ case ltc2972:
+ case ltc2974:
+ case ltc2975:
+ case ltc2977:
+ case ltc2978:
+ case ltc2979:
+ case ltc2980:
+ case ltm2987:
+ info->reg_desc = ltc2978_reg_desc;
+ if (info->num_regulators > ARRAY_SIZE(ltc2978_reg_desc)) {
+ dev_warn(&client->dev, "num_regulators too large!");
+ info->num_regulators = ARRAY_SIZE(ltc2978_reg_desc);
+ }
+ break;
+ default:
+ info->reg_desc = ltc2978_reg_desc_default;
+ if (info->num_regulators > ARRAY_SIZE(ltc2978_reg_desc_default)) {
+ dev_warn(&client->dev, "num_regulators too large!");
+ info->num_regulators =
+ ARRAY_SIZE(ltc2978_reg_desc_default);
+ }
+ break;
}
#endif
diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h
index c031a9700ace..7daaf0caf4d3 100644
--- a/drivers/hwmon/pmbus/pmbus.h
+++ b/drivers/hwmon/pmbus/pmbus.h
@@ -406,7 +406,7 @@ enum pmbus_sensor_classes {
#define PMBUS_PHASE_VIRTUAL BIT(30) /* Phases on this page are virtual */
#define PMBUS_PAGE_VIRTUAL BIT(31) /* Page is virtual */
-enum pmbus_data_format { linear = 0, direct, vid };
+enum pmbus_data_format { linear = 0, ieee754, direct, vid };
enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv };
struct pmbus_driver_info {
@@ -463,8 +463,8 @@ struct pmbus_driver_info {
extern const struct regulator_ops pmbus_regulator_ops;
-/* Macro for filling in array of struct regulator_desc */
-#define PMBUS_REGULATOR(_name, _id) \
+/* Macros for filling in array of struct regulator_desc */
+#define PMBUS_REGULATOR_STEP(_name, _id, _voltages, _step) \
[_id] = { \
.name = (_name # _id), \
.supply_name = "vin", \
@@ -474,8 +474,12 @@ extern const struct regulator_ops pmbus_regulator_ops;
.ops = &pmbus_regulator_ops, \
.type = REGULATOR_VOLTAGE, \
.owner = THIS_MODULE, \
+ .n_voltages = _voltages, \
+ .uV_step = _step, \
}
+#define PMBUS_REGULATOR(_name, _id) PMBUS_REGULATOR_STEP(_name, _id, 0, 0)
+
/* Function declarations */
void pmbus_clear_cache(struct i2c_client *client);
diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c
index 02912022853d..f10bac8860fc 100644
--- a/drivers/hwmon/pmbus/pmbus_core.c
+++ b/drivers/hwmon/pmbus/pmbus_core.c
@@ -104,6 +104,9 @@ struct pmbus_data {
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 {
@@ -441,6 +444,18 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg,
}
EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, PMBUS);
+static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg,
+ char *data_buf)
+{
+ int rv;
+
+ 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,
int reg)
{
@@ -578,6 +593,22 @@ bool pmbus_check_word_register(struct i2c_client *client, int page, int 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;
+}
+
const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client)
{
struct pmbus_data *data = i2c_get_clientdata(client);
@@ -612,6 +643,66 @@ static void pmbus_update_sensor_data(struct i2c_client *client, struct pmbus_sen
}
/*
+ * 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;
+ }
+
+ /* 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.
*/
@@ -741,6 +832,9 @@ static s64 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);
@@ -749,8 +843,72 @@ static s64 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, s64 val)
@@ -796,12 +954,12 @@ static u16 pmbus_data2reg_linear(struct pmbus_data *data,
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;
}
@@ -875,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);
@@ -2369,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;
}
@@ -2388,6 +2553,42 @@ static int pmbus_read_status_word(struct i2c_client *client, int page)
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,
struct pmbus_driver_info *info)
{
@@ -2474,6 +2675,20 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
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;
}
@@ -2636,6 +2851,58 @@ static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned
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]) {
+ 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]) {
+ 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);
@@ -2671,24 +2938,13 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
*selector = 0;
- if (pmbus_check_word_register(client, s.page, PMBUS_MFR_VOUT_MIN))
- s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_MFR_VOUT_MIN);
- if (s.data < 0) {
- s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_VOUT_MARGIN_LOW);
- if (s.data < 0)
- return s.data;
- }
- low = pmbus_reg2data(data, &s);
+ low = pmbus_regulator_get_low_margin(client, s.page);
+ if (low < 0)
+ return low;
- s.data = -1;
- if (pmbus_check_word_register(client, s.page, PMBUS_MFR_VOUT_MAX))
- s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_MFR_VOUT_MAX);
- if (s.data < 0) {
- s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_VOUT_MARGIN_HIGH);
- if (s.data < 0)
- return s.data;
- }
- high = pmbus_reg2data(data, &s);
+ high = pmbus_regulator_get_high_margin(client, s.page);
+ if (high < 0)
+ return high;
/* Make sure we are within margins */
if (low > val)
@@ -2701,6 +2957,35 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
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,
@@ -2708,6 +2993,7 @@ const struct regulator_ops pmbus_regulator_ops = {
.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_NS_GPL(pmbus_regulator_ops, PMBUS);
@@ -2782,41 +3068,33 @@ 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 int pmbus_debugfs_get_pec(void *data, u64 *val)
-{
- struct i2c_client *client = data;
-
- *val = !!(client->flags & I2C_CLIENT_PEC);
-
- return 0;
-}
-
-static int pmbus_debugfs_set_pec(void *data, u64 val)
+static ssize_t pmbus_debugfs_mfr_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
{
int rc;
- struct i2c_client *client = data;
-
- if (!val) {
- client->flags &= ~I2C_CLIENT_PEC;
- return 0;
- }
-
- if (val != 1)
- return -EINVAL;
+ struct pmbus_debugfs_entry *entry = file->private_data;
+ char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 };
- rc = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY);
+ rc = pmbus_read_block_data(entry->client, entry->page, entry->reg,
+ data);
if (rc < 0)
return rc;
- if (!(rc & PB_CAPABILITY_ERROR_CHECK))
- return -EOPNOTSUPP;
+ /* Add newline at the end of a read data */
+ data[rc] = '\n';
- client->flags |= I2C_CLIENT_PEC;
+ /* Include newline into the length */
+ rc += 1;
- return 0;
+ return simple_read_from_buffer(buf, count, ppos, data, rc);
}
-DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops_pec, pmbus_debugfs_get_pec,
- pmbus_debugfs_set_pec, "%llu\n");
+
+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)
{
@@ -2846,16 +3124,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;
- debugfs_create_file("pec", 0664, data->debugfs, client,
- &pmbus_debugfs_ops_pec);
-
+ /*
+ * 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)) {