diff mbox series

[2/2] drivers: hwmon: add driver for max42500

Message ID 20241220012003.9568-3-Kent.Libetario@analog.com (mailing list archive)
State Changes Requested
Headers show
Series Add driver for MAX42500 | expand

Commit Message

Kent Libetario Dec. 20, 2024, 1:20 a.m. UTC
Add the core implementation of the MAX42500 hwmon driver.

Signed-off-by: Kent Libetario <Kent.Libetario@analog.com>
---
 MAINTAINERS              |    1 +
 drivers/hwmon/Kconfig    |   13 +
 drivers/hwmon/Makefile   |    1 +
 drivers/hwmon/max42500.c | 1049 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 1064 insertions(+)
 create mode 100644 drivers/hwmon/max42500.c

Comments

kernel test robot Dec. 21, 2024, 5:01 a.m. UTC | #1
Hi Kent,

kernel test robot noticed the following build warnings:

[auto build test WARNING on de076198d1e4934c5fc17aa52d5f1884f469ce1a]

url:    https://github.com/intel-lab-lkp/linux/commits/Kent-Libetario/dt-bindings-hwmon-add-adi-max42500-yaml/20241220-092728
base:   de076198d1e4934c5fc17aa52d5f1884f469ce1a
patch link:    https://lore.kernel.org/r/20241220012003.9568-3-Kent.Libetario%40analog.com
patch subject: [PATCH 2/2] drivers: hwmon: add driver for max42500
config: x86_64-randconfig-r072-20241221 (https://download.01.org/0day-ci/archive/20241221/202412211209.zqypWgoc-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241221/202412211209.zqypWgoc-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202412211209.zqypWgoc-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/hwmon/max42500.c:206: warning: Cannot understand  * @brief Read a raw value from a register.
    on line 206 - I thought it was a doc line
>> drivers/hwmon/max42500.c:251: warning: Cannot understand  * @brief Write a raw value to a register.
    on line 251 - I thought it was a doc line
>> drivers/hwmon/max42500.c:278: warning: Cannot understand  * @brief Update a register's value based on a mask.
    on line 278 - I thought it was a doc line
>> drivers/hwmon/max42500.c:298: warning: Cannot understand  * @brief Set nominal voltage for VM1 to VM5.
    on line 298 - I thought it was a doc line
>> drivers/hwmon/max42500.c:331: warning: Cannot understand  * @brief Get the status of the voltage monitor input.
    on line 331 - I thought it was a doc line
>> drivers/hwmon/max42500.c:390: warning: Cannot understand  * @brief Set the overvoltage threshold of VM1 to VM5.
    on line 390 - I thought it was a doc line
>> drivers/hwmon/max42500.c:418: warning: Cannot understand  * @brief Set the overvoltage threshold of VM6 and VM7.
    on line 418 - I thought it was a doc line
>> drivers/hwmon/max42500.c:446: warning: Cannot understand  * @brief Set the undervoltage threshold of VM1 to VM5.
    on line 446 - I thought it was a doc line
>> drivers/hwmon/max42500.c:473: warning: Cannot understand  * @brief Set the undervoltage threshold of VM6 and VM7.
    on line 473 - I thought it was a doc line
>> drivers/hwmon/max42500.c:501: warning: Cannot understand  * @brief Get the FPS clock divider value.
    on line 501 - I thought it was a doc line
>> drivers/hwmon/max42500.c:520: warning: Cannot understand  * @brief Get power-up timestamp for a specified voltage monitor input.
    on line 520 - I thought it was a doc line
>> drivers/hwmon/max42500.c:551: warning: Cannot understand  * @brief Get power-down timestamp for a specified voltage monitor input.
    on line 551 - I thought it was a doc line
>> drivers/hwmon/max42500.c:582: warning: Cannot understand  * @brief Enable/Disable watchdog
    on line 582 - I thought it was a doc line
>> drivers/hwmon/max42500.c:604: warning: Cannot understand  * @brief 8-bit watchdog key computation.
    on line 604 - I thought it was a doc line
>> drivers/hwmon/max42500.c:630: warning: Cannot understand  * @brief Update the watchdog key based on the mode and current value.
    on line 630 - I thought it was a doc line


vim +206 drivers/hwmon/max42500.c

   203	
   204	/************************ Functions Definitions **************************/
   205	/**
 > 206	 * @brief Read a raw value from a register.
   207	 * @return 0 in case of success, error code otherwise.
   208	 */
   209	static int max42500_reg_read(struct max42500_state *st,
   210									u8 reg_addr, u8 *reg_data)
   211	{
   212		int ret;
   213		u8 i2c_data[MAX42500_I2C_RD_FRAME_SIZE] = {0};
   214		u8 bytes_num;
   215		u8 pece_value;
   216	
   217		/* PEC is computed over entire I2C frame from first START condition */
   218		i2c_data[0] = (st->client->addr << 1);
   219		i2c_data[1] = reg_addr;
   220		i2c_data[2] = (st->client->addr << 1) | 0x1;
   221	
   222		/* I2C write target address */
   223		bytes_num = 1;
   224	
   225		ret = regmap_bulk_write(st->regmap, reg_addr, &i2c_data[1], bytes_num);
   226		if (ret)
   227			return ret;
   228	
   229		/* Change byte count if PECE is enabled (1-byte data. 1-byte PEC) */
   230		bytes_num = (st->config->pece) ? 2 : bytes_num;
   231	
   232		ret = regmap_bulk_read(st->regmap, reg_addr, &i2c_data[3], bytes_num);
   233		if (ret)
   234			return ret;
   235	
   236		if (st->config->pece) {
   237			/* Compute CRC over entire I2C frame */
   238			pece_value = crc8(max42500_crc8, i2c_data,
   239								(MAX42500_I2C_RD_FRAME_SIZE - 1), 0);
   240	
   241			if (i2c_data[4] != pece_value)
   242				return -EIO;
   243		}
   244	
   245		*reg_data = i2c_data[3];
   246	
   247		return 0;
   248	}
   249	
   250	/**
 > 251	 * @brief Write a raw value to a register.
   252	 * @return 0 in case of success, negative error code otherwise.
   253	 */
   254	static int max42500_reg_write(struct max42500_state *st,
   255									u8 reg_addr, u8 data)
   256	{
   257		u8 i2c_data[MAX42500_I2C_WR_FRAME_SIZE] = {0};
   258		u8 bytes_num;
   259		u8 pece_value;
   260	
   261		bytes_num = (st->config->pece) ? (MAX42500_I2C_WR_FRAME_SIZE - 1) : 2;
   262		i2c_data[0] = (st->client->addr << 1);
   263		i2c_data[1] = reg_addr;
   264		i2c_data[2] = (u8)(data & 0xFF);
   265	
   266		pece_value = 0;
   267		if (st->config->pece)
   268			pece_value = crc8(max42500_crc8, i2c_data, bytes_num, 0);
   269	
   270		i2c_data[0] = i2c_data[1];
   271		i2c_data[1] = i2c_data[2];
   272		i2c_data[2] = pece_value;
   273	
   274		return regmap_bulk_write(st->regmap, reg_addr, i2c_data, bytes_num);
   275	}
   276	
   277	/**
 > 278	 * @brief Update a register's value based on a mask.
   279	 * @return 0 in case of success, negative error code otherwise.
   280	 */
   281	static int max42500_reg_update(struct max42500_state *st,
   282									u8 reg_addr, u8 mask, u8 data)
   283	{
   284		int ret;
   285		u8 reg_data;
   286	
   287		ret = max42500_reg_read(st, reg_addr, &reg_data);
   288		if (ret)
   289			return ret;
   290	
   291		reg_data &= ~mask;
   292		reg_data |= mask & data;
   293	
   294		return max42500_reg_write(st, reg_addr, reg_data);
   295	}
   296	
   297	/**
 > 298	 * @brief Set nominal voltage for VM1 to VM5.
   299	 * @return 0 in case of success, negative error code otherwise.
   300	 */
   301	static int max42500_set_nominal_voltage(struct max42500_state *st,
   302		enum max42500_vm_input vm_in, u8 voltage)
   303	{
   304		u8 reg_addr;
   305	
   306		switch (vm_in) {
   307		case MAX42500_VM1:
   308		case MAX42500_VM2:
   309		case MAX42500_VM3:
   310		case MAX42500_VM4:
   311			if (voltage < MAX42500_MIN_VNOM ||
   312				voltage > MAX42500_VNOM_MAX_VM1_VM4)
   313				return -EINVAL;
   314			reg_addr = MAX42500_REG_VIN1 + vm_in;
   315			break;
   316		case MAX42500_VM5:
   317			if (voltage < MAX42500_MIN_VNOM ||
   318				voltage > MAX42500_VNOM_MAX_VM5)
   319				return -EINVAL;
   320			reg_addr = MAX42500_REG_VIN5;
   321			break;
   322		default:
   323			return -EINVAL;
   324		}
   325	
   326		st->nominal_volt[vm_in] = voltage;
   327		return max42500_reg_write(st, reg_addr, voltage);
   328	}
   329	
   330	/**
 > 331	 * @brief Get the status of the voltage monitor input.
   332	 * @return 0 in case of success, negative error code otherwise.
   333	 */
   334	static int max42500_get_comp_status(struct max42500_state *st,
   335										u8 vm_in, u8 *status)
   336	{
   337		int ret;
   338		u8 reg_addr;
   339		u8 vm_in_status;
   340	
   341		switch (vm_in % MAX42500_COMP_STAT_MAX) {
   342		case MAX42500_COMP_STAT_OFF:
   343			reg_addr = MAX42500_REG_STATOFF;
   344			break;
   345		case MAX42500_COMP_STAT_UV:
   346			reg_addr = MAX42500_REG_STATUV;
   347			break;
   348		case MAX42500_COMP_STAT_OV:
   349			reg_addr = MAX42500_REG_STATOV;
   350			break;
   351		default:
   352			return -EINVAL;
   353		}
   354	
   355		ret = max42500_reg_read(st, reg_addr, &vm_in_status);
   356		if (ret)
   357			return ret;
   358	
   359		switch (vm_in % MAX42500_VM_MAX) {
   360		case MAX42500_VM1:
   361			*status = (u8)FIELD_GET(BIT(MAX42500_VM1), vm_in_status);
   362			break;
   363		case MAX42500_VM2:
   364			*status = (u8)FIELD_GET(BIT(MAX42500_VM2), vm_in_status);
   365			break;
   366		case MAX42500_VM3:
   367			*status = (u8)FIELD_GET(BIT(MAX42500_VM3), vm_in_status);
   368			break;
   369		case MAX42500_VM4:
   370			*status = (u8)FIELD_GET(BIT(MAX42500_VM4), vm_in_status);
   371			break;
   372		case MAX42500_VM5:
   373			*status = (u8)FIELD_GET(BIT(MAX42500_VM5), vm_in_status);
   374			break;
   375		case MAX42500_VM6:
   376			*status = (u8)FIELD_GET(BIT(MAX42500_VM6), vm_in_status);
   377			break;
   378		case MAX42500_VM7:
   379			*status = (u8)FIELD_GET(BIT(MAX42500_VM7), vm_in_status);
   380			break;
   381		default:
   382			return -EINVAL;
   383		}
   384		st->comp_status[vm_in] = *status;
   385	
   386		return 0;
   387	}
   388	
   389	/**
 > 390	 * @brief Set the overvoltage threshold of VM1 to VM5.
   391	 * @return 0 in case of success, negative error code otherwise.
   392	 */
   393	static int max42500_set_ov_thresh1(struct max42500_state *st,
   394		enum max42500_vm_input vm_in, u8 thresh)
   395	{
   396		if (thresh < MAX42500_MIN_THRESH_VM1_VM5 ||
   397			thresh > MAX42500_MAX_THRESH_VM1_VM5)
   398			return -EINVAL;
   399	
   400		switch (vm_in) {
   401		case MAX42500_VM1:
   402		case MAX42500_VM2:
   403		case MAX42500_VM3:
   404		case MAX42500_VM4:
   405		case MAX42500_VM5:
   406			st->ov_thresh1[vm_in] = thresh;
   407			return max42500_reg_update(st,
   408									MAX42500_REG_OVUV1 + vm_in,
   409									GENMASK(7, 4),
   410									FIELD_PREP(GENMASK(7, 4),
   411									thresh));
   412		default:
   413			return -EINVAL;
   414		}
   415	}
   416
Guenter Roeck Dec. 21, 2024, 6:08 p.m. UTC | #2
On Fri, Dec 20, 2024 at 09:20:03AM +0800, Kent Libetario wrote:
> Add the core implementation of the MAX42500 hwmon driver.
> 

Please have a look into Documentation/process/submitting-patches.rst
and follow the guidance about describing patches.

> Signed-off-by: Kent Libetario <Kent.Libetario@analog.com>
> ---
>  MAINTAINERS              |    1 +
>  drivers/hwmon/Kconfig    |   13 +
>  drivers/hwmon/Makefile   |    1 +
>  drivers/hwmon/max42500.c | 1049 ++++++++++++++++++++++++++++++++++++++

Documentation missing.

There is a large number of checkpatch CHECK messages, almost all about
multi-line alignment. Indeed, when looking through the code,
multi-line aligment seems arbitrary. Please fix.

PEC support should be implemented through the i2c controller driver.
Please see other drivers such as lm90.c or max31827.c for examples
how to do that.

Some more comments inline. The review is partial. The major problem
I see is that there are not just a few but _lots_ of ABI violations.
This is unacceptable. Please rework the driver and follow the hwmon ABI
for all sysfs attributes.

Guenter

>  4 files changed, 1064 insertions(+)
>  create mode 100644 drivers/hwmon/max42500.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d1703b4834c8..434191e16dd5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14051,6 +14051,7 @@ M:	Kent Libetario <kent.libetario@analog.com>
>  L:	linux-hwmon@vger.kernel.org
>  S:	Supported
>  F:	Documentation/devicetree/bindings/hwmon/adi,max42500.yaml
> +F:	drivers/hwmon/max42500.c
>  
>  MAX6650 HARDWARE MONITOR AND FAN CONTROLLER DRIVER
>  L:	linux-hwmon@vger.kernel.org
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index dd376602f3f1..ec0d7aad7789 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1220,6 +1220,19 @@ config MAX31827
>  	  This driver can also be built as a module.  If so, the module
>  	  will be called max31827.
>  
> +config SENSORS_MAX42500
> +	tristate "MAX42500 Industrial Power System Monitor Family"
> +	depends on I2C
> +	select REGMAP_I2C
> +	help
> +	  If you say yes here you get support for MAX42500 SoC power-system monitor
> +	  with up to seven voltage monitor. The driver also contains a  programmable
> +	  challenge/response watchdog, which is accessible through the I2C interface,
> +	  along with a configurable RESET output.
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called max42500.
> +
>  config SENSORS_MAX6620
>  	tristate "Maxim MAX6620 fan controller"
>  	depends on I2C
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index b827b92f2a78..d27d8fc01141 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -152,6 +152,7 @@ obj-$(CONFIG_SENSORS_MAX197)	+= max197.o
>  obj-$(CONFIG_SENSORS_MAX31722)	+= max31722.o
>  obj-$(CONFIG_SENSORS_MAX31730)	+= max31730.o
>  obj-$(CONFIG_SENSORS_MAX31760)  += max31760.o
> +obj-$(CONFIG_SENSORS_MAX42500)  += max42500.o
>  obj-$(CONFIG_SENSORS_MAX6620)	+= max6620.o
>  obj-$(CONFIG_SENSORS_MAX6621)	+= max6621.o
>  obj-$(CONFIG_SENSORS_MAX6639)	+= max6639.o
> diff --git a/drivers/hwmon/max42500.c b/drivers/hwmon/max42500.c
> new file mode 100644
> index 000000000000..23b90c766767
> --- /dev/null
> +++ b/drivers/hwmon/max42500.c
> @@ -0,0 +1,1049 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * MAX42500 - Industrial Power System Monitor
> + *
> + * Copyright 2024 Analog Devices Inc.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/jiffies.h>
> +#include <linux/slab.h>
> +#include <linux/crc8.h>
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/err.h>
> +#include <linux/gpio.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/hwmon.h>
> +#include <linux/i2c.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +
> +/* Implements Polynomial X^8 + X^2 + X^1 +1 */
> +#define CRC8_PEC		0x07
> +
> +DECLARE_CRC8_TABLE(max42500_crc8);
> +
> +#define MAX42500_REG_ID					0x00
> +#define MAX42500_REG_CONFIG1			0x01
> +#define MAX42500_REG_CONFIG2			0x02
> +#define MAX42500_REG_VMON				0x03
> +#define MAX42500_REG_RSTMAP				0x04
> +#define MAX42500_REG_STATOV				0x05
> +#define MAX42500_REG_STATUV				0x06
> +#define MAX42500_REG_STATOFF			0x07
> +#define MAX42500_REG_VIN1				0x08
> +#define MAX42500_REG_VIN2				0x09
> +#define MAX42500_REG_VIN3				0x0A
> +#define MAX42500_REG_VIN4				0x0B
> +#define MAX42500_REG_VIN5				0x0C
> +#define MAX42500_REG_VINO6				0x0D
> +#define MAX42500_REG_VINU6				0x0E
> +#define MAX42500_REG_VINO7				0x0F
> +#define MAX42500_REG_VINU7				0x10
> +#define MAX42500_REG_OVUV1				0x11
> +#define MAX42500_REG_OVUV2				0x12
> +#define MAX42500_REG_OVUV3				0x13
> +#define MAX42500_REG_OVUV4				0x14
> +#define MAX42500_REG_OVUV5				0x15
> +#define MAX42500_REG_FPSSTAT1			0x16
> +#define MAX42500_REG_FPSCFG1			0x17
> +#define MAX42500_REG_UTIME1				0x18
> +#define MAX42500_REG_UTIME2				0x19
> +#define MAX42500_REG_UTIME3				0x1A
> +#define MAX42500_REG_UTIME4				0x1B
> +#define MAX42500_REG_UTIME5				0x1C
> +#define MAX42500_REG_UTIME6				0x1D
> +#define MAX42500_REG_UTIME7				0x1E
> +#define MAX42500_REG_DTIME1				0x1F
> +#define MAX42500_REG_DTIME2				0x20
> +#define MAX42500_REG_DTIME3				0x21
> +#define MAX42500_REG_DTIME4				0x22
> +#define MAX42500_REG_DTIME5				0x23
> +#define MAX42500_REG_DTIME6				0x24
> +#define MAX42500_REG_DTIME7				0x25
> +#define MAX42500_REG_WDSTAT				0x26
> +#define MAX42500_REG_WDCDIV				0x27
> +#define MAX42500_REG_WDCFG1				0x28
> +#define MAX42500_REG_WDCFG2				0x29
> +#define MAX42500_REG_WDKEY				0x2A
> +#define MAX42500_REG_WDLOCK				0x2B
> +#define MAX42500_REG_RSTCTRL			0x2C
> +#define MAX42500_REG_CID				0x2D
> +
> +/** X is set based on the pull configuration of the ADDR pin */
> +#define MAX42500_ADDR(x)				(0x28 + (x))
> +#define MAX42500_SILICON_ID				(0x30)
> +#define MAX42500_I2C_WR_FRAME_SIZE		(4)
> +#define MAX42500_I2C_RD_FRAME_SIZE		(5)
> +
> +/** MAX42500 Nominal voltage computation */
> +#define MAX42500_VNOM_MAX_VM1_VM4		0xFF	// 3.6875v
> +#define MAX42500_VNOM_MAX_VM5			0xFF	// 5.6v
> +#define MAX42500_MIN_VNOM				0x00	// 0.5v
> +#define MAX42500_VNOM_STEP_VM1_VM4		0x01	// 0.0125v
> +#define MAX42500_VNOM_STEP_VM5			0x01	// 0.02v
> +
> +/** MAX42500 Undervoltage/Overvoltage maximum and minimum thresholds*/
> +#define MAX42500_MAX_THRESH_VM1_VM5		0x0F	// OV = 110.0%, UV = 90.0%
> +#define MAX42500_MIN_THRESH_VM1_VM5		0x00	// OV = 102.5%, UV = 97.5%
> +#define MAX42500_MAX_THRESH_VM6_V7		0xFF	// 1.775v
> +#define MAX42500_MIN_THRESH_VM6_V7		0x00	// 0.5v
> +
> +/* CONFIG1 bit masks */
> +#define MAX42500_CONFIG1_PECE_MASK		BIT(0)
> +#define MAX42500_CONFIG1_MBST_MASK		BIT(1)
> +#define MAX42500_CONFIG1_RR_MASK		BIT(2)
> +
> +/* VMON bit masks */
> +#define MAX42500_VMON_IN_MASK(bit)		BIT(bit)
> +#define MAX42500_VMON_VMPD_MASK			BIT(7)
> +
> +/* RSTMAP bit masks */
> +#define MAX42500_RSTMAP_IN_MASK(bit)	BIT(bit)
> +#define MAX42500_RSTMAP_PARM_MASK		BIT(7)
> +
> +/* WDCDIV bit masks */
> +#define MAX42500_WDCDIV_SWW_MASK		BIT(6)
> +#define MAX42500_WDCDIV_WDIC_MASK		(0x3F)
> +
> +/* WDCFG2 bit masks */
> +#define MAX42500_WDCFG2_WDEN_MASK		BIT(3)
> +#define MAX42500_WDCFG2_1UP_MASK		(0x7)
> +
> +/* WDLOCK bit masks */
> +#define MAX42500_WDLOCK_LOCK_MASK		BIT(0)
> +
> +/* RSTCTRL bit masks */
> +#define MAX42500_RSTCTRL_MR1_MASK		BIT(2)
> +#define MAX42500_RSTCTRL_RHLD_MASK		(0x3)
> +
> +/* MAX42500 device status */
> +enum max42500_status {
> +	MAX42500_STATUS_OFF,
> +	MAX42500_STATUS_SLEEP,
> +	MAX42500_STATUS_ON,
> +	MAX42500_STATUS_MAX
> +};
> +
> +/* MAX42500 voltage monitor input */
> +enum max42500_vm_input {
> +	MAX42500_VM1,
> +	MAX42500_VM2,
> +	MAX42500_VM3,
> +	MAX42500_VM4,
> +	MAX42500_VM5,
> +	MAX42500_VM6,
> +	MAX42500_VM7,
> +	MAX42500_VM_MAX
> +};
> +
> +/* MAX42500 comparator status */
> +enum max42500_comp_stat {
> +	MAX42500_COMP_STAT_OFF,
> +	MAX42500_COMP_STAT_UV,
> +	MAX42500_COMP_STAT_OV,
> +	MAX42500_COMP_STAT_MAX
> +};
> +
> +/* MAX42500 watchdog mode */
> +enum max42500_wd_mode {
> +	MAX42500_WD_MODE_CH_RESP,
> +	MAX42500_WD_MODE_SIMPLE,
> +	MAX42500_WD_MODE_MAX
> +};
> +
> +/* MAX42500 reset hold/active timeout time. */
> +enum max42500_wd_rhld {
> +	MAX42500_WD_RHOLD_0_MS,
> +	MAX42500_WD_RHOLD_8_MS,
> +	MAX42500_WD_RHOLD_16_MS,
> +	MAX42500_WD_RHOLD_32_MS,
> +	MAX42500_WD_RHOLD_MAX
> +};
> +
> +struct max42500_config {
> +	/* Packet error checking enable */
> +	u8 pece;
> +	/* Enabled voltage monitor inputs */
> +	u8 vmon_en;
> +	/* Voltage monitor power down enable */
> +	u8 vmon_vmpd;
> +	/* Enabled voltage monitor reset mapping */
> +	u8 reset_map;
> +	/* Watchdog mode */
> +	enum max42500_wd_mode wd_mode;
> +	/* Watchdog clock div */
> +	u8 wd_cdiv;
> +	/* Watchdog close window */
> +	u8 wd_close;
> +	/* Watchdog open window */
> +	u8 wd_open;
> +	/* Watchdog first update window */
> +	u8 wd_1ud;
> +	/* Watchdog enable */
> +	u8 wd_en;
> +};
> +
> +struct max42500_state {
> +	struct i2c_client *client;
> +	struct regmap *regmap;
> +	struct max42500_config *config;
> +	long pwrup_stamp[MAX42500_VM_MAX];
> +	long pwrdn_stamp[MAX42500_VM_MAX];
> +	u8 ov_thresh1[MAX42500_VM_MAX - 2];
> +	u8 ov_thresh2[MAX42500_VM_MAX - 5];
> +	u8 uv_thresh1[MAX42500_VM_MAX - 2];
> +	u8 uv_thresh2[MAX42500_VM_MAX - 5];
> +	u8 nominal_volt[MAX42500_VM_MAX - 2];
> +	u8 comp_status[MAX42500_VM_MAX * MAX42500_COMP_STAT_MAX];
> +};
> +
> +/************************ Functions Definitions **************************/
> +/**
> + * @brief Read a raw value from a register.
> + * @return 0 in case of success, error code otherwise.
> + */
> +static int max42500_reg_read(struct max42500_state *st,
> +								u8 reg_addr, u8 *reg_data)
> +{
> +	int ret;
> +	u8 i2c_data[MAX42500_I2C_RD_FRAME_SIZE] = {0};
> +	u8 bytes_num;
> +	u8 pece_value;
> +
> +	/* PEC is computed over entire I2C frame from first START condition */
> +	i2c_data[0] = (st->client->addr << 1);
> +	i2c_data[1] = reg_addr;
> +	i2c_data[2] = (st->client->addr << 1) | 0x1;
> +
> +	/* I2C write target address */
> +	bytes_num = 1;
> +
> +	ret = regmap_bulk_write(st->regmap, reg_addr, &i2c_data[1], bytes_num);
> +	if (ret)
> +		return ret;
> +
> +	/* Change byte count if PECE is enabled (1-byte data. 1-byte PEC) */
> +	bytes_num = (st->config->pece) ? 2 : bytes_num;
> +
> +	ret = regmap_bulk_read(st->regmap, reg_addr, &i2c_data[3], bytes_num);
> +	if (ret)
> +		return ret;
> +
> +	if (st->config->pece) {
> +		/* Compute CRC over entire I2C frame */
> +		pece_value = crc8(max42500_crc8, i2c_data,
> +							(MAX42500_I2C_RD_FRAME_SIZE - 1), 0);
> +
> +		if (i2c_data[4] != pece_value)
> +			return -EIO;
> +	}
> +
> +	*reg_data = i2c_data[3];
> +
> +	return 0;

This seems to re-implement PEC support in the driver. As mentioned, PEC support should
be handled in the i2c controller driver.

Also, _if_ it is for some reasons necessary to implement chip access functions,
those should be implemented as regmap bus. The functional part of the driver
should just use regmap functions to access the chip.

> +}
> +
> +/**
> + * @brief Write a raw value to a register.
> + * @return 0 in case of success, negative error code otherwise.
> + */
> +static int max42500_reg_write(struct max42500_state *st,
> +								u8 reg_addr, u8 data)
> +{
> +	u8 i2c_data[MAX42500_I2C_WR_FRAME_SIZE] = {0};
> +	u8 bytes_num;
> +	u8 pece_value;
> +
> +	bytes_num = (st->config->pece) ? (MAX42500_I2C_WR_FRAME_SIZE - 1) : 2;
> +	i2c_data[0] = (st->client->addr << 1);
> +	i2c_data[1] = reg_addr;
> +	i2c_data[2] = (u8)(data & 0xFF);
> +
> +	pece_value = 0;
> +	if (st->config->pece)
> +		pece_value = crc8(max42500_crc8, i2c_data, bytes_num, 0);
> +
> +	i2c_data[0] = i2c_data[1];
> +	i2c_data[1] = i2c_data[2];
> +	i2c_data[2] = pece_value;
> +
> +	return regmap_bulk_write(st->regmap, reg_addr, i2c_data, bytes_num);
> +}
> +
> +/**
> + * @brief Update a register's value based on a mask.
> + * @return 0 in case of success, negative error code otherwise.

Please fix.

> + */
> +static int max42500_reg_update(struct max42500_state *st,
> +								u8 reg_addr, u8 mask, u8 data)
> +{
> +	int ret;
> +	u8 reg_data;
> +
> +	ret = max42500_reg_read(st, reg_addr, &reg_data);
> +	if (ret)
> +		return ret;
> +
> +	reg_data &= ~mask;
> +	reg_data |= mask & data;
> +
> +	return max42500_reg_write(st, reg_addr, reg_data);
> +}
> +
> +/**
> + * @brief Set nominal voltage for VM1 to VM5.
> + * @return 0 in case of success, negative error code otherwise.
> + */
> +static int max42500_set_nominal_voltage(struct max42500_state *st,
> +	enum max42500_vm_input vm_in, u8 voltage)
> +{
> +	u8 reg_addr;
> +
> +	switch (vm_in) {
> +	case MAX42500_VM1:
> +	case MAX42500_VM2:
> +	case MAX42500_VM3:
> +	case MAX42500_VM4:
> +		if (voltage < MAX42500_MIN_VNOM ||
> +			voltage > MAX42500_VNOM_MAX_VM1_VM4)
> +			return -EINVAL;

The driver should use clamp_val() for value boundary control.

> +		reg_addr = MAX42500_REG_VIN1 + vm_in;
> +		break;
> +	case MAX42500_VM5:
> +		if (voltage < MAX42500_MIN_VNOM ||
> +			voltage > MAX42500_VNOM_MAX_VM5)
> +			return -EINVAL;
> +		reg_addr = MAX42500_REG_VIN5;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	st->nominal_volt[vm_in] = voltage;

I fail to understand how this is supposed to work. The locally
cached values are set (only) here, with a sysfs attribute. During
probe they are not set, meaning the input attribute won't be available
because it is only created if nominal_volt is set which is not the case
at that time. That doesn't make sense. There needs to be some default.

Also, the whole point of using regmap is that regmap provides
caching. The code should use that facility instead of caching locally.

> +	return max42500_reg_write(st, reg_addr, voltage);
> +}
> +
> +/**
> + * @brief Get the status of the voltage monitor input.
> + * @return 0 in case of success, negative error code otherwise.
> + */
> +static int max42500_get_comp_status(struct max42500_state *st,
> +									u8 vm_in, u8 *status)
> +{
> +	int ret;
> +	u8 reg_addr;
> +	u8 vm_in_status;
> +
> +	switch (vm_in % MAX42500_COMP_STAT_MAX) {
> +	case MAX42500_COMP_STAT_OFF:
> +		reg_addr = MAX42500_REG_STATOFF;
> +		break;
> +	case MAX42500_COMP_STAT_UV:
> +		reg_addr = MAX42500_REG_STATUV;
> +		break;
> +	case MAX42500_COMP_STAT_OV:
> +		reg_addr = MAX42500_REG_STATOV;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	ret = max42500_reg_read(st, reg_addr, &vm_in_status);
> +	if (ret)
> +		return ret;
> +
> +	switch (vm_in % MAX42500_VM_MAX) {
> +	case MAX42500_VM1:
> +		*status = (u8)FIELD_GET(BIT(MAX42500_VM1), vm_in_status);
> +		break;
> +	case MAX42500_VM2:
> +		*status = (u8)FIELD_GET(BIT(MAX42500_VM2), vm_in_status);
> +		break;
> +	case MAX42500_VM3:
> +		*status = (u8)FIELD_GET(BIT(MAX42500_VM3), vm_in_status);
> +		break;
> +	case MAX42500_VM4:
> +		*status = (u8)FIELD_GET(BIT(MAX42500_VM4), vm_in_status);
> +		break;
> +	case MAX42500_VM5:
> +		*status = (u8)FIELD_GET(BIT(MAX42500_VM5), vm_in_status);
> +		break;
> +	case MAX42500_VM6:
> +		*status = (u8)FIELD_GET(BIT(MAX42500_VM6), vm_in_status);
> +		break;
> +	case MAX42500_VM7:
> +		*status = (u8)FIELD_GET(BIT(MAX42500_VM7), vm_in_status);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	st->comp_status[vm_in] = *status;
> +
> +	return 0;
> +}
> +
> +/**
> + * @brief Set the overvoltage threshold of VM1 to VM5.
> + * @return 0 in case of success, negative error code otherwise.
> + */
> +static int max42500_set_ov_thresh1(struct max42500_state *st,
> +	enum max42500_vm_input vm_in, u8 thresh)
> +{
> +	if (thresh < MAX42500_MIN_THRESH_VM1_VM5 ||
> +		thresh > MAX42500_MAX_THRESH_VM1_VM5)
> +		return -EINVAL;
> +
> +	switch (vm_in) {
> +	case MAX42500_VM1:
> +	case MAX42500_VM2:
> +	case MAX42500_VM3:
> +	case MAX42500_VM4:
> +	case MAX42500_VM5:
> +		st->ov_thresh1[vm_in] = thresh;
> +		return max42500_reg_update(st,
> +								MAX42500_REG_OVUV1 + vm_in,
> +								GENMASK(7, 4),
> +								FIELD_PREP(GENMASK(7, 4),
> +								thresh));
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +/**
> + * @brief Set the overvoltage threshold of VM6 and VM7.
> + * @return 0 in case of success, negative error code otherwise.
> + */
> +static int max42500_set_ov_thresh2(struct max42500_state *st,
> +	enum max42500_vm_input vm_in, u8 thresh)
> +{
> +	u8 reg_addr;
> +
> +	if (thresh < MAX42500_MIN_THRESH_VM6_V7 ||
> +		thresh > MAX42500_MAX_THRESH_VM6_V7)
> +		return -EINVAL;
> +
> +	switch (vm_in) {
> +	case MAX42500_VM6:
> +		reg_addr = MAX42500_REG_VINO6;
> +		break;
> +	case MAX42500_VM7:
> +		reg_addr = MAX42500_REG_VINO7;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	st->ov_thresh2[vm_in] = thresh;
> +	return max42500_reg_write(st, reg_addr, thresh);
> +}
> +
> +/**
> + * @brief Set the undervoltage threshold of VM1 to VM5.
> + * @return 0 in case of success, negative error code otherwise.
> + */
> +static int max42500_set_uv_thresh1(struct max42500_state *st,
> +	enum max42500_vm_input vm_in, u8 thresh)
> +{
> +	if (thresh < MAX42500_MIN_THRESH_VM1_VM5 ||
> +		thresh > MAX42500_MAX_THRESH_VM1_VM5)
> +		return -EINVAL;
> +
> +	switch (vm_in) {
> +	case MAX42500_VM1:
> +	case MAX42500_VM2:
> +	case MAX42500_VM3:
> +	case MAX42500_VM4:
> +	case MAX42500_VM5:
> +		st->uv_thresh1[vm_in] = thresh;
> +		return max42500_reg_update(st,
> +					MAX42500_REG_OVUV1 + vm_in,
> +					GENMASK(3, 0),
> +					thresh);
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +/**
> + * @brief Set the undervoltage threshold of VM6 and VM7.
> + * @return 0 in case of success, negative error code otherwise.
> + */
> +static int max42500_set_uv_thresh2(struct max42500_state *st,
> +	enum max42500_vm_input vm_in, u8 thresh)
> +{
> +	u8 reg_addr;
> +
> +	if (thresh < MAX42500_MIN_THRESH_VM6_V7 ||
> +		thresh > MAX42500_MAX_THRESH_VM6_V7)
> +		return -EINVAL;
> +
> +	switch (vm_in) {
> +	case MAX42500_VM6:
> +		reg_addr = MAX42500_REG_VINU6;
> +		break;
> +	case MAX42500_VM7:
> +		reg_addr = MAX42500_REG_VINU7;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	st->uv_thresh2[vm_in] = thresh;
> +	return max42500_reg_write(st, reg_addr, thresh);
> +}
> +
> +/**
> + * @brief Get the FPS clock divider value.
> + * @return 0 in case of success, negative error code otherwise.
> + */
> +static int max42500_get_fps_clk_div(struct max42500_state *st,
> +	u8 *fps_clk_div)
> +{
> +	int ret;
> +	u8 reg_val;
> +
> +	ret = max42500_reg_read(st, MAX42500_REG_FPSCFG1, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	*fps_clk_div = (u8)FIELD_GET(GENMASK(2, 0), reg_val);
> +
> +	return 0;
> +}
> +
> +/**
> + * @brief Get power-up timestamp for a specified voltage monitor input.
> + * @return 0 in case of success, negative error code otherwise.
> + */
> +static int max42500_get_power_up_timestamp(struct max42500_state *st,
> +	enum max42500_vm_input vm_in, long *timestamp)
> +{
> +	int ret;
> +	u8 reg_val;
> +	u8 fps_clk_div;
> +
> +	ret = max42500_reg_read(st, MAX42500_REG_UTIME1 + vm_in, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	/* Check if the input voltage rose above the UV threshold */
> +	if (reg_val == 0) {
> +		/* Input voltage never rose above UV threshold*/
> +		*timestamp = 0;
> +		return 0;
> +	}
> +
> +	ret = max42500_get_fps_clk_div(st, &fps_clk_div);
> +	if (ret)
> +		return ret;
> +
> +	*timestamp = (reg_val - 1) * 25 * (1 << fps_clk_div);
> +	st->pwrup_stamp[vm_in] = *timestamp;
> +	return 0;
> +}
> +
> +/**
> + * @brief Get power-down timestamp for a specified voltage monitor input.
> + * @return 0 in case of success, negative error code otherwise.
> + */
> +static int max42500_get_power_down_timestamp(struct max42500_state *st,
> +	enum max42500_vm_input vm_in, long *timestamp)
> +{
> +	int ret;
> +	u8 reg_val;
> +	u8 fps_clk_div;
> +
> +	ret = max42500_reg_read(st, MAX42500_REG_DTIME1 + vm_in, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	/* Check if the input voltage fell below the OFF threshold */
> +	if (reg_val == 0) {
> +		/* Input voltage never fell below OFF threshold */
> +		*timestamp = 0;
> +		return 0;
> +	}
> +
> +	ret = max42500_get_fps_clk_div(st, &fps_clk_div);
> +	if (ret)
> +		return ret;
> +
> +	*timestamp = (reg_val - 1) * 25 * (1 << fps_clk_div);
> +	st->pwrdn_stamp[vm_in] = *timestamp;
> +	return 0;
> +}
> +
> +/**
> + * @brief Enable/Disable watchdog
> + * @return 0 in case of success, error code otherwise
> + */
> +static int max42500_set_watchdog_enable(struct max42500_state *st,
> +										bool wd_enable)
> +{
> +	int ret;
> +	u8 reg_val;
> +
> +	ret = max42500_reg_read(st, MAX42500_REG_WDCFG2, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	if (wd_enable)
> +		reg_val |= BIT(3);
> +	else
> +		reg_val &= ~BIT(3);
> +
> +	return max42500_reg_write(st, MAX42500_REG_WDCFG2, reg_val);
> +}
> +
> +/**
> + * @brief 8-bit watchdog key computation.
> + * @return 0 in case of success, negative error code otherwise.
> + */
> +static int max42500_get_watchdog_key(struct max42500_state *st,
> +										u8 *new_wd_key)
> +{
> +	int ret;
> +	u8 curr_wd_key;
> +
> +	ret = max42500_reg_read(st, MAX42500_REG_WDKEY, &curr_wd_key);
> +	if (ret)
> +		return ret;
> +
> +	/* Calculate the new bit using the LFSR polynomial */
> +	u8 new_bit = ((curr_wd_key >> 7) ^
> +					(curr_wd_key >> 5) ^
> +					(curr_wd_key >> 4) ^
> +					(curr_wd_key >> 3)) & 0x01;
> +
> +	/* Shift existing bits upwards to MSb and insert the new bit as LSb */
> +	*new_wd_key = (curr_wd_key << 1) | new_bit;
> +
> +	return 0;
> +}
> +
> +/**
> + * @brief Update the watchdog key based on the mode and current value.
> + * @return 0 in case of success, error code otherwise.
> + */
> +static int max42500_set_watchdog_key(struct max42500_state *st)
> +{
> +	int ret;
> +	u8 reg_val;
> +	u8 wd_key;
> +	u8 wd_mode;
> +
> +	ret = max42500_reg_read(st, MAX42500_REG_WDKEY, &wd_key);
> +	if (ret)
> +		return ret;
> +
> +	ret = max42500_reg_read(st, MAX42500_REG_WDCDIV, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	wd_mode = (u8)FIELD_GET(BIT(6), reg_val);
> +
> +	/* Compute new watchdog key for challenge/response mode */
> +	if (wd_mode == MAX42500_WD_MODE_CH_RESP)
> +		max42500_get_watchdog_key(st, &wd_key);
> +
> +	return max42500_reg_write(st, MAX42500_REG_WDKEY, wd_key);
> +}
> +
> +/** @brief Set watchdog reset hold time
> + * @return 0 in case of success, error code otherwise
> + */
> +static int max42500_set_watchdog_rhld(struct max42500_state *st, u8 rhld)
> +{
> +	return max42500_reg_update(st,
> +								MAX42500_REG_RSTCTRL,
> +								GENMASK(1, 0),
> +								rhld);
> +}
> +
> +static umode_t max42500_is_visible(const void *data,
> +	enum hwmon_sensor_types type, u32 attr, int channel)
> +{
> +	const struct max42500_state *st = data;
> +
> +	switch (type) {
> +	case hwmon_chip:
> +		switch (attr) {
> +		case hwmon_chip_in_reset_history:
> +			if (st->pwrup_stamp[channel])
> +				return 0444;
> +			break;
> +		case hwmon_chip_curr_reset_history:
> +			if (st->pwrdn_stamp[channel])
> +				return 0444;
> +			break;
> +		case hwmon_chip_register_tz:
> +			return 0200;
> +		case hwmon_chip_alarms:
> +		case hwmon_chip_update_interval:
> +		case hwmon_chip_temp_reset_history:
> +		case hwmon_chip_power_reset_history:
> +			return 0644;
> +		}
> +		break;
> +	case hwmon_in:
> +		switch (attr) {
> +		case hwmon_in_input:
> +			if (st->nominal_volt[channel])
> +				return 0644;
> +			break;
> +		case hwmon_in_lowest:
> +			if (st->ov_thresh1[channel])
> +				return 0644;
> +			break;
> +		case hwmon_in_highest:
> +			if (st->ov_thresh2[channel])
> +				return 0644;
> +			break;
> +		case hwmon_in_min:
> +			if (st->uv_thresh1[channel])
> +				return 0644;
> +			break;
> +		case hwmon_in_max:
> +			if (st->uv_thresh2[channel])
> +				return 0644;
> +			break;
> +		case hwmon_in_label:
> +			if (st->comp_status[channel])
> +				return 0444;
> +			break;
> +		}
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max42500_read_in(struct device *dev, u32 attr, int channel,
> +							long *value)
> +{
> +	struct max42500_state *st = dev_get_drvdata(dev);
> +
> +	switch (attr) {
> +	case hwmon_in_label:
> +		return max42500_get_comp_status(st, channel, (u8 *)value);

The label atribute is supposed to return a text.

> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int max42500_read_chip(struct device *dev, u32 attr, int channel,
> +								long *value)
> +{
> +	struct max42500_state *st = dev_get_drvdata(dev);
> +
> +	switch (attr) {
> +	case hwmon_chip_in_reset_history:
> +		return max42500_get_power_up_timestamp(st, channel, value);
> +	case hwmon_chip_curr_reset_history:
> +		return max42500_get_power_down_timestamp(st, channel, value);
> +	case hwmon_chip_power_reset_history:
> +		return max42500_get_watchdog_key(st, (u8 *)value);
> +	case hwmon_chip_update_interval:
> +		return max42500_get_fps_clk_div(st, (u8 *)value);

More ABI violations.

> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int max42500_read(struct device *dev, enum hwmon_sensor_types type,
> +							u32 attr, int channel, long *value)
> +{
> +	switch (type) {
> +	case hwmon_chip:
> +		return max42500_read_chip(dev, attr, channel, value);
> +	case hwmon_in:
> +		return max42500_read_in(dev, attr, channel, value);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int max42500_write_in(struct device *dev, u32 attr, int channel,
> +								long value)
> +{
> +	struct max42500_state *st = dev_get_drvdata(dev);
> +
> +	switch (attr) {
> +	case hwmon_in_input:
> +		return max42500_set_nominal_voltage(st, channel, (u8)value);

This is an ABI violation: inX_input attributes return voltages provided by
the chip, and are not writeable. Looking into the datasheet, this has to be
set using the min/max attributes.

> +	case hwmon_in_min:
> +		return max42500_set_uv_thresh1(st, channel, (u8)value);

This is a severe ABI violation. First, again, "(u8)value" is unacceptable.
Second, the ABI states that this is a value in mV, not a percentage.
I don't know why the chip uses a concept of "nominal voltage" and percentages
of that for thresholds. I undersatand that this makes working with the chip
difficult, but that does not mean that ABI violations are acceptable.

AFAICS the chip doesn't even report the current voltages, so any inX_input
attributes must not be provided. The limit attributes must set limits in mV.

> +	case hwmon_in_max:
> +		return max42500_set_uv_thresh2(st, channel, (u8)value);
> +	case hwmon_in_lowest:
> +		return max42500_set_ov_thresh1(st, channel, (u8)value);
> +	case hwmon_in_highest:
> +		return max42500_set_ov_thresh2(st, channel, (u8)value);

This looks like another ABI violation. _lowest and _highest_ attributes
report historic values.

> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int max42500_write_chip(struct device *dev, u32 attr, int channel,
> +								long value)
> +{
> +	struct max42500_state *st = dev_get_drvdata(dev);
> +
> +	switch (attr) {
> +	case hwmon_chip_temp_reset_history:
> +		return max42500_set_watchdog_rhld(st, (u8)value);
> +	case hwmon_chip_register_tz:
> +		return max42500_set_watchdog_key(st);
> +	case hwmon_chip_alarms:
> +		return max42500_set_watchdog_enable(st, (bool)value);
> +	default:

The above all seem to abouse the ABI and need documentation
and explanation to even be able to evaluate.

On top of that, cutting off values such as by "(u8)value" is
unacceptable; writing 256 would have the same result as writing 0.

> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int max42500_write(struct device *dev, enum hwmon_sensor_types type,
> +							u32 attr, int channel, long value)
> +{
> +	switch (type) {
> +	case hwmon_chip:
> +		return max42500_write_chip(dev, attr, channel, value);
> +	case hwmon_in:
> +		return max42500_write_in(dev, attr, channel, value);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static const struct max42500_config max42500_init = {
> +	.pece = true,
> +	.vmon_en = true,
> +	.vmon_vmpd = true,
> +	.reset_map = (MAX42500_RSTMAP_IN_MASK(MAX42500_VM2) |
> +					MAX42500_RSTMAP_IN_MASK(MAX42500_VM3) |
> +					MAX42500_RSTMAP_IN_MASK(MAX42500_VM4) |
> +					MAX42500_RSTMAP_PARM_MASK),
> +	.wd_mode = MAX42500_WD_MODE_SIMPLE,
> +	.wd_cdiv = 0x0,
> +	.wd_close = 0x0,
> +	.wd_open = 0x0,
> +	.wd_1ud = 0x0,
> +	.wd_en = true,
> +};
> +
> +static int max42500_init_client(struct max42500_state *st,
> +								struct device *dev)
> +{
> +	int err;
> +	u8 device_id;
> +
> +	if (!st || !dev)
> +		return -EINVAL;

How would any of those ever be NULL ? Please drop such unnecessary checks.
> +
> +	crc8_populate_msb(max42500_crc8, CRC8_PEC);
> +
> +	/* Check device silicon ID */
> +	err = max42500_reg_read(st, MAX42500_REG_ID, &device_id);
> +	if (err)
> +		return err;
> +
> +	if (device_id != MAX42500_SILICON_ID)
> +		return -ENODEV;
> +
> +	/* Configure PEC */
> +	err = max42500_reg_update(st,
> +				  MAX42500_REG_CONFIG1,
> +				  BIT(0),
> +				  max42500_init.pece);
> +	if (err)
> +		return err;
> +
> +	/* Set PEC enable/disable for subsequent register access */
> +	st->config->pece = max42500_init.pece;
> +
> +	/* Enable voltage monitor inputs */
> +	err = max42500_reg_update(st,
> +				  MAX42500_REG_VMON,
> +				  GENMASK(6, 0),
> +				  max42500_init.vmon_en);
> +	if (err)
> +		return err;
> +
> +	/* Enable voltage monitor power-down */
> +	err = max42500_reg_update(st,
> +				  MAX42500_REG_VMON,
> +				  BIT(7),
> +				  max42500_init.vmon_vmpd);
> +	if (err)
> +		return err;
> +
> +	/* Enable input OV/UV mapping to reset pin */
> +	err = max42500_reg_update(st,
> +				  MAX42500_REG_RSTMAP,
> +				  GENMASK(6, 0),
> +				  max42500_init.reset_map);
> +	if (err)
> +		return err;
> +
> +	/* Set watchdog mode */
> +	err = max42500_reg_update(st,
> +				  MAX42500_REG_WDCDIV,
> +				  BIT(6),
> +				  max42500_init.wd_mode << 6);
> +	if (err)
> +		return err;
> +
> +	/* Set watchdog clock div */
> +	err = max42500_reg_update(st,
> +				  MAX42500_REG_WDCDIV,
> +				  GENMASK(5, 0),
> +				  max42500_init.wd_cdiv);
> +	if (err)
> +		return err;
> +
> +	/* Set watchdog open window */
> +	err = max42500_reg_update(st,
> +				  MAX42500_REG_WDCFG1,
> +				  GENMASK(3, 0),
> +				  max42500_init.wd_open);
> +	if (err)
> +		return err;
> +
> +	/* Set watchdog close window */
> +	err = max42500_reg_update(st,
> +				  MAX42500_REG_WDCFG1,
> +				  GENMASK(7, 4),
> +				  max42500_init.wd_close);
> +	if (err)
> +		return err;
> +
> +	/* Set watchdog first update window */
> +	err = max42500_reg_update(st,
> +				  MAX42500_REG_WDCFG2,
> +				  GENMASK(2, 0),
> +				  max42500_init.wd_1ud);
> +	if (err)
> +		return err;
> +
> +	/* Set watchdog enable */
> +	err = max42500_reg_update(st,
> +				  MAX42500_REG_WDCFG2,
> +				  BIT(3),
> +				  max42500_init.wd_en);
> +	if (err)
> +		return err;
> +
> +	/* Update parameters */
> +	st->config->vmon_en = max42500_init.vmon_en;
> +	st->config->vmon_vmpd = max42500_init.vmon_vmpd;
> +	st->config->reset_map = max42500_init.reset_map;
> +	st->config->wd_mode = max42500_init.wd_mode;
> +	st->config->wd_cdiv = max42500_init.wd_cdiv;
> +	st->config->wd_open = max42500_init.wd_open;
> +	st->config->wd_close = max42500_init.wd_close;
> +	st->config->wd_1ud = max42500_init.wd_1ud;
> +	st->config->wd_en = max42500_init.wd_en;

Why not just setting a pointer to max42500_init ? 

> +
> +	return 0;
> +}
> +
> +static const struct hwmon_ops max42500_hwmon_ops = {
> +	.is_visible = max42500_is_visible,
> +	.read = max42500_read,
> +	.write = max42500_write,
> +};
> +
> +static const struct hwmon_channel_info *max42500_info[] = {
> +	HWMON_CHANNEL_INFO(chip,
> +			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY |
> +			HWMON_C_TEMP_RESET_HISTORY | HWMON_C_POWER_RESET_HISTORY |
> +			HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL | HWMON_C_ALARMS,
> +			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY,
> +			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY,
> +			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY,
> +			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY,
> +			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY,
> +			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY),
> +	HWMON_CHANNEL_INFO(in,
> +			HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_MIN |
> +			HWMON_I_HIGHEST | HWMON_I_MAX,
> +			HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_MIN |
> +			HWMON_I_HIGHEST | HWMON_I_MAX,
> +			HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_MIN,
> +			HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_MIN,
> +			HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_MIN,
> +			HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL,
> +			HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL,
> +			HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL,
> +			HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL),
> +	NULL
> +};
> +
> +static const struct hwmon_chip_info max42500_chip_info = {
> +	.ops = &max42500_hwmon_ops,
> +	.info = max42500_info,
> +};
> +
> +static const struct regmap_config max42500_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.max_register = 0x2D
> +};
> +
> +static int max42500_probe(struct i2c_client *client)
> +{
> +	struct device *hwmon_dev;
> +	struct max42500_state *st;
> +	int err;
> +
> +	st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
> +	if (!st)
> +		return -ENOMEM;
> +
> +	st->client = client;
> +	st->regmap = devm_regmap_init_i2c(client, &max42500_regmap_config);
> +	if (IS_ERR(st->regmap))
> +		return PTR_ERR(st->regmap);
> +
> +	err = max42500_init_client(st, &client->dev);
> +	if (err)
> +		return err;
> +
> +	hwmon_dev = devm_hwmon_device_register_with_info(&client->dev,
> +					client->name, st, &max42500_chip_info, NULL);
> +
> +	return PTR_ERR_OR_ZERO(hwmon_dev);
> +}
> +
> +static const struct of_device_id max42500_of_match[] = {
> +	{ .compatible = "adi,max42500" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, max42500_of_match);
> +
> +static const struct i2c_device_id max42500_id[] = {
> +	{ "max42500", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, max42500_id);
> +
> +static struct i2c_driver max42500_driver = {
> +	.driver = {
> +		.name	= "max42500",
> +		.of_match_table = max42500_of_match,
> +	},
> +	.probe		= max42500_probe,
> +	.id_table	= max42500_id,
> +};
> +
> +module_i2c_driver(max42500_driver);
> +
> +MODULE_AUTHOR("Kent Libetario <kent.libetario@analog.com>");
> +MODULE_DESCRIPTION("Hwmon driver for MAX42500");
> +MODULE_LICENSE("GPL");
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index d1703b4834c8..434191e16dd5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14051,6 +14051,7 @@  M:	Kent Libetario <kent.libetario@analog.com>
 L:	linux-hwmon@vger.kernel.org
 S:	Supported
 F:	Documentation/devicetree/bindings/hwmon/adi,max42500.yaml
+F:	drivers/hwmon/max42500.c
 
 MAX6650 HARDWARE MONITOR AND FAN CONTROLLER DRIVER
 L:	linux-hwmon@vger.kernel.org
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index dd376602f3f1..ec0d7aad7789 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1220,6 +1220,19 @@  config MAX31827
 	  This driver can also be built as a module.  If so, the module
 	  will be called max31827.
 
+config SENSORS_MAX42500
+	tristate "MAX42500 Industrial Power System Monitor Family"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  If you say yes here you get support for MAX42500 SoC power-system monitor
+	  with up to seven voltage monitor. The driver also contains a  programmable
+	  challenge/response watchdog, which is accessible through the I2C interface,
+	  along with a configurable RESET output.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called max42500.
+
 config SENSORS_MAX6620
 	tristate "Maxim MAX6620 fan controller"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b827b92f2a78..d27d8fc01141 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -152,6 +152,7 @@  obj-$(CONFIG_SENSORS_MAX197)	+= max197.o
 obj-$(CONFIG_SENSORS_MAX31722)	+= max31722.o
 obj-$(CONFIG_SENSORS_MAX31730)	+= max31730.o
 obj-$(CONFIG_SENSORS_MAX31760)  += max31760.o
+obj-$(CONFIG_SENSORS_MAX42500)  += max42500.o
 obj-$(CONFIG_SENSORS_MAX6620)	+= max6620.o
 obj-$(CONFIG_SENSORS_MAX6621)	+= max6621.o
 obj-$(CONFIG_SENSORS_MAX6639)	+= max6639.o
diff --git a/drivers/hwmon/max42500.c b/drivers/hwmon/max42500.c
new file mode 100644
index 000000000000..23b90c766767
--- /dev/null
+++ b/drivers/hwmon/max42500.c
@@ -0,0 +1,1049 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MAX42500 - Industrial Power System Monitor
+ *
+ * Copyright 2024 Analog Devices Inc.
+ */
+
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/crc8.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+/* Implements Polynomial X^8 + X^2 + X^1 +1 */
+#define CRC8_PEC		0x07
+
+DECLARE_CRC8_TABLE(max42500_crc8);
+
+#define MAX42500_REG_ID					0x00
+#define MAX42500_REG_CONFIG1			0x01
+#define MAX42500_REG_CONFIG2			0x02
+#define MAX42500_REG_VMON				0x03
+#define MAX42500_REG_RSTMAP				0x04
+#define MAX42500_REG_STATOV				0x05
+#define MAX42500_REG_STATUV				0x06
+#define MAX42500_REG_STATOFF			0x07
+#define MAX42500_REG_VIN1				0x08
+#define MAX42500_REG_VIN2				0x09
+#define MAX42500_REG_VIN3				0x0A
+#define MAX42500_REG_VIN4				0x0B
+#define MAX42500_REG_VIN5				0x0C
+#define MAX42500_REG_VINO6				0x0D
+#define MAX42500_REG_VINU6				0x0E
+#define MAX42500_REG_VINO7				0x0F
+#define MAX42500_REG_VINU7				0x10
+#define MAX42500_REG_OVUV1				0x11
+#define MAX42500_REG_OVUV2				0x12
+#define MAX42500_REG_OVUV3				0x13
+#define MAX42500_REG_OVUV4				0x14
+#define MAX42500_REG_OVUV5				0x15
+#define MAX42500_REG_FPSSTAT1			0x16
+#define MAX42500_REG_FPSCFG1			0x17
+#define MAX42500_REG_UTIME1				0x18
+#define MAX42500_REG_UTIME2				0x19
+#define MAX42500_REG_UTIME3				0x1A
+#define MAX42500_REG_UTIME4				0x1B
+#define MAX42500_REG_UTIME5				0x1C
+#define MAX42500_REG_UTIME6				0x1D
+#define MAX42500_REG_UTIME7				0x1E
+#define MAX42500_REG_DTIME1				0x1F
+#define MAX42500_REG_DTIME2				0x20
+#define MAX42500_REG_DTIME3				0x21
+#define MAX42500_REG_DTIME4				0x22
+#define MAX42500_REG_DTIME5				0x23
+#define MAX42500_REG_DTIME6				0x24
+#define MAX42500_REG_DTIME7				0x25
+#define MAX42500_REG_WDSTAT				0x26
+#define MAX42500_REG_WDCDIV				0x27
+#define MAX42500_REG_WDCFG1				0x28
+#define MAX42500_REG_WDCFG2				0x29
+#define MAX42500_REG_WDKEY				0x2A
+#define MAX42500_REG_WDLOCK				0x2B
+#define MAX42500_REG_RSTCTRL			0x2C
+#define MAX42500_REG_CID				0x2D
+
+/** X is set based on the pull configuration of the ADDR pin */
+#define MAX42500_ADDR(x)				(0x28 + (x))
+#define MAX42500_SILICON_ID				(0x30)
+#define MAX42500_I2C_WR_FRAME_SIZE		(4)
+#define MAX42500_I2C_RD_FRAME_SIZE		(5)
+
+/** MAX42500 Nominal voltage computation */
+#define MAX42500_VNOM_MAX_VM1_VM4		0xFF	// 3.6875v
+#define MAX42500_VNOM_MAX_VM5			0xFF	// 5.6v
+#define MAX42500_MIN_VNOM				0x00	// 0.5v
+#define MAX42500_VNOM_STEP_VM1_VM4		0x01	// 0.0125v
+#define MAX42500_VNOM_STEP_VM5			0x01	// 0.02v
+
+/** MAX42500 Undervoltage/Overvoltage maximum and minimum thresholds*/
+#define MAX42500_MAX_THRESH_VM1_VM5		0x0F	// OV = 110.0%, UV = 90.0%
+#define MAX42500_MIN_THRESH_VM1_VM5		0x00	// OV = 102.5%, UV = 97.5%
+#define MAX42500_MAX_THRESH_VM6_V7		0xFF	// 1.775v
+#define MAX42500_MIN_THRESH_VM6_V7		0x00	// 0.5v
+
+/* CONFIG1 bit masks */
+#define MAX42500_CONFIG1_PECE_MASK		BIT(0)
+#define MAX42500_CONFIG1_MBST_MASK		BIT(1)
+#define MAX42500_CONFIG1_RR_MASK		BIT(2)
+
+/* VMON bit masks */
+#define MAX42500_VMON_IN_MASK(bit)		BIT(bit)
+#define MAX42500_VMON_VMPD_MASK			BIT(7)
+
+/* RSTMAP bit masks */
+#define MAX42500_RSTMAP_IN_MASK(bit)	BIT(bit)
+#define MAX42500_RSTMAP_PARM_MASK		BIT(7)
+
+/* WDCDIV bit masks */
+#define MAX42500_WDCDIV_SWW_MASK		BIT(6)
+#define MAX42500_WDCDIV_WDIC_MASK		(0x3F)
+
+/* WDCFG2 bit masks */
+#define MAX42500_WDCFG2_WDEN_MASK		BIT(3)
+#define MAX42500_WDCFG2_1UP_MASK		(0x7)
+
+/* WDLOCK bit masks */
+#define MAX42500_WDLOCK_LOCK_MASK		BIT(0)
+
+/* RSTCTRL bit masks */
+#define MAX42500_RSTCTRL_MR1_MASK		BIT(2)
+#define MAX42500_RSTCTRL_RHLD_MASK		(0x3)
+
+/* MAX42500 device status */
+enum max42500_status {
+	MAX42500_STATUS_OFF,
+	MAX42500_STATUS_SLEEP,
+	MAX42500_STATUS_ON,
+	MAX42500_STATUS_MAX
+};
+
+/* MAX42500 voltage monitor input */
+enum max42500_vm_input {
+	MAX42500_VM1,
+	MAX42500_VM2,
+	MAX42500_VM3,
+	MAX42500_VM4,
+	MAX42500_VM5,
+	MAX42500_VM6,
+	MAX42500_VM7,
+	MAX42500_VM_MAX
+};
+
+/* MAX42500 comparator status */
+enum max42500_comp_stat {
+	MAX42500_COMP_STAT_OFF,
+	MAX42500_COMP_STAT_UV,
+	MAX42500_COMP_STAT_OV,
+	MAX42500_COMP_STAT_MAX
+};
+
+/* MAX42500 watchdog mode */
+enum max42500_wd_mode {
+	MAX42500_WD_MODE_CH_RESP,
+	MAX42500_WD_MODE_SIMPLE,
+	MAX42500_WD_MODE_MAX
+};
+
+/* MAX42500 reset hold/active timeout time. */
+enum max42500_wd_rhld {
+	MAX42500_WD_RHOLD_0_MS,
+	MAX42500_WD_RHOLD_8_MS,
+	MAX42500_WD_RHOLD_16_MS,
+	MAX42500_WD_RHOLD_32_MS,
+	MAX42500_WD_RHOLD_MAX
+};
+
+struct max42500_config {
+	/* Packet error checking enable */
+	u8 pece;
+	/* Enabled voltage monitor inputs */
+	u8 vmon_en;
+	/* Voltage monitor power down enable */
+	u8 vmon_vmpd;
+	/* Enabled voltage monitor reset mapping */
+	u8 reset_map;
+	/* Watchdog mode */
+	enum max42500_wd_mode wd_mode;
+	/* Watchdog clock div */
+	u8 wd_cdiv;
+	/* Watchdog close window */
+	u8 wd_close;
+	/* Watchdog open window */
+	u8 wd_open;
+	/* Watchdog first update window */
+	u8 wd_1ud;
+	/* Watchdog enable */
+	u8 wd_en;
+};
+
+struct max42500_state {
+	struct i2c_client *client;
+	struct regmap *regmap;
+	struct max42500_config *config;
+	long pwrup_stamp[MAX42500_VM_MAX];
+	long pwrdn_stamp[MAX42500_VM_MAX];
+	u8 ov_thresh1[MAX42500_VM_MAX - 2];
+	u8 ov_thresh2[MAX42500_VM_MAX - 5];
+	u8 uv_thresh1[MAX42500_VM_MAX - 2];
+	u8 uv_thresh2[MAX42500_VM_MAX - 5];
+	u8 nominal_volt[MAX42500_VM_MAX - 2];
+	u8 comp_status[MAX42500_VM_MAX * MAX42500_COMP_STAT_MAX];
+};
+
+/************************ Functions Definitions **************************/
+/**
+ * @brief Read a raw value from a register.
+ * @return 0 in case of success, error code otherwise.
+ */
+static int max42500_reg_read(struct max42500_state *st,
+								u8 reg_addr, u8 *reg_data)
+{
+	int ret;
+	u8 i2c_data[MAX42500_I2C_RD_FRAME_SIZE] = {0};
+	u8 bytes_num;
+	u8 pece_value;
+
+	/* PEC is computed over entire I2C frame from first START condition */
+	i2c_data[0] = (st->client->addr << 1);
+	i2c_data[1] = reg_addr;
+	i2c_data[2] = (st->client->addr << 1) | 0x1;
+
+	/* I2C write target address */
+	bytes_num = 1;
+
+	ret = regmap_bulk_write(st->regmap, reg_addr, &i2c_data[1], bytes_num);
+	if (ret)
+		return ret;
+
+	/* Change byte count if PECE is enabled (1-byte data. 1-byte PEC) */
+	bytes_num = (st->config->pece) ? 2 : bytes_num;
+
+	ret = regmap_bulk_read(st->regmap, reg_addr, &i2c_data[3], bytes_num);
+	if (ret)
+		return ret;
+
+	if (st->config->pece) {
+		/* Compute CRC over entire I2C frame */
+		pece_value = crc8(max42500_crc8, i2c_data,
+							(MAX42500_I2C_RD_FRAME_SIZE - 1), 0);
+
+		if (i2c_data[4] != pece_value)
+			return -EIO;
+	}
+
+	*reg_data = i2c_data[3];
+
+	return 0;
+}
+
+/**
+ * @brief Write a raw value to a register.
+ * @return 0 in case of success, negative error code otherwise.
+ */
+static int max42500_reg_write(struct max42500_state *st,
+								u8 reg_addr, u8 data)
+{
+	u8 i2c_data[MAX42500_I2C_WR_FRAME_SIZE] = {0};
+	u8 bytes_num;
+	u8 pece_value;
+
+	bytes_num = (st->config->pece) ? (MAX42500_I2C_WR_FRAME_SIZE - 1) : 2;
+	i2c_data[0] = (st->client->addr << 1);
+	i2c_data[1] = reg_addr;
+	i2c_data[2] = (u8)(data & 0xFF);
+
+	pece_value = 0;
+	if (st->config->pece)
+		pece_value = crc8(max42500_crc8, i2c_data, bytes_num, 0);
+
+	i2c_data[0] = i2c_data[1];
+	i2c_data[1] = i2c_data[2];
+	i2c_data[2] = pece_value;
+
+	return regmap_bulk_write(st->regmap, reg_addr, i2c_data, bytes_num);
+}
+
+/**
+ * @brief Update a register's value based on a mask.
+ * @return 0 in case of success, negative error code otherwise.
+ */
+static int max42500_reg_update(struct max42500_state *st,
+								u8 reg_addr, u8 mask, u8 data)
+{
+	int ret;
+	u8 reg_data;
+
+	ret = max42500_reg_read(st, reg_addr, &reg_data);
+	if (ret)
+		return ret;
+
+	reg_data &= ~mask;
+	reg_data |= mask & data;
+
+	return max42500_reg_write(st, reg_addr, reg_data);
+}
+
+/**
+ * @brief Set nominal voltage for VM1 to VM5.
+ * @return 0 in case of success, negative error code otherwise.
+ */
+static int max42500_set_nominal_voltage(struct max42500_state *st,
+	enum max42500_vm_input vm_in, u8 voltage)
+{
+	u8 reg_addr;
+
+	switch (vm_in) {
+	case MAX42500_VM1:
+	case MAX42500_VM2:
+	case MAX42500_VM3:
+	case MAX42500_VM4:
+		if (voltage < MAX42500_MIN_VNOM ||
+			voltage > MAX42500_VNOM_MAX_VM1_VM4)
+			return -EINVAL;
+		reg_addr = MAX42500_REG_VIN1 + vm_in;
+		break;
+	case MAX42500_VM5:
+		if (voltage < MAX42500_MIN_VNOM ||
+			voltage > MAX42500_VNOM_MAX_VM5)
+			return -EINVAL;
+		reg_addr = MAX42500_REG_VIN5;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	st->nominal_volt[vm_in] = voltage;
+	return max42500_reg_write(st, reg_addr, voltage);
+}
+
+/**
+ * @brief Get the status of the voltage monitor input.
+ * @return 0 in case of success, negative error code otherwise.
+ */
+static int max42500_get_comp_status(struct max42500_state *st,
+									u8 vm_in, u8 *status)
+{
+	int ret;
+	u8 reg_addr;
+	u8 vm_in_status;
+
+	switch (vm_in % MAX42500_COMP_STAT_MAX) {
+	case MAX42500_COMP_STAT_OFF:
+		reg_addr = MAX42500_REG_STATOFF;
+		break;
+	case MAX42500_COMP_STAT_UV:
+		reg_addr = MAX42500_REG_STATUV;
+		break;
+	case MAX42500_COMP_STAT_OV:
+		reg_addr = MAX42500_REG_STATOV;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = max42500_reg_read(st, reg_addr, &vm_in_status);
+	if (ret)
+		return ret;
+
+	switch (vm_in % MAX42500_VM_MAX) {
+	case MAX42500_VM1:
+		*status = (u8)FIELD_GET(BIT(MAX42500_VM1), vm_in_status);
+		break;
+	case MAX42500_VM2:
+		*status = (u8)FIELD_GET(BIT(MAX42500_VM2), vm_in_status);
+		break;
+	case MAX42500_VM3:
+		*status = (u8)FIELD_GET(BIT(MAX42500_VM3), vm_in_status);
+		break;
+	case MAX42500_VM4:
+		*status = (u8)FIELD_GET(BIT(MAX42500_VM4), vm_in_status);
+		break;
+	case MAX42500_VM5:
+		*status = (u8)FIELD_GET(BIT(MAX42500_VM5), vm_in_status);
+		break;
+	case MAX42500_VM6:
+		*status = (u8)FIELD_GET(BIT(MAX42500_VM6), vm_in_status);
+		break;
+	case MAX42500_VM7:
+		*status = (u8)FIELD_GET(BIT(MAX42500_VM7), vm_in_status);
+		break;
+	default:
+		return -EINVAL;
+	}
+	st->comp_status[vm_in] = *status;
+
+	return 0;
+}
+
+/**
+ * @brief Set the overvoltage threshold of VM1 to VM5.
+ * @return 0 in case of success, negative error code otherwise.
+ */
+static int max42500_set_ov_thresh1(struct max42500_state *st,
+	enum max42500_vm_input vm_in, u8 thresh)
+{
+	if (thresh < MAX42500_MIN_THRESH_VM1_VM5 ||
+		thresh > MAX42500_MAX_THRESH_VM1_VM5)
+		return -EINVAL;
+
+	switch (vm_in) {
+	case MAX42500_VM1:
+	case MAX42500_VM2:
+	case MAX42500_VM3:
+	case MAX42500_VM4:
+	case MAX42500_VM5:
+		st->ov_thresh1[vm_in] = thresh;
+		return max42500_reg_update(st,
+								MAX42500_REG_OVUV1 + vm_in,
+								GENMASK(7, 4),
+								FIELD_PREP(GENMASK(7, 4),
+								thresh));
+	default:
+		return -EINVAL;
+	}
+}
+
+/**
+ * @brief Set the overvoltage threshold of VM6 and VM7.
+ * @return 0 in case of success, negative error code otherwise.
+ */
+static int max42500_set_ov_thresh2(struct max42500_state *st,
+	enum max42500_vm_input vm_in, u8 thresh)
+{
+	u8 reg_addr;
+
+	if (thresh < MAX42500_MIN_THRESH_VM6_V7 ||
+		thresh > MAX42500_MAX_THRESH_VM6_V7)
+		return -EINVAL;
+
+	switch (vm_in) {
+	case MAX42500_VM6:
+		reg_addr = MAX42500_REG_VINO6;
+		break;
+	case MAX42500_VM7:
+		reg_addr = MAX42500_REG_VINO7;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	st->ov_thresh2[vm_in] = thresh;
+	return max42500_reg_write(st, reg_addr, thresh);
+}
+
+/**
+ * @brief Set the undervoltage threshold of VM1 to VM5.
+ * @return 0 in case of success, negative error code otherwise.
+ */
+static int max42500_set_uv_thresh1(struct max42500_state *st,
+	enum max42500_vm_input vm_in, u8 thresh)
+{
+	if (thresh < MAX42500_MIN_THRESH_VM1_VM5 ||
+		thresh > MAX42500_MAX_THRESH_VM1_VM5)
+		return -EINVAL;
+
+	switch (vm_in) {
+	case MAX42500_VM1:
+	case MAX42500_VM2:
+	case MAX42500_VM3:
+	case MAX42500_VM4:
+	case MAX42500_VM5:
+		st->uv_thresh1[vm_in] = thresh;
+		return max42500_reg_update(st,
+					MAX42500_REG_OVUV1 + vm_in,
+					GENMASK(3, 0),
+					thresh);
+	default:
+		return -EINVAL;
+	}
+}
+
+/**
+ * @brief Set the undervoltage threshold of VM6 and VM7.
+ * @return 0 in case of success, negative error code otherwise.
+ */
+static int max42500_set_uv_thresh2(struct max42500_state *st,
+	enum max42500_vm_input vm_in, u8 thresh)
+{
+	u8 reg_addr;
+
+	if (thresh < MAX42500_MIN_THRESH_VM6_V7 ||
+		thresh > MAX42500_MAX_THRESH_VM6_V7)
+		return -EINVAL;
+
+	switch (vm_in) {
+	case MAX42500_VM6:
+		reg_addr = MAX42500_REG_VINU6;
+		break;
+	case MAX42500_VM7:
+		reg_addr = MAX42500_REG_VINU7;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	st->uv_thresh2[vm_in] = thresh;
+	return max42500_reg_write(st, reg_addr, thresh);
+}
+
+/**
+ * @brief Get the FPS clock divider value.
+ * @return 0 in case of success, negative error code otherwise.
+ */
+static int max42500_get_fps_clk_div(struct max42500_state *st,
+	u8 *fps_clk_div)
+{
+	int ret;
+	u8 reg_val;
+
+	ret = max42500_reg_read(st, MAX42500_REG_FPSCFG1, &reg_val);
+	if (ret)
+		return ret;
+
+	*fps_clk_div = (u8)FIELD_GET(GENMASK(2, 0), reg_val);
+
+	return 0;
+}
+
+/**
+ * @brief Get power-up timestamp for a specified voltage monitor input.
+ * @return 0 in case of success, negative error code otherwise.
+ */
+static int max42500_get_power_up_timestamp(struct max42500_state *st,
+	enum max42500_vm_input vm_in, long *timestamp)
+{
+	int ret;
+	u8 reg_val;
+	u8 fps_clk_div;
+
+	ret = max42500_reg_read(st, MAX42500_REG_UTIME1 + vm_in, &reg_val);
+	if (ret)
+		return ret;
+
+	/* Check if the input voltage rose above the UV threshold */
+	if (reg_val == 0) {
+		/* Input voltage never rose above UV threshold*/
+		*timestamp = 0;
+		return 0;
+	}
+
+	ret = max42500_get_fps_clk_div(st, &fps_clk_div);
+	if (ret)
+		return ret;
+
+	*timestamp = (reg_val - 1) * 25 * (1 << fps_clk_div);
+	st->pwrup_stamp[vm_in] = *timestamp;
+	return 0;
+}
+
+/**
+ * @brief Get power-down timestamp for a specified voltage monitor input.
+ * @return 0 in case of success, negative error code otherwise.
+ */
+static int max42500_get_power_down_timestamp(struct max42500_state *st,
+	enum max42500_vm_input vm_in, long *timestamp)
+{
+	int ret;
+	u8 reg_val;
+	u8 fps_clk_div;
+
+	ret = max42500_reg_read(st, MAX42500_REG_DTIME1 + vm_in, &reg_val);
+	if (ret)
+		return ret;
+
+	/* Check if the input voltage fell below the OFF threshold */
+	if (reg_val == 0) {
+		/* Input voltage never fell below OFF threshold */
+		*timestamp = 0;
+		return 0;
+	}
+
+	ret = max42500_get_fps_clk_div(st, &fps_clk_div);
+	if (ret)
+		return ret;
+
+	*timestamp = (reg_val - 1) * 25 * (1 << fps_clk_div);
+	st->pwrdn_stamp[vm_in] = *timestamp;
+	return 0;
+}
+
+/**
+ * @brief Enable/Disable watchdog
+ * @return 0 in case of success, error code otherwise
+ */
+static int max42500_set_watchdog_enable(struct max42500_state *st,
+										bool wd_enable)
+{
+	int ret;
+	u8 reg_val;
+
+	ret = max42500_reg_read(st, MAX42500_REG_WDCFG2, &reg_val);
+	if (ret)
+		return ret;
+
+	if (wd_enable)
+		reg_val |= BIT(3);
+	else
+		reg_val &= ~BIT(3);
+
+	return max42500_reg_write(st, MAX42500_REG_WDCFG2, reg_val);
+}
+
+/**
+ * @brief 8-bit watchdog key computation.
+ * @return 0 in case of success, negative error code otherwise.
+ */
+static int max42500_get_watchdog_key(struct max42500_state *st,
+										u8 *new_wd_key)
+{
+	int ret;
+	u8 curr_wd_key;
+
+	ret = max42500_reg_read(st, MAX42500_REG_WDKEY, &curr_wd_key);
+	if (ret)
+		return ret;
+
+	/* Calculate the new bit using the LFSR polynomial */
+	u8 new_bit = ((curr_wd_key >> 7) ^
+					(curr_wd_key >> 5) ^
+					(curr_wd_key >> 4) ^
+					(curr_wd_key >> 3)) & 0x01;
+
+	/* Shift existing bits upwards to MSb and insert the new bit as LSb */
+	*new_wd_key = (curr_wd_key << 1) | new_bit;
+
+	return 0;
+}
+
+/**
+ * @brief Update the watchdog key based on the mode and current value.
+ * @return 0 in case of success, error code otherwise.
+ */
+static int max42500_set_watchdog_key(struct max42500_state *st)
+{
+	int ret;
+	u8 reg_val;
+	u8 wd_key;
+	u8 wd_mode;
+
+	ret = max42500_reg_read(st, MAX42500_REG_WDKEY, &wd_key);
+	if (ret)
+		return ret;
+
+	ret = max42500_reg_read(st, MAX42500_REG_WDCDIV, &reg_val);
+	if (ret)
+		return ret;
+
+	wd_mode = (u8)FIELD_GET(BIT(6), reg_val);
+
+	/* Compute new watchdog key for challenge/response mode */
+	if (wd_mode == MAX42500_WD_MODE_CH_RESP)
+		max42500_get_watchdog_key(st, &wd_key);
+
+	return max42500_reg_write(st, MAX42500_REG_WDKEY, wd_key);
+}
+
+/** @brief Set watchdog reset hold time
+ * @return 0 in case of success, error code otherwise
+ */
+static int max42500_set_watchdog_rhld(struct max42500_state *st, u8 rhld)
+{
+	return max42500_reg_update(st,
+								MAX42500_REG_RSTCTRL,
+								GENMASK(1, 0),
+								rhld);
+}
+
+static umode_t max42500_is_visible(const void *data,
+	enum hwmon_sensor_types type, u32 attr, int channel)
+{
+	const struct max42500_state *st = data;
+
+	switch (type) {
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_in_reset_history:
+			if (st->pwrup_stamp[channel])
+				return 0444;
+			break;
+		case hwmon_chip_curr_reset_history:
+			if (st->pwrdn_stamp[channel])
+				return 0444;
+			break;
+		case hwmon_chip_register_tz:
+			return 0200;
+		case hwmon_chip_alarms:
+		case hwmon_chip_update_interval:
+		case hwmon_chip_temp_reset_history:
+		case hwmon_chip_power_reset_history:
+			return 0644;
+		}
+		break;
+	case hwmon_in:
+		switch (attr) {
+		case hwmon_in_input:
+			if (st->nominal_volt[channel])
+				return 0644;
+			break;
+		case hwmon_in_lowest:
+			if (st->ov_thresh1[channel])
+				return 0644;
+			break;
+		case hwmon_in_highest:
+			if (st->ov_thresh2[channel])
+				return 0644;
+			break;
+		case hwmon_in_min:
+			if (st->uv_thresh1[channel])
+				return 0644;
+			break;
+		case hwmon_in_max:
+			if (st->uv_thresh2[channel])
+				return 0644;
+			break;
+		case hwmon_in_label:
+			if (st->comp_status[channel])
+				return 0444;
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int max42500_read_in(struct device *dev, u32 attr, int channel,
+							long *value)
+{
+	struct max42500_state *st = dev_get_drvdata(dev);
+
+	switch (attr) {
+	case hwmon_in_label:
+		return max42500_get_comp_status(st, channel, (u8 *)value);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int max42500_read_chip(struct device *dev, u32 attr, int channel,
+								long *value)
+{
+	struct max42500_state *st = dev_get_drvdata(dev);
+
+	switch (attr) {
+	case hwmon_chip_in_reset_history:
+		return max42500_get_power_up_timestamp(st, channel, value);
+	case hwmon_chip_curr_reset_history:
+		return max42500_get_power_down_timestamp(st, channel, value);
+	case hwmon_chip_power_reset_history:
+		return max42500_get_watchdog_key(st, (u8 *)value);
+	case hwmon_chip_update_interval:
+		return max42500_get_fps_clk_div(st, (u8 *)value);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int max42500_read(struct device *dev, enum hwmon_sensor_types type,
+							u32 attr, int channel, long *value)
+{
+	switch (type) {
+	case hwmon_chip:
+		return max42500_read_chip(dev, attr, channel, value);
+	case hwmon_in:
+		return max42500_read_in(dev, attr, channel, value);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int max42500_write_in(struct device *dev, u32 attr, int channel,
+								long value)
+{
+	struct max42500_state *st = dev_get_drvdata(dev);
+
+	switch (attr) {
+	case hwmon_in_input:
+		return max42500_set_nominal_voltage(st, channel, (u8)value);
+	case hwmon_in_min:
+		return max42500_set_uv_thresh1(st, channel, (u8)value);
+	case hwmon_in_max:
+		return max42500_set_uv_thresh2(st, channel, (u8)value);
+	case hwmon_in_lowest:
+		return max42500_set_ov_thresh1(st, channel, (u8)value);
+	case hwmon_in_highest:
+		return max42500_set_ov_thresh2(st, channel, (u8)value);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int max42500_write_chip(struct device *dev, u32 attr, int channel,
+								long value)
+{
+	struct max42500_state *st = dev_get_drvdata(dev);
+
+	switch (attr) {
+	case hwmon_chip_temp_reset_history:
+		return max42500_set_watchdog_rhld(st, (u8)value);
+	case hwmon_chip_register_tz:
+		return max42500_set_watchdog_key(st);
+	case hwmon_chip_alarms:
+		return max42500_set_watchdog_enable(st, (bool)value);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int max42500_write(struct device *dev, enum hwmon_sensor_types type,
+							u32 attr, int channel, long value)
+{
+	switch (type) {
+	case hwmon_chip:
+		return max42500_write_chip(dev, attr, channel, value);
+	case hwmon_in:
+		return max42500_write_in(dev, attr, channel, value);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static const struct max42500_config max42500_init = {
+	.pece = true,
+	.vmon_en = true,
+	.vmon_vmpd = true,
+	.reset_map = (MAX42500_RSTMAP_IN_MASK(MAX42500_VM2) |
+					MAX42500_RSTMAP_IN_MASK(MAX42500_VM3) |
+					MAX42500_RSTMAP_IN_MASK(MAX42500_VM4) |
+					MAX42500_RSTMAP_PARM_MASK),
+	.wd_mode = MAX42500_WD_MODE_SIMPLE,
+	.wd_cdiv = 0x0,
+	.wd_close = 0x0,
+	.wd_open = 0x0,
+	.wd_1ud = 0x0,
+	.wd_en = true,
+};
+
+static int max42500_init_client(struct max42500_state *st,
+								struct device *dev)
+{
+	int err;
+	u8 device_id;
+
+	if (!st || !dev)
+		return -EINVAL;
+
+	crc8_populate_msb(max42500_crc8, CRC8_PEC);
+
+	/* Check device silicon ID */
+	err = max42500_reg_read(st, MAX42500_REG_ID, &device_id);
+	if (err)
+		return err;
+
+	if (device_id != MAX42500_SILICON_ID)
+		return -ENODEV;
+
+	/* Configure PEC */
+	err = max42500_reg_update(st,
+				  MAX42500_REG_CONFIG1,
+				  BIT(0),
+				  max42500_init.pece);
+	if (err)
+		return err;
+
+	/* Set PEC enable/disable for subsequent register access */
+	st->config->pece = max42500_init.pece;
+
+	/* Enable voltage monitor inputs */
+	err = max42500_reg_update(st,
+				  MAX42500_REG_VMON,
+				  GENMASK(6, 0),
+				  max42500_init.vmon_en);
+	if (err)
+		return err;
+
+	/* Enable voltage monitor power-down */
+	err = max42500_reg_update(st,
+				  MAX42500_REG_VMON,
+				  BIT(7),
+				  max42500_init.vmon_vmpd);
+	if (err)
+		return err;
+
+	/* Enable input OV/UV mapping to reset pin */
+	err = max42500_reg_update(st,
+				  MAX42500_REG_RSTMAP,
+				  GENMASK(6, 0),
+				  max42500_init.reset_map);
+	if (err)
+		return err;
+
+	/* Set watchdog mode */
+	err = max42500_reg_update(st,
+				  MAX42500_REG_WDCDIV,
+				  BIT(6),
+				  max42500_init.wd_mode << 6);
+	if (err)
+		return err;
+
+	/* Set watchdog clock div */
+	err = max42500_reg_update(st,
+				  MAX42500_REG_WDCDIV,
+				  GENMASK(5, 0),
+				  max42500_init.wd_cdiv);
+	if (err)
+		return err;
+
+	/* Set watchdog open window */
+	err = max42500_reg_update(st,
+				  MAX42500_REG_WDCFG1,
+				  GENMASK(3, 0),
+				  max42500_init.wd_open);
+	if (err)
+		return err;
+
+	/* Set watchdog close window */
+	err = max42500_reg_update(st,
+				  MAX42500_REG_WDCFG1,
+				  GENMASK(7, 4),
+				  max42500_init.wd_close);
+	if (err)
+		return err;
+
+	/* Set watchdog first update window */
+	err = max42500_reg_update(st,
+				  MAX42500_REG_WDCFG2,
+				  GENMASK(2, 0),
+				  max42500_init.wd_1ud);
+	if (err)
+		return err;
+
+	/* Set watchdog enable */
+	err = max42500_reg_update(st,
+				  MAX42500_REG_WDCFG2,
+				  BIT(3),
+				  max42500_init.wd_en);
+	if (err)
+		return err;
+
+	/* Update parameters */
+	st->config->vmon_en = max42500_init.vmon_en;
+	st->config->vmon_vmpd = max42500_init.vmon_vmpd;
+	st->config->reset_map = max42500_init.reset_map;
+	st->config->wd_mode = max42500_init.wd_mode;
+	st->config->wd_cdiv = max42500_init.wd_cdiv;
+	st->config->wd_open = max42500_init.wd_open;
+	st->config->wd_close = max42500_init.wd_close;
+	st->config->wd_1ud = max42500_init.wd_1ud;
+	st->config->wd_en = max42500_init.wd_en;
+
+	return 0;
+}
+
+static const struct hwmon_ops max42500_hwmon_ops = {
+	.is_visible = max42500_is_visible,
+	.read = max42500_read,
+	.write = max42500_write,
+};
+
+static const struct hwmon_channel_info *max42500_info[] = {
+	HWMON_CHANNEL_INFO(chip,
+			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY |
+			HWMON_C_TEMP_RESET_HISTORY | HWMON_C_POWER_RESET_HISTORY |
+			HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL | HWMON_C_ALARMS,
+			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY,
+			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY,
+			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY,
+			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY,
+			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY,
+			HWMON_C_IN_RESET_HISTORY | HWMON_C_CURR_RESET_HISTORY),
+	HWMON_CHANNEL_INFO(in,
+			HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_MIN |
+			HWMON_I_HIGHEST | HWMON_I_MAX,
+			HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_MIN |
+			HWMON_I_HIGHEST | HWMON_I_MAX,
+			HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_MIN,
+			HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_MIN,
+			HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_MIN,
+			HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL,
+			HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL,
+			HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL,
+			HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL, HWMON_I_LABEL),
+	NULL
+};
+
+static const struct hwmon_chip_info max42500_chip_info = {
+	.ops = &max42500_hwmon_ops,
+	.info = max42500_info,
+};
+
+static const struct regmap_config max42500_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0x2D
+};
+
+static int max42500_probe(struct i2c_client *client)
+{
+	struct device *hwmon_dev;
+	struct max42500_state *st;
+	int err;
+
+	st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
+	if (!st)
+		return -ENOMEM;
+
+	st->client = client;
+	st->regmap = devm_regmap_init_i2c(client, &max42500_regmap_config);
+	if (IS_ERR(st->regmap))
+		return PTR_ERR(st->regmap);
+
+	err = max42500_init_client(st, &client->dev);
+	if (err)
+		return err;
+
+	hwmon_dev = devm_hwmon_device_register_with_info(&client->dev,
+					client->name, st, &max42500_chip_info, NULL);
+
+	return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct of_device_id max42500_of_match[] = {
+	{ .compatible = "adi,max42500" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, max42500_of_match);
+
+static const struct i2c_device_id max42500_id[] = {
+	{ "max42500", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, max42500_id);
+
+static struct i2c_driver max42500_driver = {
+	.driver = {
+		.name	= "max42500",
+		.of_match_table = max42500_of_match,
+	},
+	.probe		= max42500_probe,
+	.id_table	= max42500_id,
+};
+
+module_i2c_driver(max42500_driver);
+
+MODULE_AUTHOR("Kent Libetario <kent.libetario@analog.com>");
+MODULE_DESCRIPTION("Hwmon driver for MAX42500");
+MODULE_LICENSE("GPL");