devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/2] hwmon: (pmbus) Add support for MPS multi-phase mp2869a/mp29612a controllers
@ 2025-06-24  7:41 tzuhao.wtmh
  2025-06-24  7:41 ` [PATCH 2/2] dt-bindings: trivial-devices: Add mp2869a/mp29612a device entry tzuhao.wtmh
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: tzuhao.wtmh @ 2025-06-24  7:41 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jean Delvare,
	Guenter Roeck, Jonathan Corbet, Jonathan Cameron, Naresh Solanki,
	Rodrigo Gobbi, Michal Simek, Fabio Estevam, Henry Wu,
	Grant Peltier, Laurent Pinchart, Cedric Encarnacion,
	Kim Seer Paller, Leo Yang, Ninad Palsule, Alex Vdovydchenko,
	John Erasmus Mari Geronimo, Nuno Sa, Jerome Brunet, Noah Wang,
	Mariel Tinaco, devicetree, linux-kernel, linux-hwmon, linux-doc

From: Henry Wu <Henry_Wu@quantatw.com>

Add support for the mp2869a and mp29612a controllers from Monolithic Power
Systems, Inc. (MPS). These are dual-loop, digital, multi-phase modulation
controllers.

Signed-off-by: Henry Wu <Henry_Wu@quantatw.com>
---
 Documentation/hwmon/index.rst   |   1 +
 Documentation/hwmon/mp2869a.rst |  86 +++++++++
 drivers/hwmon/pmbus/Kconfig     |  10 ++
 drivers/hwmon/pmbus/Makefile    |   1 +
 drivers/hwmon/pmbus/mp2869a.c   | 299 ++++++++++++++++++++++++++++++++
 5 files changed, 397 insertions(+)
 create mode 100644 Documentation/hwmon/mp2869a.rst
 create mode 100644 drivers/hwmon/pmbus/mp2869a.c

diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index b45bfb4ebf30..10bf4bd77f7b 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -172,6 +172,7 @@ Hardware Monitoring Kernel Drivers
    menf21bmc
    mlxreg-fan
    mp2856
+   mp2869a
    mp2888
    mp2891
    mp2975
diff --git a/Documentation/hwmon/mp2869a.rst b/Documentation/hwmon/mp2869a.rst
new file mode 100644
index 000000000000..a98ccb3d630d
--- /dev/null
+++ b/Documentation/hwmon/mp2869a.rst
@@ -0,0 +1,86 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver mp2896a
+=====================
+
+Supported chips:
+
+  * MPS MP2896A
+
+    Prefix: 'mp2896a'
+
+  * MPS MP29612A
+
+    Prefix: 'mp29612a'
+
+Author:
+
+    Henry Wu <Henry_WU@quantatw.com>
+
+Description
+-----------
+
+This driver implements support for Monolithic Power Systems, Inc. (MPS)
+MP2896A, a digital, multi-phase voltage regulator controller with PMBus interface.
+
+This device:
+
+- Supports up to two power rails.
+- Supports multiple PMBus pages for telemetry and configuration.
+- Supports VOUT readout in **VID format only** (no support for direct format).
+- Supports AMD SVI3 VID protocol with 5-mV/LSB resolution (if applicable).
+- Uses vendor-specific registers for VOUT scaling and phase configuration.
+
+Device supports:
+
+- SVID interface.
+- AVSBus interface.
+
+Device compliant with:
+
+- PMBus rev 1.3 interface.
+
+Sysfs Interface
+---------------
+
+The driver provides the following sysfs attributes:
+
+**Current measurements:**
+
+- Index 1: "iin"
+- Indexes 2, 3: "iout"
+
+**curr[1-3]_alarm**
+**curr[1-3]_input**
+**curr[1-3]_label**
+
+**Voltage measurements:**
+
+- Index 1: "vin"
+- Indexes 2, 3: "vout"
+
+**in[1-3]_crit**
+**in[1-3]_crit_alarm**
+**in[1-3]_input**
+**in[1-3]_label**
+**in[1-3]_lcrit**
+**in[1-3]_lcrit_alarm**
+
+**Power measurements:**
+
+- Index 1: "pin"
+- Indexes 2, 3: "pout"
+
+**power[1-3]_alarm**
+**power[1-3]_input**
+**power[1-3]_label**
+
+**Temperature measurements:**
+
+**temp[1-2]_crit**
+**temp[1-2]_crit_alarm**
+**temp[1-2]_input**
+**temp[1-2]_max**
+**temp[1-2]_max_alarm**
+
+
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index 441f984a859d..93b558761cc6 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -364,6 +364,16 @@ config SENSORS_MP2856
 	  This driver can also be built as a module. If so, the module will
 	  be called mp2856.
 
+config SENSORS_MP2869A
+	tristate "MP2869A PMBus sensor"
+	depends on I2C && PMBUS
+	help
+	  If you say yes here you get support for the MPS MP2869A MP29612A
+	  voltage regulator via the PMBus interface.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called mp2869a.
+
 config SENSORS_MP2888
 	tristate "MPS MP2888"
 	help
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index 29cd8a3317d2..42087d0dedbc 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_SENSORS_MAX31785)	+= max31785.o
 obj-$(CONFIG_SENSORS_MAX34440)	+= max34440.o
 obj-$(CONFIG_SENSORS_MAX8688)	+= max8688.o
 obj-$(CONFIG_SENSORS_MP2856)	+= mp2856.o
+obj-$(CONFIG_SENSORS_MP2869A)   += mp2869a.o
 obj-$(CONFIG_SENSORS_MP2888)	+= mp2888.o
 obj-$(CONFIG_SENSORS_MP2891)	+= mp2891.o
 obj-$(CONFIG_SENSORS_MP2975)	+= mp2975.o
diff --git a/drivers/hwmon/pmbus/mp2869a.c b/drivers/hwmon/pmbus/mp2869a.c
new file mode 100644
index 000000000000..7fa03cad1953
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp2869a.c
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MP2856A/MP29612A
+ * Monolithic Power Systems VR Controller
+ *
+ * Copyright (C) 2023 Quanta Computer lnc.
+ */
+
+#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 "pmbus.h"
+
+/* Vendor specific registers. */
+#define MP2869A_VOUT_MODE		     0x20
+#define MP2869A_VOUT_MODE_MASK       GENMASK(7, 5)
+#define MP2869A_VOUT_MODE_VID        (0 << 5)
+
+
+#define MP2869A_READ_VOUT			 0x8b
+
+#define MP2869A_MFR_VOUT_SCALE_LOOP  0x29
+#define MP2869A_VID_RES_MASK         GENMASK(12, 10)
+#define MP2869A_VOUT_SCALE_MASK      GENMASK(7, 0)
+
+#define MP2869A_MAX_PHASE_RAIL1		 16
+#define MP2869A_MAX_PHASE_RAIL2		 8
+#define MP29612A_MAX_PHASE_RAIL1	 12
+#define MP29612A_MAX_PHASE_RAIL2	 6
+
+#define MP2869A_PAGE_NUM			 2
+#define MP29612A_PAGE_NUM			 2
+
+enum chips { mp2869a = 1, mp29612a };
+
+static const int mp2869a_max_phases[][MP2869A_PAGE_NUM] = {
+	[mp2869a] = { MP2869A_MAX_PHASE_RAIL1, MP2869A_MAX_PHASE_RAIL2 },
+	[mp29612a] = { MP29612A_MAX_PHASE_RAIL1, MP29612A_MAX_PHASE_RAIL2},
+};
+
+static const struct i2c_device_id mp2869a_id[] = {
+	{ "mp2869a", mp2869a },
+	{ "mp29612a", mp29612a },
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, mp2869a_id);
+
+struct MP2869A_data {
+	struct pmbus_driver_info info;
+	int vout_format[MP2869A_PAGE_NUM];
+	int curr_sense_gain[MP2869A_PAGE_NUM];
+	int max_phases[MP2869A_PAGE_NUM];
+	enum chips chip_id;
+};
+
+#define to_MP2869A_data(x)	container_of(x, struct MP2869A_data, info)
+
+static int
+MP2869A_read_word_helper(struct i2c_client *client, int page, int phase, u8 reg,
+	u16 mask)
+{
+	int ret = pmbus_read_word_data(client, page, phase, reg);
+
+	return (ret > 0) ? ret & mask : ret;
+}
+
+struct mp2869a_vout_info {
+	int vid_step_uv;
+	int vdiff_gain_ratio;
+};
+
+static int
+MP2869A_read_vid_and_scale(struct i2c_client *client, struct MP2869A_data *data,
+	int page, int phase,
+	struct mp2869a_vout_info *out)
+{
+	int ret;
+	u16 reg_val;
+	u8 vid_sel, vscale;
+
+	ret = pmbus_read_word_data(client, page, phase,
+		MP2869A_MFR_VOUT_SCALE_LOOP);
+	if (ret < 0)
+		return ret;
+
+	reg_val = (u16)ret;
+
+	/* Analyze VID Step Resolution(bits 12:10) */
+	vid_sel = (reg_val >> 10) & 0x7;
+	switch (vid_sel) {
+	case 0b000:
+		out->vid_step_uv = 6250;
+		break;
+	case 0b001:
+		out->vid_step_uv = 5000;
+		break;
+	case 0b010:
+		out->vid_step_uv = 2500;
+		break;
+	case 0b011:
+		out->vid_step_uv = 2000;
+		break;
+	case 0b100:
+		out->vid_step_uv = 1000;
+		break;
+	case 0b101:
+		out->vid_step_uv = 3906;
+		break; // 1000000 / 256
+	case 0b110:
+		out->vid_step_uv = 1953;
+		break; // 1000000 / 512
+	case 0b111:
+		out->vid_step_uv = 977;
+		break; // 1000000 / 1024
+	default:
+		return -EINVAL;
+	}
+
+	/* Analyze VOUT_SCALE_LOOP(bits 7:0) */
+	vscale = reg_val & 0xff;
+	if (vscale == 0)
+		return -EINVAL;
+	// Store as "magnification * 1000" to avoid floating point
+	out->vdiff_gain_ratio = 32 * 1000 / vscale;
+
+	return 0;
+}
+
+static int
+MP2869A_read_vout(struct i2c_client *client, struct MP2869A_data *data,
+	int page, int phase, u8 reg)
+{
+	int ret;
+	int raw;
+	struct mp2869a_vout_info vout_info;
+
+    raw = MP2869A_read_word_helper(client, page, phase, reg, GENMASK(11, 0));
+    if (raw < 0)
+		return raw;
+
+    ret = MP2869A_read_vid_and_scale(client, data, page, phase, &vout_info);
+    if (ret < 0)
+		return ret;
+
+	int m = 1;
+	int R = 0;
+	int step = vout_info.vid_step_uv;
+
+	// Let step = 10^R / m
+	while (step % 10 == 0 && R > -6) {
+		step /= 10;
+		R--;
+	}
+	m = 1000000 / vout_info.vid_step_uv;  // approximate if step ≠ 1mV, 5mV, etc
+
+	data->info.m[PSC_VOLTAGE_OUT] = m;
+	data->info.R[PSC_VOLTAGE_OUT] = R+3;
+	data->info.b[PSC_VOLTAGE_OUT] = 0;
+
+    return raw;
+}
+
+static int
+MP2869A_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 MP2869A_data *data = to_MP2869A_data(info);
+	int ret;
+
+	switch (reg) {
+	case PMBUS_READ_VOUT:
+		ret = MP2869A_read_vout(client, data, page, phase, reg);
+		break;
+	default:
+		return -ENODATA;
+	}
+
+	return ret;
+}
+
+static int
+MP2869A_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
+MP2869A_identify_vout_format(struct i2c_client *client,
+			    struct MP2869A_data *data)
+{
+	int i, ret;
+
+	for (i = 0; i < data->info.pages; i++) {
+		ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i);
+		if (ret < 0)
+			return ret;
+
+		ret = i2c_smbus_read_word_data(client, MP2869A_VOUT_MODE);
+		if (ret < 0)
+			return ret;
+
+		switch (ret & MP2869A_VOUT_MODE_MASK) {
+		case MP2869A_VOUT_MODE_VID:
+			data->vout_format[i] = vid;
+			break;
+		default:
+		return -EINVAL;
+		}
+		}
+	return 0;
+}
+
+static struct pmbus_driver_info MP2869A_info = {
+	.pages = MP2869A_PAGE_NUM,
+	.format[PSC_VOLTAGE_IN] = linear,
+	.format[PSC_VOLTAGE_OUT] = direct,
+	.format[PSC_TEMPERATURE] = linear,
+	.format[PSC_CURRENT_IN] = linear,
+	.format[PSC_CURRENT_OUT] = linear,
+	.format[PSC_POWER] = linear,
+	.m[PSC_VOLTAGE_OUT] = 1,
+	.b[PSC_VOLTAGE_OUT] = 0,
+	.R[PSC_VOLTAGE_OUT] = -3,
+	.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
+		PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
+		PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_POUT |
+		PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT,
+	.func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IOUT |
+		PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP,
+	.read_byte_data = MP2869A_read_byte_data,
+	.read_word_data = MP2869A_read_word_data,
+};
+
+static int mp2869a_probe(struct i2c_client *client)
+{
+	struct pmbus_driver_info *info;
+	struct MP2869A_data *data;
+	int ret;
+
+	data = devm_kzalloc(&client->dev, sizeof(struct MP2869A_data),
+		GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->chip_id = (enum chips)(uintptr_t)i2c_get_match_data(client);
+
+	memcpy(data->max_phases, mp2869a_max_phases[data->chip_id],
+		sizeof(data->max_phases));
+
+	memcpy(&data->info, &MP2869A_info, sizeof(*info));
+	info = &data->info;
+
+
+	/* Identify vout format. */
+	ret = MP2869A_identify_vout_format(client, data);
+	if (ret)
+		return ret;
+
+	/* set the device to page 0 */
+	i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+
+	return pmbus_do_probe(client, info);
+}
+
+static const struct of_device_id __maybe_unused mp2869a_of_match[] = {
+	{ .compatible = "mps,mp2869a", .data = (void *)mp2869a },
+	{ .compatible = "mps,mp29612a", .data = (void *)mp29612a},
+	{}
+};
+
+MODULE_DEVICE_TABLE(of, mp2869a_of_match);
+
+static struct i2c_driver mp2869a_driver = {
+	.driver = {
+		.name = "mp2869a",
+		.of_match_table = mp2869a_of_match,
+	},
+	.probe = mp2869a_probe,
+	.id_table = mp2869a_id,
+};
+
+module_i2c_driver(mp2869a_driver);
+
+
+MODULE_AUTHOR("Henry Wu <Henry_WU@quantatw.com>");
+MODULE_DESCRIPTION("PMBus driver for MPS MP2869A/MP29612A device");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PMBUS);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2025-06-25 13:11 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-24  7:41 [PATCH 1/2] hwmon: (pmbus) Add support for MPS multi-phase mp2869a/mp29612a controllers tzuhao.wtmh
2025-06-24  7:41 ` [PATCH 2/2] dt-bindings: trivial-devices: Add mp2869a/mp29612a device entry tzuhao.wtmh
2025-06-24  7:44   ` Krzysztof Kozlowski
2025-06-24  7:48 ` [PATCH 1/2] hwmon: (pmbus) Add support for MPS multi-phase mp2869a/mp29612a controllers Krzysztof Kozlowski
2025-06-25  6:31   ` 吳梓豪
2025-06-25  8:08     ` Krzysztof Kozlowski
2025-06-25 13:11     ` Guenter Roeck
2025-06-24 20:10 ` kernel test robot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).