diff mbox series

hwmon: add MP2891 driver

Message ID SEYPR04MB64825A740FDE7282B2FECEE2FAE22@SEYPR04MB6482.apcprd04.prod.outlook.com (mailing list archive)
State Changes Requested
Headers show
Series hwmon: add MP2891 driver | expand

Commit Message

Noah Wang May 13, 2024, 5:52 a.m. UTC
This driver is designed for MPS VR controller mp2891. The input
voltage, output voltage, input current, output current, input
power, output power and temperature of per rail can be obtained
from hwmon sysfs that the driver provided.

Signed-off-by: Noah Wang <noahwang.wang@outlook.com>
---
 Documentation/hwmon/mp2891.rst |  95 +++++++++++++++
 drivers/hwmon/pmbus/Kconfig    |   9 ++
 drivers/hwmon/pmbus/Makefile   |   1 +
 drivers/hwmon/pmbus/mp2891.c   | 210 +++++++++++++++++++++++++++++++++
 4 files changed, 315 insertions(+)
 create mode 100644 Documentation/hwmon/mp2891.rst
 create mode 100644 drivers/hwmon/pmbus/mp2891.c

--
2.25.1

Comments

Bagas Sanjaya May 13, 2024, 10:01 a.m. UTC | #1
On Mon, May 13, 2024 at 01:52:25PM +0800, Noah Wang wrote:
>  Documentation/hwmon/mp2891.rst |  95 +++++++++++++++
>  drivers/hwmon/pmbus/Kconfig    |   9 ++
>  drivers/hwmon/pmbus/Makefile   |   1 +
>  drivers/hwmon/pmbus/mp2891.c   | 210 +++++++++++++++++++++++++++++++++
>  4 files changed, 315 insertions(+)
>  create mode 100644 Documentation/hwmon/mp2891.rst
>  create mode 100644 drivers/hwmon/pmbus/mp2891.c

You forget to add toctree entry:

---- >8 ----
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 1ca7a4fe1f8f57..5825a056ad054f 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -164,6 +164,7 @@ Hardware Monitoring Kernel Drivers
    mlxreg-fan
    mp2856
    mp2888
+   mp2891
    mp2975
    mp5023
    mp5990

Thanks.
Guenter Roeck May 13, 2024, 2:20 p.m. UTC | #2
On 5/12/24 22:52, Noah Wang wrote:
> This driver is designed for MPS VR controller mp2891. The input
> voltage, output voltage, input current, output current, input
> power, output power and temperature of per rail can be obtained
> from hwmon sysfs that the driver provided.
> 
> Signed-off-by: Noah Wang <noahwang.wang@outlook.com>
> ---
>   Documentation/hwmon/mp2891.rst |  95 +++++++++++++++

Needs to be added to index.rst.

>   drivers/hwmon/pmbus/Kconfig    |   9 ++
>   drivers/hwmon/pmbus/Makefile   |   1 +
>   drivers/hwmon/pmbus/mp2891.c   | 210 +++++++++++++++++++++++++++++++++
>   4 files changed, 315 insertions(+)
>   create mode 100644 Documentation/hwmon/mp2891.rst
>   create mode 100644 drivers/hwmon/pmbus/mp2891.c
> 
> diff --git a/Documentation/hwmon/mp2891.rst b/Documentation/hwmon/mp2891.rst
> new file mode 100644
> index 000000000..eaf73fe60
> --- /dev/null
> +++ b/Documentation/hwmon/mp2891.rst
> @@ -0,0 +1,95 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +Kernel driver mp2891
> +====================
> +
> +Supported chips:
> +
> +  * MPS mp2891
> +
> +    Prefix: 'mp2891'
> +
> +  * Datasheet
> +
> +    Publicly available at the MPS website : https://www.monolithicpower.com/en/mp2891.html

No, it isn't. It is not even accessible with an account (the provided "datasheet"
is a useless one-pager).

> +
> +Author:
> +
> +	Noah Wang <noahwang.wang@outlook.com>
> +
> +Description
> +-----------
> +
> +This driver implements support for Monolithic Power Systems, Inc. (MPS)
> +MP2891 Multi-phase Digital VR Controller.
> +
> +Device compliant with:
> +
> +- PMBus rev 1.3 interface.
> +
> +Device supports direct and linear format for reading input voltage,
> +output voltage, input currect, output current, input power, output
> +power, and temperature.
> +
> +The driver exports the following attributes via the 'sysfs' files
> +for input voltage:
> +
> +**in1_input**
> +
> +**in1_label**
> +
> +The driver provides the following attributes for output voltage:
> +
> +**in2_input**
> +
> +**in2_label**
> +
> +**in3_input**
> +
> +**in3_label**
> +
> +The driver provides the following attributes for input current:
> +
> +**curr1_input**
> +
> +**curr1_label**
> +
> +**curr2_input**
> +
> +**curr2_label**
> +
> +The driver provides the following attributes for output current:
> +
> +**curr3_input**
> +
> +**curr3_label**
> +
> +**curr4_input**
> +
> +**curr4_label**
> +
> +The driver provides the following attributes for input power:
> +
> +**power1_input**
> +
> +**power1_label**
> +
> +**power2_input**
> +
> +**power2_label**
> +
> +The driver provides the following attributes for output power:
> +
> +**power3_input**
> +
> +**power3_label**
> +
> +**power4_input**
> +
> +**power4_label**
> +
> +The driver provides the following attributes for temperature:
> +
> +**temp1_input**
> +
> +**temp2_input**
> diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
> index 557ae0c41..b8b6c7724 100644
> --- a/drivers/hwmon/pmbus/Kconfig
> +++ b/drivers/hwmon/pmbus/Kconfig
> @@ -361,6 +361,15 @@ config SENSORS_MP5990
>   	  This driver can also be built as a module. If so, the module will
>   	  be called mp5990.
> 
> +config SENSORS_MP2891
> +    tristate "MPS MP2891"
> +    help
> +      If you say yes here you get hardware monitoring support for MPS
> +      MP2891 Dual Loop Digital Multi-Phase Controller.
> +
> +      This driver can also be built as a module. If so, the module will
> +      be called mp2891.
> +
>   config SENSORS_MPQ7932_REGULATOR
>   	bool "Regulator support for MPQ7932"
>   	depends on SENSORS_MPQ7932 && REGULATOR
> diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
> index f14ecf03a..57b91c20e 100644
> --- a/drivers/hwmon/pmbus/Makefile
> +++ b/drivers/hwmon/pmbus/Makefile
> @@ -38,6 +38,7 @@ obj-$(CONFIG_SENSORS_MP2888)	+= mp2888.o
>   obj-$(CONFIG_SENSORS_MP2975)	+= mp2975.o
>   obj-$(CONFIG_SENSORS_MP5023)	+= mp5023.o
>   obj-$(CONFIG_SENSORS_MP5990)	+= mp5990.o
> +obj-$(CONFIG_SENSORS_MP2891)   += mp2891.o
>   obj-$(CONFIG_SENSORS_MPQ7932)	+= mpq7932.o
>   obj-$(CONFIG_SENSORS_MPQ8785)	+= mpq8785.o
>   obj-$(CONFIG_SENSORS_PLI1209BC)	+= pli1209bc.o
> diff --git a/drivers/hwmon/pmbus/mp2891.c b/drivers/hwmon/pmbus/mp2891.c
> new file mode 100644
> index 000000000..c98d9ec6b
> --- /dev/null
> +++ b/drivers/hwmon/pmbus/mp2891.c
> @@ -0,0 +1,210 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP2891)
> + */
> +
> +#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, the register READ_PIN_EST(0x94),

Please use standard multi-line comments.

> + * MFR_VOUT_LOOP_CTRL(0xBD) and READ_IIN_EST(0x95) redefine
> + * the standard PMBUS register.

That doesn't explain the reason for using the _EST registers
instead of the standard registers.

> + */
> +#define MFR_VOUT_LOOP_CTRL      0xBD
> +#define READ_PIN_EST            0x94
> +#define READ_IIN_EST            0x95
> +
> +#define MP2891_PAGE_NUM			2
> +
> +#define MP2891_RAIL1_FUNC	(PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
> +							PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | \
> +							PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | \
> +							PMBUS_HAVE_IIN | PMBUS_PHASE_VIRTUAL)
> +
> +#define MP2891_RAIL2_FUNC	(PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | \
> +							PMBUS_HAVE_TEMP | PMBUS_HAVE_POUT | \
> +							PMBUS_HAVE_IIN | PMBUS_PHASE_VIRTUAL)

What is the point of PMBUS_PHASE_VIRTUAL ?

> +
> +struct mp2891_data {
> +	struct pmbus_driver_info info;
> +};
> +
> +#define to_mp2891_data(x) container_of(x, struct mp2891_data, info)
> +
Unused.

> +static int mp2891_read_byte_data(struct i2c_client *client, int page, int reg)
> +{
> +	int ret;
> +
> +	switch (reg) {
> +	case PMBUS_VOUT_MODE:
> +		ret = PB_VOUT_MODE_DIRECT;

Needs explanation.

> +		break;
> +	default:
> +		ret = -EINVAL;

No status registers ? I do not believe this is correct.

> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static int mp2891_read_word_data(struct i2c_client *client, int page, int phase,
> +			      int reg)
> +{
> +	int ret;
> +
> +	switch (reg) {
> +	case PMBUS_READ_VIN:
> +	case PMBUS_READ_IOUT:
> +	case PMBUS_READ_POUT:
> +	case PMBUS_READ_VOUT:
> +	case PMBUS_READ_TEMPERATURE_1:
> +		ret = pmbus_read_word_data(client, page, phase, reg);

Use
		return -ENODATA;
instead.

> +		break;
> +	case PMBUS_READ_IIN:
> +		ret = pmbus_read_word_data(client, page, phase, READ_IIN_EST);
> +		break;
> +	case PMBUS_READ_PIN:
> +		ret = pmbus_read_word_data(client, page, phase, READ_PIN_EST);

This needs an explanation. Why not use standard READ_IIN and READ_PIN ?

> +		break;
> +	default:
> +		ret = -EINVAL;

No limit registers ? That seems extremely unlikely.


Given that you are essentially claiming that the chip would support
almost no standard registers, I'll need to see the datasheet to confirm.

> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static int
> +mp2891_identify_vout_scale(struct i2c_client *client, struct mp2891_data *data,
> +							u32 reg, int page)
> +{
> +	int ret;
> +
> +	ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = i2c_smbus_read_word_data(client, reg);
> +	if (ret < 0)
> +		return ret;
> +
> +	/*
> +	 * Obtain vout scale from the register MFR_VOUT_LOOP_CTRL, bits 15-14,bit 13.
> +	 * If MFR_VOUT_LOOP_CTRL[13] = 1, the vout scale is below:
> +	 * 2.5mV/LSB
> +	 * If MFR_VOUT_LOOP_CTRL[13] = 0, the vout scale is decided by
> +	 * MFR_VOUT_LOOP_CTRL[15:14]:
> +	 * 00b - 6.25mV/LSB, 01b - 5mV/LSB, 10b - 2mV/LSB, 11b - 1mV
> +	 */
> +	if (ret & GENMASK(13, 13)) {
> +		data->info.m[PSC_VOLTAGE_OUT] = 4;
> +		data->info.R[PSC_VOLTAGE_OUT] = -1;
> +		data->info.b[PSC_VOLTAGE_OUT] = 0;
> +	} else {
> +		ret = (ret & GENMASK(15, 14)) >> 14;

Why not FIELD_GET() ?

> +		if (ret == 0) {
> +			data->info.m[PSC_VOLTAGE_OUT] = 16;
> +			data->info.R[PSC_VOLTAGE_OUT] = -2;
> +			data->info.b[PSC_VOLTAGE_OUT] = 0;
> +		} else if (ret == 1) {
> +			data->info.m[PSC_VOLTAGE_OUT] = 2;
> +			data->info.R[PSC_VOLTAGE_OUT] = -1;
> +			data->info.b[PSC_VOLTAGE_OUT] = 0;
> +		} else if (ret == 2) {
> +			data->info.m[PSC_VOLTAGE_OUT] = 5;
> +			data->info.R[PSC_VOLTAGE_OUT] = -1;
> +			data->info.b[PSC_VOLTAGE_OUT] = 0;
> +		} else {
> +			data->info.m[PSC_VOLTAGE_OUT] = 1;
> +			data->info.R[PSC_VOLTAGE_OUT] = 0;
> +			data->info.b[PSC_VOLTAGE_OUT] = 0;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +mp2891_identify_rails_vout_scale(struct i2c_client *client, struct mp2891_data *data)
> +{
> +	int ret;
> +
> +	/* Identify vout scale from register  MFR_VOUT_LOOP_CTRL. */

Useless comment.

> +	/* Identify vout scale for rail 1. */
> +	ret = mp2891_identify_vout_scale(client, data, MFR_VOUT_LOOP_CTRL, 0);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Identify vout scale for rail 2. */
> +	ret = mp2891_identify_vout_scale(client, data, MFR_VOUT_LOOP_CTRL, 1);

Passing the register to this function is useless. Just use the register directly
in mp2891_identify_vout_scale().

> +
> +	return ret;
> +}
> +
> +static struct pmbus_driver_info mp2891_info = {
> +	.pages = MP2891_PAGE_NUM,
> +	.format[PSC_VOLTAGE_IN] = linear,
> +	.format[PSC_CURRENT_IN] = linear,
> +	.format[PSC_CURRENT_OUT] = linear,
> +	.format[PSC_TEMPERATURE] = linear,
> +	.format[PSC_POWER] = linear,
> +	.format[PSC_VOLTAGE_OUT] = direct,
> +
> +	.func[0] = MP2891_RAIL1_FUNC,
> +	.func[1] = MP2891_RAIL2_FUNC,
> +	.read_word_data = mp2891_read_word_data,
> +	.read_byte_data = mp2891_read_byte_data,
> +};
> +
> +static int mp2891_probe(struct i2c_client *client)
> +{
> +	struct pmbus_driver_info *info;
> +	struct mp2891_data *data;
> +	int ret;
> +
> +	data = devm_kzalloc(&client->dev, sizeof(struct mp2891_data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	memcpy(&data->info, &mp2891_info, sizeof(*info));
> +	info = &data->info;
> +
> +	/* Identify vout scale per rail. */
> +	ret = mp2891_identify_rails_vout_scale(client, data);
> +	if (ret < 0)
> +		return ret;
> +

Why not use the identify() callback instead ?

> +	return pmbus_do_probe(client, info);
> +}
> +
> +static const struct i2c_device_id mp2891_id[] = {
> +	{"mp2891", 0},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(i2c, mp2891_id);
> +
> +static const struct of_device_id __maybe_unused mp2891_of_match[] = {
> +	{.compatible = "mps,mp2891"},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, mp2891_of_match);
> +
> +static struct i2c_driver mp2891_driver = {
> +	.driver = {
> +		.name = "mp2891",
> +		.of_match_table = mp2891_of_match,
> +	},
> +	.probe = mp2891_probe,
> +	.id_table = mp2891_id,
> +};
> +
> +module_i2c_driver(mp2891_driver);
> +
> +MODULE_AUTHOR("Noah Wang <noahwang.wang@outlook.com>");
> +MODULE_DESCRIPTION("PMBus driver for MPS MP2891 device");

drop "device"

> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS(PMBUS);
> --
> 2.25.1
>
Guenter Roeck May 13, 2024, 2:30 p.m. UTC | #3
On 5/12/24 22:52, Noah Wang wrote:
> This driver is designed for MPS VR controller mp2891. The input
> voltage, output voltage, input current, output current, input
> power, output power and temperature of per rail can be obtained
> from hwmon sysfs that the driver provided.
> 
> Signed-off-by: Noah Wang <noahwang.wang@outlook.com>

The chip name looked vaguely familiar, and indeed I found:

https://patchwork.kernel.org/project/linux-hwmon/patch/TYZPR03MB7130A7F41D61BFB611DDF0C7FA479@TYZPR03MB7130.apcprd03.prod.outlook.com/

So this is really v2 of your patch without saying so,
it pretty much ignores all feedback I sent as response to v1,
and it does drop support for limit and status attributes for no
reason.

Please don't do that.

Guenter
diff mbox series

Patch

diff --git a/Documentation/hwmon/mp2891.rst b/Documentation/hwmon/mp2891.rst
new file mode 100644
index 000000000..eaf73fe60
--- /dev/null
+++ b/Documentation/hwmon/mp2891.rst
@@ -0,0 +1,95 @@ 
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver mp2891
+====================
+
+Supported chips:
+
+  * MPS mp2891
+
+    Prefix: 'mp2891'
+
+  * Datasheet
+
+    Publicly available at the MPS website : https://www.monolithicpower.com/en/mp2891.html
+
+Author:
+
+	Noah Wang <noahwang.wang@outlook.com>
+
+Description
+-----------
+
+This driver implements support for Monolithic Power Systems, Inc. (MPS)
+MP2891 Multi-phase Digital VR Controller.
+
+Device compliant with:
+
+- PMBus rev 1.3 interface.
+
+Device supports direct and linear format for reading input voltage,
+output voltage, input currect, output current, input power, output
+power, and temperature.
+
+The driver exports the following attributes via the 'sysfs' files
+for input voltage:
+
+**in1_input**
+
+**in1_label**
+
+The driver provides the following attributes for output voltage:
+
+**in2_input**
+
+**in2_label**
+
+**in3_input**
+
+**in3_label**
+
+The driver provides the following attributes for input current:
+
+**curr1_input**
+
+**curr1_label**
+
+**curr2_input**
+
+**curr2_label**
+
+The driver provides the following attributes for output current:
+
+**curr3_input**
+
+**curr3_label**
+
+**curr4_input**
+
+**curr4_label**
+
+The driver provides the following attributes for input power:
+
+**power1_input**
+
+**power1_label**
+
+**power2_input**
+
+**power2_label**
+
+The driver provides the following attributes for output power:
+
+**power3_input**
+
+**power3_label**
+
+**power4_input**
+
+**power4_label**
+
+The driver provides the following attributes for temperature:
+
+**temp1_input**
+
+**temp2_input**
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index 557ae0c41..b8b6c7724 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -361,6 +361,15 @@  config SENSORS_MP5990
 	  This driver can also be built as a module. If so, the module will
 	  be called mp5990.

+config SENSORS_MP2891
+    tristate "MPS MP2891"
+    help
+      If you say yes here you get hardware monitoring support for MPS
+      MP2891 Dual Loop Digital Multi-Phase Controller.
+
+      This driver can also be built as a module. If so, the module will
+      be called mp2891.
+
 config SENSORS_MPQ7932_REGULATOR
 	bool "Regulator support for MPQ7932"
 	depends on SENSORS_MPQ7932 && REGULATOR
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index f14ecf03a..57b91c20e 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -38,6 +38,7 @@  obj-$(CONFIG_SENSORS_MP2888)	+= mp2888.o
 obj-$(CONFIG_SENSORS_MP2975)	+= mp2975.o
 obj-$(CONFIG_SENSORS_MP5023)	+= mp5023.o
 obj-$(CONFIG_SENSORS_MP5990)	+= mp5990.o
+obj-$(CONFIG_SENSORS_MP2891)   += mp2891.o
 obj-$(CONFIG_SENSORS_MPQ7932)	+= mpq7932.o
 obj-$(CONFIG_SENSORS_MPQ8785)	+= mpq8785.o
 obj-$(CONFIG_SENSORS_PLI1209BC)	+= pli1209bc.o
diff --git a/drivers/hwmon/pmbus/mp2891.c b/drivers/hwmon/pmbus/mp2891.c
new file mode 100644
index 000000000..c98d9ec6b
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp2891.c
@@ -0,0 +1,210 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP2891)
+ */
+
+#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, the register READ_PIN_EST(0x94),
+ * MFR_VOUT_LOOP_CTRL(0xBD) and READ_IIN_EST(0x95) redefine
+ * the standard PMBUS register.
+ */
+#define MFR_VOUT_LOOP_CTRL      0xBD
+#define READ_PIN_EST            0x94
+#define READ_IIN_EST            0x95
+
+#define MP2891_PAGE_NUM			2
+
+#define MP2891_RAIL1_FUNC	(PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
+							PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | \
+							PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | \
+							PMBUS_HAVE_IIN | PMBUS_PHASE_VIRTUAL)
+
+#define MP2891_RAIL2_FUNC	(PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | \
+							PMBUS_HAVE_TEMP | PMBUS_HAVE_POUT | \
+							PMBUS_HAVE_IIN | PMBUS_PHASE_VIRTUAL)
+
+struct mp2891_data {
+	struct pmbus_driver_info info;
+};
+
+#define to_mp2891_data(x) container_of(x, struct mp2891_data, info)
+
+static int mp2891_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+	int ret;
+
+	switch (reg) {
+	case PMBUS_VOUT_MODE:
+		ret = PB_VOUT_MODE_DIRECT;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int mp2891_read_word_data(struct i2c_client *client, int page, int phase,
+			      int reg)
+{
+	int ret;
+
+	switch (reg) {
+	case PMBUS_READ_VIN:
+	case PMBUS_READ_IOUT:
+	case PMBUS_READ_POUT:
+	case PMBUS_READ_VOUT:
+	case PMBUS_READ_TEMPERATURE_1:
+		ret = pmbus_read_word_data(client, page, phase, reg);
+		break;
+	case PMBUS_READ_IIN:
+		ret = pmbus_read_word_data(client, page, phase, READ_IIN_EST);
+		break;
+	case PMBUS_READ_PIN:
+		ret = pmbus_read_word_data(client, page, phase, READ_PIN_EST);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int
+mp2891_identify_vout_scale(struct i2c_client *client, struct mp2891_data *data,
+							u32 reg, int page)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_read_word_data(client, reg);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Obtain vout scale from the register MFR_VOUT_LOOP_CTRL, bits 15-14,bit 13.
+	 * If MFR_VOUT_LOOP_CTRL[13] = 1, the vout scale is below:
+	 * 2.5mV/LSB
+	 * If MFR_VOUT_LOOP_CTRL[13] = 0, the vout scale is decided by
+	 * MFR_VOUT_LOOP_CTRL[15:14]:
+	 * 00b - 6.25mV/LSB, 01b - 5mV/LSB, 10b - 2mV/LSB, 11b - 1mV
+	 */
+	if (ret & GENMASK(13, 13)) {
+		data->info.m[PSC_VOLTAGE_OUT] = 4;
+		data->info.R[PSC_VOLTAGE_OUT] = -1;
+		data->info.b[PSC_VOLTAGE_OUT] = 0;
+	} else {
+		ret = (ret & GENMASK(15, 14)) >> 14;
+		if (ret == 0) {
+			data->info.m[PSC_VOLTAGE_OUT] = 16;
+			data->info.R[PSC_VOLTAGE_OUT] = -2;
+			data->info.b[PSC_VOLTAGE_OUT] = 0;
+		} else if (ret == 1) {
+			data->info.m[PSC_VOLTAGE_OUT] = 2;
+			data->info.R[PSC_VOLTAGE_OUT] = -1;
+			data->info.b[PSC_VOLTAGE_OUT] = 0;
+		} else if (ret == 2) {
+			data->info.m[PSC_VOLTAGE_OUT] = 5;
+			data->info.R[PSC_VOLTAGE_OUT] = -1;
+			data->info.b[PSC_VOLTAGE_OUT] = 0;
+		} else {
+			data->info.m[PSC_VOLTAGE_OUT] = 1;
+			data->info.R[PSC_VOLTAGE_OUT] = 0;
+			data->info.b[PSC_VOLTAGE_OUT] = 0;
+		}
+	}
+
+	return 0;
+}
+
+static int
+mp2891_identify_rails_vout_scale(struct i2c_client *client, struct mp2891_data *data)
+{
+	int ret;
+
+	/* Identify vout scale from register  MFR_VOUT_LOOP_CTRL. */
+	/* Identify vout scale for rail 1. */
+	ret = mp2891_identify_vout_scale(client, data, MFR_VOUT_LOOP_CTRL, 0);
+	if (ret < 0)
+		return ret;
+
+	/* Identify vout scale for rail 2. */
+	ret = mp2891_identify_vout_scale(client, data, MFR_VOUT_LOOP_CTRL, 1);
+
+	return ret;
+}
+
+static struct pmbus_driver_info mp2891_info = {
+	.pages = MP2891_PAGE_NUM,
+	.format[PSC_VOLTAGE_IN] = linear,
+	.format[PSC_CURRENT_IN] = linear,
+	.format[PSC_CURRENT_OUT] = linear,
+	.format[PSC_TEMPERATURE] = linear,
+	.format[PSC_POWER] = linear,
+	.format[PSC_VOLTAGE_OUT] = direct,
+
+	.func[0] = MP2891_RAIL1_FUNC,
+	.func[1] = MP2891_RAIL2_FUNC,
+	.read_word_data = mp2891_read_word_data,
+	.read_byte_data = mp2891_read_byte_data,
+};
+
+static int mp2891_probe(struct i2c_client *client)
+{
+	struct pmbus_driver_info *info;
+	struct mp2891_data *data;
+	int ret;
+
+	data = devm_kzalloc(&client->dev, sizeof(struct mp2891_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	memcpy(&data->info, &mp2891_info, sizeof(*info));
+	info = &data->info;
+
+	/* Identify vout scale per rail. */
+	ret = mp2891_identify_rails_vout_scale(client, data);
+	if (ret < 0)
+		return ret;
+
+	return pmbus_do_probe(client, info);
+}
+
+static const struct i2c_device_id mp2891_id[] = {
+	{"mp2891", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, mp2891_id);
+
+static const struct of_device_id __maybe_unused mp2891_of_match[] = {
+	{.compatible = "mps,mp2891"},
+	{}
+};
+MODULE_DEVICE_TABLE(of, mp2891_of_match);
+
+static struct i2c_driver mp2891_driver = {
+	.driver = {
+		.name = "mp2891",
+		.of_match_table = mp2891_of_match,
+	},
+	.probe = mp2891_probe,
+	.id_table = mp2891_id,
+};
+
+module_i2c_driver(mp2891_driver);
+
+MODULE_AUTHOR("Noah Wang <noahwang.wang@outlook.com>");
+MODULE_DESCRIPTION("PMBus driver for MPS MP2891 device");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PMBUS);