aboutsummaryrefslogtreecommitdiff
path: root/drivers/hwmon/pmbus
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hwmon/pmbus')
-rw-r--r--drivers/hwmon/pmbus/Kconfig34
-rw-r--r--drivers/hwmon/pmbus/Makefile3
-rw-r--r--drivers/hwmon/pmbus/adm1275.c14
-rw-r--r--drivers/hwmon/pmbus/bpa-rs600.c29
-rw-r--r--drivers/hwmon/pmbus/dps920ab.c206
-rw-r--r--drivers/hwmon/pmbus/mp2888.c408
-rw-r--r--drivers/hwmon/pmbus/pim4328.c233
-rw-r--r--drivers/hwmon/pmbus/pmbus.c19
-rw-r--r--drivers/hwmon/pmbus/pmbus.h2
-rw-r--r--drivers/hwmon/pmbus/pmbus_core.c151
-rw-r--r--drivers/hwmon/pmbus/zl6100.c94
11 files changed, 1151 insertions, 42 deletions
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index 37a5c39784fa..ffb609cee3a4 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -19,9 +19,10 @@ config SENSORS_PMBUS
default y
help
If you say yes here you get hardware monitoring support for generic
- PMBus devices, including but not limited to ADP4000, BMR453, BMR454,
- MAX20796, MDT040, NCP4200, NCP4208, PDT003, PDT006, PDT012, TPS40400,
- TPS544B20, TPS544B25, TPS544C20, TPS544C25, and UDT020.
+ PMBus devices, including but not limited to ADP4000, BMR310, BMR453,
+ BMR454, BMR456, BMR457, BMR458, BMR480, BMR490, BMR491, BMR492,
+ MAX20796, MDT040, NCP4200, NCP4208, PDT003, PDT006, PDT012,
+ TPS40400, TPS544B20, TPS544B25, TPS544C20, TPS544C25, and UDT020.
This driver can also be built as a module. If so, the module will
be called pmbus.
@@ -85,6 +86,15 @@ config SENSORS_IBM_CFFPS
This driver can also be built as a module. If so, the module will
be called ibm-cffps.
+config SENSORS_DPS920AB
+ tristate "Delta DPS920AB Power Supply"
+ help
+ If you say yes here you get hardware monitoring support for Delta
+ DPS920AB Power Supplies.
+
+ This driver can also be built as a module. If so, the module will
+ be called dps920ab.
+
config SENSORS_INSPUR_IPSPS
tristate "INSPUR Power System Power Supply"
help
@@ -248,6 +258,15 @@ config SENSORS_MAX8688
This driver can also be built as a module. If so, the module will
be called max8688.
+config SENSORS_MP2888
+ tristate "MPS MP2888"
+ help
+ If you say yes here you get hardware monitoring support for MPS
+ MP2888 Digital, Multi-Phase, Pulse-Width Modulation Controller.
+
+ This driver can also be built as a module. If so, the module will
+ be called mp2888.
+
config SENSORS_MP2975
tristate "MPS MP2975"
help
@@ -257,6 +276,15 @@ config SENSORS_MP2975
This driver can also be built as a module. If so, the module will
be called mp2975.
+config SENSORS_PIM4328
+ tristate "Flex PIM4328 and compatibles"
+ help
+ If you say yes here you get hardware monitoring support for Flex
+ PIM4328, PIM4820 and PIM4006 Power Interface Modules.
+
+ This driver can also be built as a module. If so, the module will
+ be called pim4328.
+
config SENSORS_PM6764TR
tristate "ST PM6764TR"
help
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index f8dcc27cd56a..0ed4d596a948 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_SENSORS_BEL_PFE) += bel-pfe.o
obj-$(CONFIG_SENSORS_BPA_RS600) += bpa-rs600.o
obj-$(CONFIG_SENSORS_FSP_3Y) += fsp-3y.o
obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o
+obj-$(CONFIG_SENSORS_DPS920AB) += dps920ab.o
obj-$(CONFIG_SENSORS_INSPUR_IPSPS) += inspur-ipsps.o
obj-$(CONFIG_SENSORS_IR35221) += ir35221.o
obj-$(CONFIG_SENSORS_IR36021) += ir36021.o
@@ -28,6 +29,7 @@ obj-$(CONFIG_SENSORS_MAX20751) += max20751.o
obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
obj-$(CONFIG_SENSORS_MAX34440) += max34440.o
obj-$(CONFIG_SENSORS_MAX8688) += max8688.o
+obj-$(CONFIG_SENSORS_MP2888) += mp2888.o
obj-$(CONFIG_SENSORS_MP2975) += mp2975.o
obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o
obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o
@@ -39,3 +41,4 @@ obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o
obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o
obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o
+obj-$(CONFIG_SENSORS_PIM4328) += pim4328.o
diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c
index 980a3850b2f3..d311e0557401 100644
--- a/drivers/hwmon/pmbus/adm1275.c
+++ b/drivers/hwmon/pmbus/adm1275.c
@@ -611,11 +611,13 @@ static int adm1275_probe(struct i2c_client *client)
tindex = 8;
info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT |
- PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
+ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
- /* Enable VOUT if not enabled (it is disabled by default) */
- if (!(config & ADM1278_VOUT_EN)) {
- config |= ADM1278_VOUT_EN;
+ /* Enable VOUT & TEMP1 if not enabled (disabled by default) */
+ if ((config & (ADM1278_VOUT_EN | ADM1278_TEMP1_EN)) !=
+ (ADM1278_VOUT_EN | ADM1278_TEMP1_EN)) {
+ config |= ADM1278_VOUT_EN | ADM1278_TEMP1_EN;
ret = i2c_smbus_write_byte_data(client,
ADM1275_PMON_CONFIG,
config);
@@ -625,10 +627,6 @@ static int adm1275_probe(struct i2c_client *client)
return -ENODEV;
}
}
-
- if (config & ADM1278_TEMP1_EN)
- info->func[0] |=
- PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
if (config & ADM1278_VIN_EN)
info->func[0] |= PMBUS_HAVE_VIN;
break;
diff --git a/drivers/hwmon/pmbus/bpa-rs600.c b/drivers/hwmon/pmbus/bpa-rs600.c
index f6558ee9dec3..2be69fedfa36 100644
--- a/drivers/hwmon/pmbus/bpa-rs600.c
+++ b/drivers/hwmon/pmbus/bpa-rs600.c
@@ -46,6 +46,32 @@ static int bpa_rs600_read_byte_data(struct i2c_client *client, int page, int reg
return ret;
}
+/*
+ * The BPA-RS600 violates the PMBus spec. Specifically it treats the
+ * mantissa as unsigned. Deal with this here to allow the PMBus core
+ * to work with correctly encoded data.
+ */
+static int bpa_rs600_read_vin(struct i2c_client *client)
+{
+ int ret, exponent, mantissa;
+
+ ret = pmbus_read_word_data(client, 0, 0xff, PMBUS_READ_VIN);
+ if (ret < 0)
+ return ret;
+
+ if (ret & BIT(10)) {
+ exponent = ret >> 11;
+ mantissa = ret & 0x7ff;
+
+ exponent++;
+ mantissa >>= 1;
+
+ ret = (exponent << 11) | mantissa;
+ }
+
+ return ret;
+}
+
static int bpa_rs600_read_word_data(struct i2c_client *client, int page, int phase, int reg)
{
int ret;
@@ -85,6 +111,9 @@ static int bpa_rs600_read_word_data(struct i2c_client *client, int page, int pha
/* These commands return data but it is invalid/un-documented */
ret = -ENXIO;
break;
+ case PMBUS_READ_VIN:
+ ret = bpa_rs600_read_vin(client);
+ break;
default:
if (reg >= PMBUS_VIRT_BASE)
ret = -ENXIO;
diff --git a/drivers/hwmon/pmbus/dps920ab.c b/drivers/hwmon/pmbus/dps920ab.c
new file mode 100644
index 000000000000..d3941f6eb29a
--- /dev/null
+++ b/drivers/hwmon/pmbus/dps920ab.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Delta DPS920AB PSU
+ *
+ * Copyright (C) 2021 Delta Networks, Inc.
+ * Copyright (C) 2021 Sartura Ltd.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+struct dps920ab_data {
+ char *mfr_model;
+ char *mfr_id;
+};
+
+static int dps920ab_read_word_data(struct i2c_client *client, int page, int phase, int reg)
+{
+ /*
+ * This masks commands which are not supported.
+ * PSU advertises that all features are supported,
+ * in reality that unfortunately is not true.
+ * So enable only those that the datasheet confirms.
+ */
+ switch (reg) {
+ case PMBUS_FAN_COMMAND_1:
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ case PMBUS_STATUS_WORD:
+ case PMBUS_READ_VIN:
+ case PMBUS_READ_IIN:
+ case PMBUS_READ_VOUT:
+ case PMBUS_READ_IOUT:
+ case PMBUS_READ_TEMPERATURE_1:
+ case PMBUS_READ_TEMPERATURE_2:
+ case PMBUS_READ_TEMPERATURE_3:
+ case PMBUS_READ_FAN_SPEED_1:
+ case PMBUS_READ_POUT:
+ case PMBUS_READ_PIN:
+ case PMBUS_MFR_VOUT_MIN:
+ case PMBUS_MFR_VOUT_MAX:
+ case PMBUS_MFR_IOUT_MAX:
+ case PMBUS_MFR_POUT_MAX:
+ return pmbus_read_word_data(client, page, phase, reg);
+ default:
+ return -ENXIO;
+ }
+}
+
+static int dps920ab_write_word_data(struct i2c_client *client, int page, int reg,
+ u16 word)
+{
+ /*
+ * This masks commands which are not supported.
+ * PSU only has one R/W register and that is
+ * for the fan.
+ */
+ switch (reg) {
+ case PMBUS_FAN_COMMAND_1:
+ return pmbus_write_word_data(client, page, reg, word);
+ default:
+ return -EACCES;
+ }
+}
+
+static struct pmbus_driver_info dps920ab_info = {
+ .pages = 1,
+
+ .format[PSC_VOLTAGE_IN] = linear,
+ .format[PSC_VOLTAGE_OUT] = linear,
+ .format[PSC_CURRENT_IN] = linear,
+ .format[PSC_CURRENT_OUT] = linear,
+ .format[PSC_POWER] = linear,
+ .format[PSC_FAN] = linear,
+ .format[PSC_TEMPERATURE] = linear,
+
+ .func[0] =
+ PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN |
+ PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 |
+ PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 |
+ PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT |
+ PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
+ .read_word_data = dps920ab_read_word_data,
+ .write_word_data = dps920ab_write_word_data,
+};
+
+static int dps920ab_mfr_id_show(struct seq_file *s, void *data)
+{
+ struct dps920ab_data *priv = s->private;
+
+ seq_printf(s, "%s\n", priv->mfr_id);
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(dps920ab_mfr_id);
+
+static int dps920ab_mfr_model_show(struct seq_file *s, void *data)
+{
+ struct dps920ab_data *priv = s->private;
+
+ seq_printf(s, "%s\n", priv->mfr_model);
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(dps920ab_mfr_model);
+
+static void dps920ab_init_debugfs(struct dps920ab_data *data, struct i2c_client *client)
+{
+ struct dentry *debugfs_dir;
+ struct dentry *root;
+
+ root = pmbus_get_debugfs_dir(client);
+ if (!root)
+ return;
+
+ debugfs_dir = debugfs_create_dir(client->name, root);
+
+ debugfs_create_file("mfr_id",
+ 0400,
+ debugfs_dir,
+ data,
+ &dps920ab_mfr_id_fops);
+
+ debugfs_create_file("mfr_model",
+ 0400,
+ debugfs_dir,
+ data,
+ &dps920ab_mfr_model_fops);
+}
+
+static int dps920ab_probe(struct i2c_client *client)
+{
+ u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
+ struct dps920ab_data *data;
+ int ret;
+
+ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to read Manufacturer ID\n");
+ return ret;
+ }
+
+ buf[ret] = '\0';
+ if (ret != 5 || strncmp(buf, "DELTA", 5)) {
+ buf[ret] = '\0';
+ dev_err(&client->dev, "Unsupported Manufacturer ID '%s'\n", buf);
+ return -ENODEV;
+ }
+ data->mfr_id = devm_kstrdup(&client->dev, buf, GFP_KERNEL);
+ if (!data->mfr_id)
+ return -ENOMEM;
+
+ ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to read Manufacturer Model\n");
+ return ret;
+ }
+
+ buf[ret] = '\0';
+ if (ret != 11 || strncmp(buf, "DPS-920AB", 9)) {
+ dev_err(&client->dev, "Unsupported Manufacturer Model '%s'\n", buf);
+ return -ENODEV;
+ }
+ data->mfr_model = devm_kstrdup(&client->dev, buf, GFP_KERNEL);
+ if (!data->mfr_model)
+ return -ENOMEM;
+
+ ret = pmbus_do_probe(client, &dps920ab_info);
+ if (ret)
+ return ret;
+
+ dps920ab_init_debugfs(data, client);
+
+ return 0;
+}
+
+static const struct of_device_id __maybe_unused dps920ab_of_match[] = {
+ { .compatible = "delta,dps920ab", },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, dps920ab_of_match);
+
+static struct i2c_driver dps920ab_driver = {
+ .driver = {
+ .name = "dps920ab",
+ .of_match_table = of_match_ptr(dps920ab_of_match),
+ },
+ .probe_new = dps920ab_probe,
+};
+
+module_i2c_driver(dps920ab_driver);
+
+MODULE_AUTHOR("Robert Marko <robert.marko@sartura.hr>");
+MODULE_DESCRIPTION("PMBus driver for Delta DPS920AB PSU");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PMBUS);
diff --git a/drivers/hwmon/pmbus/mp2888.c b/drivers/hwmon/pmbus/mp2888.c
new file mode 100644
index 000000000000..8ecd4adfef40
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp2888.c
@@ -0,0 +1,408 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers
+ *
+ * Copyright (C) 2020 Nvidia Technologies Ltd.
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include "pmbus.h"
+
+/* Vendor specific registers. */
+#define MP2888_MFR_SYS_CONFIG 0x44
+#define MP2888_MFR_READ_CS1_2 0x73
+#define MP2888_MFR_READ_CS3_4 0x74
+#define MP2888_MFR_READ_CS5_6 0x75
+#define MP2888_MFR_READ_CS7_8 0x76
+#define MP2888_MFR_READ_CS9_10 0x77
+#define MP2888_MFR_VR_CONFIG1 0xe1
+
+#define MP2888_TOTAL_CURRENT_RESOLUTION BIT(3)
+#define MP2888_PHASE_CURRENT_RESOLUTION BIT(4)
+#define MP2888_DRMOS_KCS GENMASK(2, 0)
+#define MP2888_TEMP_UNIT 10
+#define MP2888_MAX_PHASE 10
+
+struct mp2888_data {
+ struct pmbus_driver_info info;
+ int total_curr_resolution;
+ int phase_curr_resolution;
+ int curr_sense_gain;
+};
+
+#define to_mp2888_data(x) container_of(x, struct mp2888_data, info)
+
+static int mp2888_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ switch (reg) {
+ case PMBUS_VOUT_MODE:
+ /* Enforce VOUT direct format. */
+ return PB_VOUT_MODE_DIRECT;
+ default:
+ return -ENODATA;
+ }
+}
+
+static int
+mp2888_current_sense_gain_and_resolution_get(struct i2c_client *client, struct mp2888_data *data)
+{
+ int ret;
+
+ /*
+ * Obtain DrMOS current sense gain of power stage from the register
+ * , bits 0-2. The value is selected as below:
+ * 00b - 5µA/A, 01b - 8.5µA/A, 10b - 9.7µA/A, 11b - 10µA/A. Other
+ * values are reserved.
+ */
+ ret = i2c_smbus_read_word_data(client, MP2888_MFR_SYS_CONFIG);
+ if (ret < 0)
+ return ret;
+
+ switch (ret & MP2888_DRMOS_KCS) {
+ case 0:
+ data->curr_sense_gain = 85;
+ break;
+ case 1:
+ data->curr_sense_gain = 97;
+ break;
+ case 2:
+ data->curr_sense_gain = 100;
+ break;
+ case 3:
+ data->curr_sense_gain = 50;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Obtain resolution selector for total and phase current report and protection.
+ * 0: original resolution; 1: half resolution (in such case phase current value should
+ * be doubled.
+ */
+ data->total_curr_resolution = (ret & MP2888_TOTAL_CURRENT_RESOLUTION) >> 3;
+ data->phase_curr_resolution = (ret & MP2888_PHASE_CURRENT_RESOLUTION) >> 4;
+
+ return 0;
+}
+
+static int
+mp2888_read_phase(struct i2c_client *client, struct mp2888_data *data, int page, int phase, u8 reg)
+{
+ int ret;
+
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ if (!((phase + 1) % 2))
+ ret >>= 8;
+ ret &= 0xff;
+
+ /*
+ * Output value is calculated as: (READ_CSx / 80 – 1.23) / (Kcs * Rcs)
+ * where:
+ * - Kcs is the DrMOS current sense gain of power stage, which is obtained from the
+ * register MP2888_MFR_VR_CONFIG1, bits 13-12 with the following selection of DrMOS
+ * (data->curr_sense_gain):
+ * 00b - 5µA/A, 01b - 8.5µA/A, 10b - 9.7µA/A, 11b - 10µA/A.
+ * - Rcs is the internal phase current sense resistor. This parameter depends on hardware
+ * assembly. By default it is set to 1kΩ. In case of different assembly, user should
+ * scale this parameter by dividing it by Rcs.
+ * If phase current resolution bit is set to 1, READ_CSx value should be doubled.
+ * Note, that current phase sensing, providing by the device is not accurate. This is
+ * because sampling of current occurrence of bit weight has a big deviation, especially for
+ * light load.
+ */
+ ret = DIV_ROUND_CLOSEST(ret * 100 - 9800, data->curr_sense_gain);
+ ret = (data->phase_curr_resolution) ? ret * 2 : ret;
+ /* Scale according to total current resolution. */
+ ret = (data->total_curr_resolution) ? ret * 8 : ret * 4;
+ return ret;
+}
+
+static int
+mp2888_read_phases(struct i2c_client *client, struct mp2888_data *data, int page, int phase)
+{
+ int ret;
+
+ switch (phase) {
+ case 0 ... 1:
+ ret = mp2888_read_phase(client, data, page, phase, MP2888_MFR_READ_CS1_2);
+ break;
+ case 2 ... 3:
+ ret = mp2888_read_phase(client, data, page, phase, MP2888_MFR_READ_CS3_4);
+ break;
+ case 4 ... 5:
+ ret = mp2888_read_phase(client, data, page, phase, MP2888_MFR_READ_CS5_6);
+ break;
+ case 6 ... 7:
+ ret = mp2888_read_phase(client, data, page, phase, MP2888_MFR_READ_CS7_8);
+ break;
+ case 8 ... 9:
+ ret = mp2888_read_phase(client, data, page, phase, MP2888_MFR_READ_CS9_10);
+ break;
+ default:
+ return -ENODATA;
+ }
+ return ret;
+}
+
+static int mp2888_read_word_data(struct i2c_client *client, int page, int phase, int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp2888_data *data = to_mp2888_data(info);
+ int ret;
+
+ switch (reg) {
+ case PMBUS_READ_VIN:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret <= 0)
+ return ret;
+
+ /*
+ * READ_VIN requires fixup to scale it to linear11 format. Register data format
+ * provides 10 bits for mantissa and 6 bits for exponent. Bits 15:10 are set with
+ * the fixed value 111011b.
+ */
+ ret = (ret & GENMASK(9, 0)) | ((ret & GENMASK(31, 10)) << 1);
+ break;
+ case PMBUS_OT_WARN_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+ /*
+ * Chip reports limits in degrees C, but the actual temperature in 10th of
+ * degrees C - scaling is needed to match both.
+ */
+ ret *= MP2888_TEMP_UNIT;
+ break;
+ case PMBUS_READ_IOUT:
+ if (phase != 0xff)
+ return mp2888_read_phases(client, data, page, phase);
+
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+ /*
+ * READ_IOUT register has unused bits 15:12 with fixed value 1110b. Clear these
+ * bits and scale with total current resolution. Data is provided in direct format.
+ */
+ ret &= GENMASK(11, 0);
+ ret = data->total_curr_resolution ? ret * 2 : ret;
+ break;
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+ ret &= GENMASK(9, 0);
+ /*
+ * Chip reports limits with resolution 1A or 2A, if total current resolution bit is
+ * set 1. Actual current is reported with 0.25A or respectively 0.5A resolution.
+ * Scaling is needed to match both.
+ */
+ ret = data->total_curr_resolution ? ret * 8 : ret * 4;
+ break;
+ case PMBUS_READ_POUT:
+ case PMBUS_READ_PIN:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+ ret = data->total_curr_resolution ? ret * 2 : ret;
+ break;
+ case PMBUS_POUT_OP_WARN_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+ /*
+ * Chip reports limits with resolution 1W or 2W, if total current resolution bit is
+ * set 1. Actual power is reported with 0.5W or 1W respectively resolution. Scaling
+ * is needed to match both.
+ */
+ ret = data->total_curr_resolution ? ret * 4 : ret * 2;
+ break;
+ /*
+ * The below registers are not implemented by device or implemented not according to the
+ * spec. Skip all of them to avoid exposing non-relevant inputs to sysfs.
+ */
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_UT_WARN_LIMIT:
+ case PMBUS_UT_FAULT_LIMIT:
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ case PMBUS_VOUT_UV_WARN_LIMIT:
+ case PMBUS_VOUT_OV_WARN_LIMIT:
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ case PMBUS_VIN_OV_WARN_LIMIT:
+ case PMBUS_IOUT_OC_LV_FAULT_LIMIT:
+ case PMBUS_IOUT_OC_FAULT_LIMIT:
+ case PMBUS_POUT_MAX:
+ case PMBUS_IOUT_UC_FAULT_LIMIT:
+ case PMBUS_POUT_OP_FAULT_LIMIT:
+ case PMBUS_PIN_OP_WARN_LIMIT:
+ case PMBUS_MFR_VIN_MIN:
+ case PMBUS_MFR_VOUT_MIN:
+ case PMBUS_MFR_VIN_MAX:
+ case PMBUS_MFR_VOUT_MAX:
+ case PMBUS_MFR_IIN_MAX:
+ case PMBUS_MFR_IOUT_MAX:
+ case PMBUS_MFR_PIN_MAX:
+ case PMBUS_MFR_POUT_MAX:
+ case PMBUS_MFR_MAX_TEMP_1:
+ return -ENXIO;
+ default:
+ return -ENODATA;
+ }
+
+ return ret;
+}
+
+static int mp2888_write_word_data(struct i2c_client *client, int page, int reg, u16 word)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp2888_data *data = to_mp2888_data(info);
+
+ switch (reg) {
+ case PMBUS_OT_WARN_LIMIT:
+ word = DIV_ROUND_CLOSEST(word, MP2888_TEMP_UNIT);
+ /* Drop unused bits 15:8. */
+ word = clamp_val(word, 0, GENMASK(7, 0));
+ break;
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ /* Fix limit according to total curent resolution. */
+ word = data->total_curr_resolution ? DIV_ROUND_CLOSEST(word, 8) :
+ DIV_ROUND_CLOSEST(word, 4);
+ /* Drop unused bits 15:10. */
+ word = clamp_val(word, 0, GENMASK(9, 0));
+ break;
+ case PMBUS_POUT_OP_WARN_LIMIT:
+ /* Fix limit according to total curent resolution. */
+ word = data->total_curr_resolution ? DIV_ROUND_CLOSEST(word, 4) :
+ DIV_ROUND_CLOSEST(word, 2);
+ /* Drop unused bits 15:10. */
+ word = clamp_val(word, 0, GENMASK(9, 0));
+ break;
+ default:
+ return -ENODATA;
+ }
+ return pmbus_write_word_data(client, page, reg, word);
+}
+
+static int
+mp2888_identify_multiphase(struct i2c_client *client, struct mp2888_data *data,
+ struct pmbus_driver_info *info)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Identify multiphase number - could be from 1 to 10. */
+ ret = i2c_smbus_read_word_data(client, MP2888_MFR_VR_CONFIG1);
+ if (ret <= 0)
+ return ret;
+
+ info->phases[0] = ret & GENMASK(3, 0);
+
+ /*
+ * The device provides a total of 10 PWM pins, and can be configured to different phase
+ * count applications for rail.
+ */
+ if (info->phases[0] > MP2888_MAX_PHASE)
+ return -EINVAL;
+
+ return 0;
+}
+
+static struct pmbus_driver_info mp2888_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = linear,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .format[PSC_CURRENT_IN] = linear,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_POWER] = direct,
+ .m[PSC_TEMPERATURE] = 1,
+ .R[PSC_TEMPERATURE] = 1,
+ .m[PSC_VOLTAGE_OUT] = 1,
+ .R[PSC_VOLTAGE_OUT] = 3,
+ .m[PSC_CURRENT_OUT] = 4,
+ .m[PSC_POWER] = 1,
+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IOUT |
+ PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP |
+ PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT |
+ PMBUS_PHASE_VIRTUAL,
+ .pfunc[0] = PMBUS_HAVE_IOUT,
+ .pfunc[1] = PMBUS_HAVE_IOUT,
+ .pfunc[2] = PMBUS_HAVE_IOUT,
+ .pfunc[3] = PMBUS_HAVE_IOUT,
+ .pfunc[4] = PMBUS_HAVE_IOUT,
+ .pfunc[5] = PMBUS_HAVE_IOUT,
+ .pfunc[6] = PMBUS_HAVE_IOUT,
+ .pfunc[7] = PMBUS_HAVE_IOUT,
+ .pfunc[8] = PMBUS_HAVE_IOUT,
+ .pfunc[9] = PMBUS_HAVE_IOUT,
+ .read_byte_data = mp2888_read_byte_data,
+ .read_word_data = mp2888_read_word_data,
+ .write_word_data = mp2888_write_word_data,
+};
+
+static int mp2888_probe(struct i2c_client *client)
+{
+ struct pmbus_driver_info *info;
+ struct mp2888_data *data;
+ int ret;
+
+ data = devm_kzalloc(&client->dev, sizeof(struct mp2888_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ memcpy(&data->info, &mp2888_info, sizeof(*info));
+ info = &data->info;
+
+ /* Identify multiphase configuration. */
+ ret = mp2888_identify_multiphase(client, data, info);
+ if (ret)
+ return ret;
+
+ /* Obtain current sense gain of power stage and current resolution. */
+ ret = mp2888_current_sense_gain_and_resolution_get(client, data);
+ if (ret)
+ return ret;
+
+ return pmbus_do_probe(client, info);
+}
+
+static const struct i2c_device_id mp2888_id[] = {
+ {"mp2888", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, mp2888_id);
+
+static const struct of_device_id __maybe_unused mp2888_of_match[] = {
+ {.compatible = "mps,mp2888"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, mp2888_of_match);
+
+static struct i2c_driver mp2888_driver = {
+ .driver = {
+ .name = "mp2888",
+ .of_match_table = of_match_ptr(mp2888_of_match),
+ },
+ .probe_new = mp2888_probe,
+ .id_table = mp2888_id,
+};
+
+module_i2c_driver(mp2888_driver);
+
+MODULE_AUTHOR("Vadim Pasternak <vadimp@nvidia.com>");
+MODULE_DESCRIPTION("PMBus driver for MPS MP2888 device");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PMBUS);
diff --git a/drivers/hwmon/pmbus/pim4328.c b/drivers/hwmon/pmbus/pim4328.c
new file mode 100644
index 000000000000..273ff6e57654
--- /dev/null
+++ b/drivers/hwmon/pmbus/pim4328.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for PIM4006, PIM4328 and PIM4820
+ *
+ * Copyright (c) 2021 Flextronics International Sweden AB
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pmbus.h>
+#include <linux/slab.h>
+#include "pmbus.h"
+
+enum chips { pim4006, pim4328, pim4820 };
+
+struct pim4328_data {
+ enum chips id;
+ struct pmbus_driver_info info;
+};
+
+#define to_pim4328_data(x) container_of(x, struct pim4328_data, info)
+
+/* PIM4006 and PIM4328 */
+#define PIM4328_MFR_READ_VINA 0xd3
+#define PIM4328_MFR_READ_VINB 0xd4
+
+/* PIM4006 */
+#define PIM4328_MFR_READ_IINA 0xd6
+#define PIM4328_MFR_READ_IINB 0xd7
+#define PIM4328_MFR_FET_CHECKSTATUS 0xd9
+
+/* PIM4328 */
+#define PIM4328_MFR_STATUS_BITS 0xd5
+
+/* PIM4820 */
+#define PIM4328_MFR_READ_STATUS 0xd0
+
+static const struct i2c_device_id pim4328_id[] = {
+ {"bmr455", pim4328},
+ {"pim4006", pim4006},
+ {"pim4106", pim4006},
+ {"pim4206", pim4006},
+ {"pim4306", pim4006},
+ {"pim4328", pim4328},
+ {"pim4406", pim4006},
+ {"pim4820", pim4820},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, pim4328_id);
+
+static int pim4328_read_word_data(struct i2c_client *client, int page,
+ int phase, int reg)
+{
+ int ret;
+
+ if (page > 0)
+ return -ENXIO;
+
+ if (phase == 0xff)
+ return -ENODATA;
+
+ switch (reg) {
+ case PMBUS_READ_VIN:
+ ret = pmbus_read_word_data(client, page, phase,
+ phase == 0 ? PIM4328_MFR_READ_VINA
+ : PIM4328_MFR_READ_VINB);
+ break;
+ case PMBUS_READ_IIN:
+ ret = pmbus_read_word_data(client, page, phase,
+ phase == 0 ? PIM4328_MFR_READ_IINA
+ : PIM4328_MFR_READ_IINB);
+ break;
+ default:
+ ret = -ENODATA;
+ }
+
+ return ret;
+}
+
+static int pim4328_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct pim4328_data *data = to_pim4328_data(info);
+ int ret, status;
+
+ if (page > 0)
+ return -ENXIO;
+
+ switch (reg) {
+ case PMBUS_STATUS_BYTE:
+ ret = pmbus_read_byte_data(client, page, PMBUS_STATUS_BYTE);
+ if (ret < 0)
+ return ret;
+ if (data->id == pim4006) {
+ status = pmbus_read_word_data(client, page, 0xff,
+ PIM4328_MFR_FET_CHECKSTATUS);
+ if (status < 0)
+ return status;
+ if (status & 0x0630) /* Input UV */
+ ret |= PB_STATUS_VIN_UV;
+ } else if (data->id == pim4328) {
+ status = pmbus_read_byte_data(client, page,
+ PIM4328_MFR_STATUS_BITS);
+ if (status < 0)
+ return status;
+ if (status & 0x04) /* Input UV */
+ ret |= PB_STATUS_VIN_UV;
+ if (status & 0x40) /* Output UV */
+ ret |= PB_STATUS_NONE_ABOVE;
+ } else if (data->id == pim4820) {
+ status = pmbus_read_byte_data(client, page,
+ PIM4328_MFR_READ_STATUS);
+ if (status < 0)
+ return status;
+ if (status & 0x05) /* Input OV or OC */
+ ret |= PB_STATUS_NONE_ABOVE;
+ if (status & 0x1a) /* Input UV */
+ ret |= PB_STATUS_VIN_UV;
+ if (status & 0x40) /* OT */
+ ret |= PB_STATUS_TEMPERATURE;
+ }
+ break;
+ default:
+ ret = -ENODATA;
+ }
+
+ return ret;
+}
+
+static int pim4328_probe(struct i2c_client *client)
+{
+ int status;
+ u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];
+ const struct i2c_device_id *mid;
+ struct pim4328_data *data;
+ struct pmbus_driver_info *info;
+ struct pmbus_platform_data *pdata;
+ struct device *dev = &client->dev;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_BYTE_DATA
+ | I2C_FUNC_SMBUS_BLOCK_DATA))
+ return -ENODEV;
+
+ data = devm_kzalloc(&client->dev, sizeof(struct pim4328_data),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id);
+ if (status < 0) {
+ dev_err(&client->dev, "Failed to read Manufacturer Model\n");
+ return status;
+ }
+ for (mid = pim4328_id; mid->name[0]; mid++) {
+ if (!strncasecmp(mid->name, device_id, strlen(mid->name)))
+ break;
+ }
+ if (!mid->name[0]) {
+ dev_err(&client->dev, "Unsupported device\n");
+ return -ENODEV;
+ }
+
+ if (strcmp(client->name, mid->name))
+ dev_notice(&client->dev,
+ "Device mismatch: Configured %s, detected %s\n",
+ client->name, mid->name);
+
+ data->id = mid->driver_data;
+ info = &data->info;
+ info->pages = 1;
+ info->read_byte_data = pim4328_read_byte_data;
+ info->read_word_data = pim4328_read_word_data;
+
+ pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data),
+ GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+ dev->platform_data = pdata;
+ pdata->flags = PMBUS_NO_CAPABILITY | PMBUS_NO_WRITE_PROTECT;
+
+ switch (data->id) {
+ case pim4006:
+ info->phases[0] = 2;
+ info->func[0] = PMBUS_PHASE_VIRTUAL | PMBUS_HAVE_VIN
+ | PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT;
+ info->pfunc[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN;
+ info->pfunc[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN;
+ break;
+ case pim4328:
+ info->phases[0] = 2;
+ info->func[0] = PMBUS_PHASE_VIRTUAL
+ | PMBUS_HAVE_VCAP | PMBUS_HAVE_VIN
+ | PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT;
+ info->pfunc[0] = PMBUS_HAVE_VIN;
+ info->pfunc[1] = PMBUS_HAVE_VIN;
+ info->format[PSC_VOLTAGE_IN] = direct;
+ info->format[PSC_TEMPERATURE] = direct;
+ info->format[PSC_CURRENT_OUT] = direct;
+ pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD;
+ break;
+ case pim4820:
+ info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_TEMP
+ | PMBUS_HAVE_IIN;
+ info->format[PSC_VOLTAGE_IN] = direct;
+ info->format[PSC_TEMPERATURE] = direct;
+ info->format[PSC_CURRENT_IN] = direct;
+ pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ return pmbus_do_probe(client, info);
+}
+
+static struct i2c_driver pim4328_driver = {
+ .driver = {
+ .name = "pim4328",
+ },
+ .probe_new = pim4328_probe,
+ .id_table = pim4328_id,
+};
+
+module_i2c_driver(pim4328_driver);
+
+MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>");
+MODULE_DESCRIPTION("PMBus driver for PIM4006, PIM4328, PIM4820 power interface modules");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PMBUS);
diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c
index 618c377664c4..d0d386990af5 100644
--- a/drivers/hwmon/pmbus/pmbus.c
+++ b/drivers/hwmon/pmbus/pmbus.c
@@ -173,13 +173,13 @@ static int pmbus_probe(struct i2c_client *client)
return -ENOMEM;
device_info = (struct pmbus_device_info *)i2c_match_id(pmbus_id, client)->driver_data;
- if (device_info->flags & PMBUS_SKIP_STATUS_CHECK) {
+ if (device_info->flags) {
pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data),
GFP_KERNEL);
if (!pdata)
return -ENOMEM;
- pdata->flags = PMBUS_SKIP_STATUS_CHECK;
+ pdata->flags = device_info->flags;
}
info->pages = device_info->pages;
@@ -193,22 +193,37 @@ static const struct pmbus_device_info pmbus_info_one = {
.pages = 1,
.flags = 0
};
+
static const struct pmbus_device_info pmbus_info_zero = {
.pages = 0,
.flags = 0
};
+
static const struct pmbus_device_info pmbus_info_one_skip = {
.pages = 1,
.flags = PMBUS_SKIP_STATUS_CHECK
};
+static const struct pmbus_device_info pmbus_info_one_status = {
+ .pages = 1,
+ .flags = PMBUS_READ_STATUS_AFTER_FAILED_CHECK
+};
+
/*
* Use driver_data to set the number of pages supported by the chip.
*/
static const struct i2c_device_id pmbus_id[] = {
{"adp4000", (kernel_ulong_t)&pmbus_info_one},
+ {"bmr310", (kernel_ulong_t)&pmbus_info_one_status},
{"bmr453", (kernel_ulong_t)&pmbus_info_one},
{"bmr454", (kernel_ulong_t)&pmbus_info_one},
+ {"bmr456", (kernel_ulong_t)&pmbus_info_one},
+ {"bmr457", (kernel_ulong_t)&pmbus_info_one},
+ {"bmr458", (kernel_ulong_t)&pmbus_info_one_status},
+ {"bmr480", (kernel_ulong_t)&pmbus_info_one_status},
+ {"bmr490", (kernel_ulong_t)&pmbus_info_one_status},
+ {"bmr491", (kernel_ulong_t)&pmbus_info_one_status},
+ {"bmr492", (kernel_ulong_t)&pmbus_info_one},
{"dps460", (kernel_ulong_t)&pmbus_info_one_skip},
{"dps650ab", (kernel_ulong_t)&pmbus_info_one_skip},
{"dps800", (kernel_ulong_t)&pmbus_info_one_skip},
diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h
index 3968924f8533..e0aa8aa46d8c 100644
--- a/drivers/hwmon/pmbus/pmbus.h
+++ b/drivers/hwmon/pmbus/pmbus.h
@@ -375,7 +375,7 @@ enum pmbus_sensor_classes {
};
#define PMBUS_PAGES 32 /* Per PMBus specification */
-#define PMBUS_PHASES 8 /* Maximum number of phases per page */
+#define PMBUS_PHASES 10 /* Maximum number of phases per page */
/* Functionality bit mask */
#define PMBUS_HAVE_VIN BIT(0)
diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c
index bbd745178147..776ee2237be2 100644
--- a/drivers/hwmon/pmbus/pmbus_core.c
+++ b/drivers/hwmon/pmbus/pmbus_core.c
@@ -523,6 +523,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;
}
@@ -1327,14 +1329,14 @@ 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,
- 0xff, 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;
@@ -2140,6 +2142,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.
*/
@@ -2214,11 +2321,14 @@ 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 */
+ /* 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))
- client->flags |= I2C_CLIENT_PEC;
+ if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) {
+ if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) {
+ client->flags |= I2C_CLIENT_PEC;
+ }
+ }
}
/*
@@ -2226,9 +2336,11 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
* 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);
@@ -2255,6 +2367,17 @@ 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;
+ }
+
return 0;
}
diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c
index b7d4eacdc3ef..e9df0c56d91e 100644
--- a/drivers/hwmon/pmbus/zl6100.c
+++ b/drivers/hwmon/pmbus/zl6100.c
@@ -18,7 +18,7 @@
#include "pmbus.h"
enum chips { zl2004, zl2005, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105,
- zl9101, zl9117 };
+ zl8802, zl9101, zl9117, zls1003, zls4009 };
struct zl6100_data {
int id;
@@ -34,6 +34,13 @@ struct zl6100_data {
#define ZL6100_MFR_XTEMP_ENABLE BIT(7)
+#define ZL8802_MFR_USER_GLOBAL_CONFIG 0xe9
+#define ZL8802_MFR_TMON_ENABLE BIT(12)
+#define ZL8802_MFR_USER_CONFIG 0xd1
+#define ZL8802_MFR_XTEMP_ENABLE_2 BIT(1)
+#define ZL8802_MFR_DDC_CONFIG 0xd3
+#define ZL8802_MFR_PHASES_MASK 0x0007
+
#define MFR_VMON_OV_FAULT_LIMIT 0xf5
#define MFR_VMON_UV_FAULT_LIMIT 0xf6
#define MFR_READ_VMON 0xf7
@@ -132,7 +139,7 @@ static int zl6100_read_word_data(struct i2c_client *client, int page,
struct zl6100_data *data = to_zl6100_data(info);
int ret, vreg;
- if (page > 0)
+ if (page >= info->pages)
return -ENXIO;
if (data->id == zl2005) {
@@ -191,7 +198,7 @@ static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg)
struct zl6100_data *data = to_zl6100_data(info);
int ret, status;
- if (page > 0)
+ if (page >= info->pages)
return -ENXIO;
zl6100_wait(data);
@@ -230,7 +237,7 @@ static int zl6100_write_word_data(struct i2c_client *client, int page, int reg,
struct zl6100_data *data = to_zl6100_data(info);
int ret, vreg;
- if (page > 0)
+ if (page >= info->pages)
return -ENXIO;
switch (reg) {
@@ -271,7 +278,7 @@ static int zl6100_write_byte(struct i2c_client *client, int page, u8 value)
struct zl6100_data *data = to_zl6100_data(info);
int ret;
- if (page > 0)
+ if (page >= info->pages)
return -ENXIO;
zl6100_wait(data);
@@ -287,6 +294,10 @@ static const struct i2c_device_id zl6100_id[] = {
{"bmr462", zl2008},
{"bmr463", zl2008},
{"bmr464", zl2008},
+ {"bmr465", zls4009},
+ {"bmr466", zls1003},
+ {"bmr467", zls4009},
+ {"bmr469", zl8802},
{"zl2004", zl2004},
{"zl2005", zl2005},
{"zl2006", zl2006},
@@ -295,15 +306,18 @@ static const struct i2c_device_id zl6100_id[] = {
{"zl2106", zl2106},
{"zl6100", zl6100},
{"zl6105", zl6105},
+ {"zl8802", zl8802},
{"zl9101", zl9101},
{"zl9117", zl9117},
+ {"zls1003", zls1003},
+ {"zls4009", zls4009},
{ }
};
MODULE_DEVICE_TABLE(i2c, zl6100_id);
static int zl6100_probe(struct i2c_client *client)
{
- int ret;
+ int ret, i;
struct zl6100_data *data;
struct pmbus_driver_info *info;
u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];
@@ -367,18 +381,70 @@ static int zl6100_probe(struct i2c_client *client)
| 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.
+ * ZL2004, ZL8802, ZL9101M, ZL9117M and ZLS4009 support monitoring
+ * an extra voltage (VMON for ZL2004, ZL8802 and ZLS4009,
+ * VDRV for ZL9101M and ZL9117M). Report it as vmon.
*/
- if (data->id == zl2004 || data->id == zl9101 || data->id == zl9117)
+ if (data->id == zl2004 || data->id == zl8802 || data->id == zl9101 ||
+ data->id == zl9117 || data->id == zls4009)
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;
+ /*
+ * ZL8802 has two outputs that can be used either independently or in
+ * a current sharing configuration. The driver uses the DDC_CONFIG
+ * register to check if the module is running with independent or
+ * shared outputs. If the module is in shared output mode, only one
+ * output voltage will be reported.
+ */
+ if (data->id == zl8802) {
+ info->pages = 2;
+ info->func[0] |= PMBUS_HAVE_IIN;
+
+ ret = i2c_smbus_read_word_data(client, ZL8802_MFR_DDC_CONFIG);
+ if (ret < 0)
+ return ret;
+
+ data->access = ktime_get();
+ zl6100_wait(data);
+
+ if (ret & ZL8802_MFR_PHASES_MASK)
+ info->func[1] |= PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT;
+ else
+ info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
+ | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT;
- if (ret & ZL6100_MFR_XTEMP_ENABLE)
- info->func[0] |= PMBUS_HAVE_TEMP2;
+ for (i = 0; i < 2; i++) {
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i);
+ if (ret < 0)
+ return ret;
+
+ data->access = ktime_get();
+ zl6100_wait(data);
+
+ ret = i2c_smbus_read_word_data(client, ZL8802_MFR_USER_CONFIG);
+ if (ret < 0)
+ return ret;
+
+ if (ret & ZL8802_MFR_XTEMP_ENABLE_2)
+ info->func[i] |= PMBUS_HAVE_TEMP2;
+
+ data->access = ktime_get();
+ zl6100_wait(data);
+ }
+ ret = i2c_smbus_read_word_data(client, ZL8802_MFR_USER_GLOBAL_CONFIG);
+ if (ret < 0)
+ return ret;
+
+ if (ret & ZL8802_MFR_TMON_ENABLE)
+ info->func[0] |= PMBUS_HAVE_TEMP3;
+ } else {
+ ret = i2c_smbus_read_word_data(client, ZL6100_MFR_CONFIG);
+ if (ret < 0)
+ return ret;
+
+ if (ret & ZL6100_MFR_XTEMP_ENABLE)
+ info->func[0] |= PMBUS_HAVE_TEMP2;
+ }
data->access = ktime_get();
zl6100_wait(data);