From: Henry Wu <Henry_Wu@xxxxxxxxxxxx> 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@xxxxxxxxxxxx> --- 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@xxxxxxxxxxxx> + +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..e61f1380dbc1 --- /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 + +#define MP2869A_GAIN_BASE 32000 + +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]; + const int *max_phases; + 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: + /* 1000000 / 256 */ + out->vid_step_uv = 3906; + break; + case 0b110: + /* 1000000 / 512 */ + out->vid_step_uv = 1953; + break; + case 0b111: + /* 1000000 / 1024 */ + out->vid_step_uv = 977; + break; + 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 = MP2869A_GAIN_BASE / 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--; + } + /* approximate if step ≠ 1mV, 5mV, etc */ + m = 1000000 / vout_info.vid_step_uv; + + 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 const 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)(kernel_ulong_t)i2c_get_match_data(client); + data->max_phases = mp2869a_max_phases[data->chip_id]; + info = &data->info; + *info = mp2869a_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@xxxxxxxxxxxx>"); +MODULE_DESCRIPTION("PMBus driver for MPS MP2869A/MP29612A device"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); -- 2.43.0