Re: [PATCH 5/5] hwmon: (pmbus) add support for MAX20826 and similar devices
Nuno Sá <[email protected]> Tue, 4 Aug 2026 10:20:03 +0100
| Newsgroups | gmane.linux.drivers.devicetree,gmane.linux.kernel.hwmon,gmane.linux.documentation |
|---|---|
| Message-ID | <anGsbjdpV_qw3QDp@nsa> |
On Tue, Jul 28, 2026 at 03:12:59PM -0700, Guenter Roeck wrote: > On 7/28/26 09:03, Nuno Sá via B4 Relay wrote: > > From: Nuno Sá <[email protected]> > > > > The MAX20826 IC and similar provide a high-density, flexible and scalable > > dual-loop solution for high current cores for AI applications. These are > > dual loop solutions multiphase voltage regulators. Between Rails A and B, > > MAX20855B and MAX20908 supports up to 8 phases total configurable from > > 8+0 to 4+4 phases, MAX20912 supports up to 12 phases from 12+0 to 6+6, > > and MAX20826 and MAX20916 supports up to 16 phases from 16+0 to 8+8. > > > > Co-developed-by: Alexis Czezar Torreno <[email protected]> > > Signed-off-by: Alexis Czezar Torreno <[email protected]> > > Signed-off-by: Nuno Sá <[email protected]> > > Very incomplete review; see it as first glance feedback. > > Guenter > > > --- > > Documentation/hwmon/index.rst | 1 + > > Documentation/hwmon/max20826.rst | 124 +++++ > > MAINTAINERS | 2 + > > drivers/hwmon/pmbus/Kconfig | 19 + > > drivers/hwmon/pmbus/Makefile | 1 + > > drivers/hwmon/pmbus/max20826.c | 1037 ++++++++++++++++++++++++++++++++++++++ > > 6 files changed, 1184 insertions(+) > > > > diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst > > index 29130df44d12..c63992b292c1 100644 > > --- a/Documentation/hwmon/index.rst > > +++ b/Documentation/hwmon/index.rst > > @@ -164,6 +164,7 @@ Hardware Monitoring Kernel Drivers > > max197 > > max20730 > > max20751 > > + max20826 > > max20830 > > max20860a > > max31722 > > diff --git a/Documentation/hwmon/max20826.rst b/Documentation/hwmon/max20826.rst > > new file mode 100644 > > index 000000000000..be97312dc612 > > --- /dev/null > > +++ b/Documentation/hwmon/max20826.rst > > @@ -0,0 +1,124 @@ > > +.. SPDX-License-Identifier: GPL-2.0 > > + > > +Kernel driver max20826 > > +====================== > > + > > +Supported chips: > > + > > + * Analog Devices MAX20826 > > + > > + Prefix: 'max20826' > > + > > + Addresses scanned: - > > + > > + Datasheet: Datasheet is not publicly available. > > + > > + * Analog Devices MAX20855B > > + > > + Prefix: 'max20855b' > > + > > + Addresses scanned: - > > + > > + Datasheet: Datasheet is not publicly available. > > + > > + * Analog Devices MAX20908 > > + > > + Prefix: 'max20908' > > + > > + Addresses scanned: - > > + > > + Datasheet: Datasheet is not publicly available. > > + > > + * Analog Devices MAX20912 > > + > > + Prefix: 'max20912' > > + > > + Addresses scanned: - > > + > > + Datasheet: Datasheet is not publicly available. > > + > > + * Analog Devices MAX20916 > > + > > + Prefix: 'max20916' > > + > > + Addresses scanned: - > > + > > + Datasheet: Datasheet is not publicly available. > > + > > +Author: > > + > > + - Nuno Sá <[email protected]> > > + > > + > > +Description > > +----------- > > + > > +This driver supports hardware monitoring for Analog Devices MAX20826, > > +MAX20855B, MAX20908, MAX20912, and MAX20916 multiphase voltage regulator > > +controllers with PMBus interface. > > + > > +The devices are dual-loop, multiphase controllers. Depending on the device and > > +configuration, the high-speed processor voltage-control interface can be > > +Nvidia PWMVID, Intel SVID, AMD SVI3, or AVSBus. PMBus is used for monitoring, > > +configuration, status, and fault reporting. > > + > > +The driver detects whether the device uses PMBus page mode or direct address > > +mode. In direct address mode, rail B is accessed at the rail A I2C address plus > > +one. If rail B is present, the driver exposes a second PMBus page. > > + > > +The driver detects the active number of phases and exposes per-phase input and > > +output current attributes through the PMBus virtual phase support. > > + > > +Usage Notes > > +----------- > > + > > +This driver does not auto-detect devices. You will have to instantiate the > > +devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for > > +details. > > + > > +The optional ``avren`` and ``bvren`` GPIOs may be provided to control the rail A > > +and rail B hardware enable pins. If regulator support is enabled, the rails are > > +also registered through the PMBus regulator framework. > > + > > +Sysfs entries > > +------------- > > + > > +The following attributes are supported. Limits, alarms, and per-phase entries > > +are exposed depending on device capabilities, rail configuration, and detected > > +phase count. > > + > > +=========================== ================================================ > > +in1_label "vin" > > +in1_input Measured input voltage > > +in1_alarm Input voltage alarm > > +in[2-3]_label "vout[1-2]" > > +in[2-3]_input Measured output voltage > > +in[2-3]_alarm Output voltage alarm > > +in[2-3]_high_speed_en Enable high-speed voltage-control interface > > +in_high_speed_bus Active high-speed voltage-control interface > > +currX_label "iinN", "iinN.P", "ioutN", or "ioutN.P" > > +currX_input Measured input/output current > > +currX_alarm Current alarm > > +powerX_label "pinN" or "poutN" > > +powerX_input Measured input/output power > > +temp[1-2]_input Measured temperature > > +temp[1-2]_alarm Temperature alarm > > +=========================== ================================================ > > + > > +Notes > > +----- > > + > > +``N`` is the rail number, starting at 1. ``P`` is the phase number, starting at > > +0. The exact ``currX`` indices depend on the number of present rails and on the > > +detected phase count. > > + > > +``in[2-3]_high_speed_en`` is a per-rail read/write attribute matching the > > +standard hwmon voltage channels for the output rails. ``in2_high_speed_en`` > > +controls rail A / ``vout1``. ``in3_high_speed_en`` controls rail B / ``vout2`` > > +and is only present if rail B is detected. Writing 1 selects the high-speed > > +voltage-control interface for that rail; writing 0 selects PMBus voltage > > +control. > > + > > +``in_high_speed_bus`` is a read-only attribute reporting the high-speed > > +interface used by the device, for example ``Nvidia PWMVID``, ``Intel SVID``, > > +``AMD SVI3``, or ``AVSBus``. > > diff --git a/MAINTAINERS b/MAINTAINERS > > index fe8808f3ee4c..8518d55800e9 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -15843,6 +15843,8 @@ L: [email protected] > > S: Supported > > W: https://ez.analog.com/linux-software-drivers > > F: Documentation/devicetree/bindings/hwmon/pmbus/adi,max20826.yaml > > +F: Documentation/hwmon/max20826.rst > > +F: drivers/hwmon/pmbus/max20826.c > > MAX20830 HARDWARE MONITOR DRIVER > > M: Alexis Czezar Torreno <[email protected]> > > diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig > > index c8cda160b5f8..8d3568f7347d 100644 > > --- a/drivers/hwmon/pmbus/Kconfig > > +++ b/drivers/hwmon/pmbus/Kconfig > > @@ -393,6 +393,25 @@ config SENSORS_MAX20751 > > This driver can also be built as a module. If so, the module will > > be called max20751. > > +config SENSORS_MAX20826 > > + tristate "Analog Devices MAX20826 and similar devices" > > + help > > + If you say yes here you get hardware monitoring support for Analog > > + Devices MAX20826 and similar devices. > > + > > + This driver can also be built as a module. If so, the module will > > + be called max20826. > > + > > +config SENSORS_MAX20826_REGULATOR > > + bool "Regulator support for Analog Devices MAX20826 and similar devices" > > + depends on SENSORS_MAX20826 && REGULATOR > > + help > > + If you say yes here you get regulator support for Analog Devices > > + MAX20826 and similar sensors. > > + > > + This enables the MAX20826 to be used as a regulator device, > > + providing voltage control through the regulator framework. > > + > > config SENSORS_MAX20830 > > tristate "Analog Devices MAX20830" > > help > > diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile > > index ffc05f493213..15601443470b 100644 > > --- a/drivers/hwmon/pmbus/Makefile > > +++ b/drivers/hwmon/pmbus/Makefile > > @@ -38,6 +38,7 @@ obj-$(CONFIG_SENSORS_MAX16601) += max16601.o > > obj-$(CONFIG_SENSORS_MAX17616) += max17616.o > > obj-$(CONFIG_SENSORS_MAX20730) += max20730.o > > obj-$(CONFIG_SENSORS_MAX20751) += max20751.o > > +obj-$(CONFIG_SENSORS_MAX20826) += max20826.o > > obj-$(CONFIG_SENSORS_MAX20830) += max20830.o > > obj-$(CONFIG_SENSORS_MAX20860A) += max20860a.o > > obj-$(CONFIG_SENSORS_MAX31785) += max31785.o > > diff --git a/drivers/hwmon/pmbus/max20826.c b/drivers/hwmon/pmbus/max20826.c > > new file mode 100644 > > index 000000000000..1b481cdd8c93 > > --- /dev/null > > +++ b/drivers/hwmon/pmbus/max20826.c > > @@ -0,0 +1,1037 @@ > > +// SPDX-License-Identifier: GPL-2.0-only > > +/* > > + * Hardware monitoring driver for Analog Devices MAX20826 PMBus device > > + * > > + * Copyright 2026 Analog Devices Inc. > > + */ > > +#include <linux/bitfield.h> > > +#include <linux/bitops.h> > > +#include <linux/bits.h> > > +#include <linux/cleanup.h> > > +#include <linux/device.h> > > +#include <linux/err.h> > > +#include <linux/hwmon-sysfs.h> > > +#include <linux/i2c.h> > > +#include <linux/gpio/consumer.h> > > +#include <linux/module.h> > > +#include <linux/mod_devicetable.h> > > +#include <linux/pmbus.h> > > +#include <linux/property.h> > > +#include <linux/regulator/driver.h> > > +#include <linux/slab.h> > > +#include <linux/sysfs.h> > > +#include <linux/string.h> > > +#include <linux/types.h> > > +#include <linux/unaligned.h> > > + > > +#include "pmbus.h" > > + > > +#define MAX20826_REG_RAIL_PHASE_CFG 0xB1 > > +#define MAX20826_REG_CTRL_MISC 0xCB > > +#define MAX20826_OPL_EN_MSK BIT(7) > > + > > +#define MAX20826_REG_VOUT_RES 0xDB > > +#define MAX20826_VOUT_RES_MSK BIT(7) > > + > > +#define MAX20855B_REG_VOUT_VRM 0xD1 > > +#define MAX20855B_REG_VOUT_VRM_MASK BIT(4) > > + > > +#define MAX20826_REG_C_MODEAB 0xDC > > +/* Byte 1 bits 7 and 6*/ > > +#define MAX20826_C_MODEAB_MASK GENMASK(15, 14) > > + > > +#define MAX20826_REG_ADDR_MODE 0xEC > > +#define MAX20826_PAGE_MODE_MSK BIT(7) > > +#define MAX20826_DIRECT_ADDR_MSK GENMASK(6, 0) > > + > > +#define MAX20826_REG_OVERRIDE 0xED > > +#define MAX20826_OVERRIDE_MASK BIT(7) > > + > > +#define MAX20826_REG_PHASE_DETECT 0xF3 > > +#define MAX20826_REG_PHASE_READ 0xF4 > > + > > +#define MAX20826_REG_STATUS_MON 0xF9 > > +#define MAX20826_PHASES_NUM_MASK GENMASK(7, 3) > > + > > +#define MAX20826_MAX_PAGES 2 > > +#define MAX20826_MAX_PHASES 16 > > +#define MAX20826_PHASES_PER_PAGE 8 > > +#define MAX20826_INTF_PWMVID 1 > > +#define MAX20826_INTF_AVSBUS 3 > > + > > +#define MAX20855B_PHASES_NUM_MASK GENMASK(7, 4) > > +#define MAX20855B_MAX_PHASES 8 > > +#define MAX20908_MAX_PHASES 8 > > +#define MAX20912_MAX_PHASES 12 > > +#define MAX20916_MAX_PHASES 16 > > + > > +struct max20826_chip_info { > > + const char *vendor_bus_name; > > + u8 max_phases; > > + unsigned int phase_num_mask; > > + u8 start_index_iin; > > + u8 start_index_iout; > > + bool is_reg_addr_mode_block; > > + bool is_vout_direct; > > + bool select_vrm; > > + bool has_avsbus; > > + bool has_opl; > > + u8 (*count_phases)(const u8 *config, int page); > > +}; > > + > > +struct max20826 { > > + const struct max20826_chip_info *chip_info; > > + struct pmbus_driver_info info; > > + struct i2c_client *client; > > + /* RAIL-B direct mode */ > > + struct i2c_client *client_b; > > + struct i2c_client *curr_client; > > + struct gpio_desc *avren; > > + struct gpio_desc *bvren; > > + bool vendor_bus; > > + bool high_speed[MAX20826_MAX_PAGES]; > > + bool on_off_ctrl[MAX20826_MAX_PAGES]; > > + bool opl_enabled[MAX20826_MAX_PAGES]; > > +}; > > + > > +static u8 __max20826_count_phases(const u8 *config, int page) > > +{ > > + if (page) > > + return hweight8(config[4]); > > + > > + return hweight8(config[0]) + hweight8(config[1]) - > > + hweight8(config[4]); > > +} > > + > > +static u8 __max20855b_count_phases(const u8 *config, int page) > > +{ > > + if (page) > > + return hweight8(config[3] & 0x3F); > > + > > + return hweight8(config[0]) + hweight8(config[1] & 0x0F) - > > + hweight8(config[3] & 0x3F); > > +} > > + > > +static u8 __max20908_count_phases(const u8 *config, int page) > > +{ > > + if (page) > > + return hweight8(config[4]); > > + > > + return hweight8(config[0]) + hweight8(config[1] & 0xF0) - > > + hweight8(config[4]); > > +} > > + > > +static u8 __max20912_count_phases(const u8 *config, int page) > > +{ > > + if (page) > > + return hweight8(config[4]); > > + > > + return hweight8(config[0]) + hweight8(config[1] & 0xFC) - > > + hweight8(config[4]); > > +} > > + > > +#define to_max20826(p) container_of(p, struct max20826, info) > > + > > +enum { > > + RAIL_A, > > + RAIL_B, > > +}; > > + > > +static const struct regulator_desc __maybe_unused max20826_reg_desc[] = { > > + PMBUS_REGULATOR("vout", 0), > > + PMBUS_REGULATOR("vout", 1), > > +}; > > + > > +static struct i2c_client *max20826_select_rail(struct max20826 *st, > > + int page, bool probing) > > +{ > > + int ret; > > + > > + /* > > + * If in direct mode and we want RAIL_B (page 1) just return client_b. > > + * Otherwise, set the proper page (if page mode) and return RAIL_A. > > + */ > > + if (st->client_b) { > > + /* if 0xff just return the last client */ > > + if (page < 0) > > + return st->curr_client; > > + if (page) > > + st->curr_client = st->client_b; > > + else > > + st->curr_client = st->client; > > + > > + return st->curr_client; > > + } > > + > > + if (!probing) > > + ret = pmbus_set_page(st->client, page, 0xff); > > + else > > + ret = i2c_smbus_write_byte_data(st->client, PMBUS_PAGE, page); > > + if (ret < 0) > > + return ERR_PTR(ret); > > + > > + return st->client; > > +} > > + > > +static int max20826_update_byte_data_unsafe(const struct i2c_client *client, > > + int reg, u8 mask, u8 value) > > +{ > > + int ret; > > + > > + ret = i2c_smbus_read_byte_data(client, reg); > > + if (ret < 0) > > + return ret; > > + > > + value = (ret & ~mask) | (value & mask); > > + > > + return i2c_smbus_write_byte_data(client, reg, value); > > +} > > Why not use pmbus_update_byte_data() ? Good question! I think the main reason at the time was max20826_high_speed_en_store(). But now that I look at it again, not really seeing any reason why I can't use pmbus_update_byte_data(). > > > + > > +static int max20826_read_block_data_unsafe(const struct i2c_client *client, > > + int reg, u8 *block, u8 size) > > +{ > > + u8 block_data[I2C_SMBUS_BLOCK_MAX]; > > + int ret; > > + > > + ret = i2c_smbus_read_i2c_block_data(client, reg, size + 1, > > + block_data); > > size can be up to I2C_SMBUS_BLOCK_MAX (32). With that, the above code > reads up to 33 bytes, which would exceed the size of block_data[]. > > > + if (ret < 0) > > + return ret; > > + if (ret < size + 1) > > + return -EIO; > > No, this is wrong. The function needs to return the length of the returned data, > which can be up to <size> and does not have to be an exact match. > > > + > > + /* byte 0 comes with the block length, discard it */ > > + memcpy(block, block_data + 1, size); > > + return size; > > This is expected to return the actual data size, i.e., the value > in block_data[0]. I see and in theory I think it was returning the actual data size even though with the above code block_data[0] would be size + 1. Anyways, all of the above will go away with the new helper. > > +} > > + > > +static int __max20826_read_block_data(struct max20826 *st, int page, > > + int reg, u8 *block, u8 size) > > +{ > > + struct i2c_client *rail; > > + > > + rail = max20826_select_rail(st, page, false); > > + if (IS_ERR(rail)) > > + return PTR_ERR(rail); > > + > > + return max20826_read_block_data_unsafe(rail, reg, block, size); > > +} > > + > > +static int __max20826_read_byte_data(struct max20826 *st, int page, int reg) > > +{ > > + struct i2c_client *rail; > > + > > + rail = max20826_select_rail(st, page, false); > > + if (IS_ERR(rail)) > > + return PTR_ERR(rail); > > + > > + return i2c_smbus_read_byte_data(rail, reg); > > +} > > + > > +static int __max20826_write_byte_data(struct max20826 *st, int page, int reg, > > + u8 value) > > +{ > > + struct i2c_client *rail; > > + > > + rail = max20826_select_rail(st, page, false); > > + if (IS_ERR(rail)) > > + return PTR_ERR(rail); > > + > > + return i2c_smbus_write_byte_data(rail, reg, value); > > +} > > + > > +static int __max20826_read_word_data(struct max20826 *st, int page, int reg) > > +{ > > + struct i2c_client *rail; > > + > > + rail = max20826_select_rail(st, page, false); > > + if (IS_ERR(rail)) > > + return PTR_ERR(rail); > > + > > + return i2c_smbus_read_word_data(rail, reg); > > +} > > + > > +static ssize_t max20826_high_speed_en_show(struct device *dev, > > + struct device_attribute *devattr, > > + char *buf) > > +{ > > + struct i2c_client *client = to_i2c_client(dev->parent); > > + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); > > + unsigned int page = to_sensor_dev_attr(devattr)->index; > > + struct max20826 *st = to_max20826(info); > > + > > + return sysfs_emit(buf, "%u\n", st->high_speed[page]); > > +} > > + > > +static ssize_t max20826_high_speed_en_store(struct device *dev, > > + struct device_attribute *devattr, > > + const char *buf, size_t count) > > +{ > > + struct i2c_client *client = to_i2c_client(dev->parent); > > + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); > > + unsigned int page = to_sensor_dev_attr(devattr)->index; > > + struct max20826 *st = to_max20826(info); > > + struct i2c_client *rail; > > + bool high_speed; > > + int ret; > > + > > + ret = kstrtobool(buf, &high_speed); > > + if (ret) > > + return ret; > > + > > + guard(pmbus_lock)(client); > > + > > + rail = max20826_select_rail(st, page, false); > > + if (IS_ERR(rail)) > > + return PTR_ERR(rail); > > + > > + if (!high_speed) { > > + if (!st->vendor_bus && st->chip_info->has_avsbus) { > > + ret = max20826_update_byte_data_unsafe(rail, > > + PMBUS_OPERATION, > > + PB_OPERATION_CONTROL_V_SRC, 0); > > + if (ret) > > + return ret; > > + > > + goto out_success; > > + } > > + > > + ret = max20826_update_byte_data_unsafe(rail, > > + MAX20826_REG_OVERRIDE, > > + MAX20826_OVERRIDE_MASK, > > + FIELD_PREP(MAX20826_OVERRIDE_MASK, 1)); > > + if (ret) > > + return ret; > > + > > + goto out_success; > > + } > > + > > + if (!st->vendor_bus) { > > + ret = max20826_update_byte_data_unsafe(rail, PMBUS_OPERATION, > > + PB_OPERATION_CONTROL_V_SRC, > > + FIELD_PREP(PB_OPERATION_CONTROL_V_SRC, 3)); > > + if (ret) > > + return ret; > > + > > + goto out_success; > > + } > > + > > + ret = max20826_update_byte_data_unsafe(rail, MAX20826_REG_OVERRIDE, > > + MAX20826_OVERRIDE_MASK, 0); > > + if (ret) > > + return ret; > > + > > +out_success: > > + st->high_speed[page] = high_speed; > > + return count; > > +} > > + > > +static ssize_t max20826_high_speed_bus_show(struct device *dev, > > + struct device_attribute *devattr, > > + char *buf) > > +{ > > + struct i2c_client *client = to_i2c_client(dev->parent); > > + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); > > + struct max20826 *st = to_max20826(info); > > + > > + if (st->vendor_bus) > > + return sysfs_emit(buf, "%s\n", st->chip_info->vendor_bus_name); > > + > > + return sysfs_emit(buf, "AVSBus\n"); > > +} > > + > > +static SENSOR_DEVICE_ATTR_RW(in2_high_speed_en, max20826_high_speed_en, 0); > > +static SENSOR_DEVICE_ATTR_RW(in3_high_speed_en, max20826_high_speed_en, 1); > > +static SENSOR_DEVICE_ATTR_RO(in_high_speed_bus, max20826_high_speed_bus, 0); > > Those don't look like the values should or would be changed on the fly. > Should that be devicetree properties ? > Devicetree makes it easier to handle but let me ask around to people who knows better about these chips usecases. - Nuno Sá