[PATCH v10 2/2] iio: adc: max14001: New driver

Marilene Andrade Garcia posted 2 patches 1 month ago
There is a newer version of this series
[PATCH v10 2/2] iio: adc: max14001: New driver
Posted by Marilene Andrade Garcia 1 month ago
The MAX14001/MAX14002 is configurable, isolated 10-bit ADCs for multi-range 
binary inputs. In addition to ADC readings, the MAX14001/MAX14002 offers 
more features, like a binary comparator, a filtered reading that can 
provide the average of the last 2, 4, or 8 ADC readings, and an inrush 
comparator that triggers the inrush current. There is also a fault feature 
that can diagnose seven possible fault conditions. And an option to select 
an external or internal ADC voltage reference.

MAX14001/MAX14002 features implemented so far:
- Raw ADC reading.
- Filtered ADC average reading with the default configuration.
- MV fault disable.
- Selection of external or internal ADC voltage reference, depending on
whether it is declared in the device tree.

Co-developed-by: Marilene Andrade Garcia <marilene.agarcia@gmail.com>
Signed-off-by: Marilene Andrade Garcia <marilene.agarcia@gmail.com>
Signed-off-by: Kim Seer Paller <kimseer.paller@analog.com>
---
 MAINTAINERS                |   1 +
 drivers/iio/adc/Kconfig    |  10 ++
 drivers/iio/adc/Makefile   |   1 +
 drivers/iio/adc/max14001.c | 355 +++++++++++++++++++++++++++++++++++++
 4 files changed, 367 insertions(+)
 create mode 100644 drivers/iio/adc/max14001.c

diff --git a/MAINTAINERS b/MAINTAINERS
index f145f0204407..b6457063da6c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14976,6 +14976,7 @@ L:	linux-iio@vger.kernel.org
 S:	Maintained
 W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/devicetree/bindings/iio/adc/adi,max14001.yaml
+F:	drivers/iio/adc/max14001.c
 
 MAXBOTIX ULTRASONIC RANGER IIO DRIVER
 M:	Andreas Klinger <ak@it-klinger.de>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index e3d3826c3357..11e911ceab4c 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -958,6 +958,16 @@ config MAX11410
 	  To compile this driver as a module, choose M here: the module will be
 	  called max11410.
 
+config MAX14001
+	tristate "Analog Devices MAX14001/MAX14002 ADC driver"
+	depends on SPI
+	help
+	  Say yes here to build support for Analog Devices MAX14001/MAX14002
+	  Configurable, Isolated 10-bit ADCs for Multi-Range Binary Inputs.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called max14001.
+
 config MAX1241
 	tristate "Maxim max1241 ADC driver"
 	depends on SPI_MASTER
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 89d72bf9ce70..569f2f5613d4 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -85,6 +85,7 @@ obj-$(CONFIG_MAX11100) += max11100.o
 obj-$(CONFIG_MAX1118) += max1118.o
 obj-$(CONFIG_MAX11205) += max11205.o
 obj-$(CONFIG_MAX11410) += max11410.o
+obj-$(CONFIG_MAX14001) += max14001.o
 obj-$(CONFIG_MAX1241) += max1241.o
 obj-$(CONFIG_MAX1363) += max1363.o
 obj-$(CONFIG_MAX34408) += max34408.o
diff --git a/drivers/iio/adc/max14001.c b/drivers/iio/adc/max14001.c
new file mode 100644
index 000000000000..6755df152976
--- /dev/null
+++ b/drivers/iio/adc/max14001.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+/*
+ * Analog Devices MAX14001/MAX14002 ADC driver
+ *
+ * Copyright (C) 2023-2025 Analog Devices Inc.
+ * Copyright (C) 2023 Kim Seer Paller <kimseer.paller@analog.com>
+ * Copyright (c) 2025 Marilene Andrade Garcia <marilene.agarcia@gmail.com>
+ *
+ * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX14001-MAX14002.pdf
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitrev.h>
+#include <linux/bits.h>
+#include <linux/byteorder/generic.h>
+#include <linux/device.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+
+/* MAX14001 Registers Address */
+#define MAX14001_REG_ADC		0x00
+#define MAX14001_REG_FADC		0x01
+#define MAX14001_REG_FLAGS		0x02
+#define MAX14001_REG_FLTEN		0x03
+#define MAX14001_REG_THL		0x04
+#define MAX14001_REG_THU		0x05
+#define MAX14001_REG_INRR		0x06
+#define MAX14001_REG_INRT		0x07
+#define MAX14001_REG_INRP		0x08
+#define MAX14001_REG_CFG		0x09
+#define MAX14001_REG_ENBL		0x0A
+#define MAX14001_REG_ACT		0x0B
+#define MAX14001_REG_WEN		0x0C
+
+#define MAX14001_REG_VERIFICATION(x)	((x) + 0x10)
+
+#define MAX14001_REG_CFG_EXRF		BIT(5)
+
+#define MAX14001_MASK_ADDR		GENMASK(15, 11)
+#define MAX14001_MASK_DATA		GENMASK(9, 0)
+
+#define MAX14001_SET_WRITE_BIT		BIT(10)
+#define MAX14001_WRITE_WEN		0x294
+
+enum max14001_chip_model {
+	max14001,
+	max14002,
+};
+
+struct max14001_chip_info {
+	const char *name;
+};
+
+struct max14001_state {
+	const struct max14001_chip_info *chip_info;
+	struct spi_device *spi;
+	int vref_mv;
+	/*
+	 * lock protect against multiple concurrent accesses, RMW sequence,
+	 * and SPI transfer.
+	 */
+	struct mutex lock;
+	/*
+	 * The following buffers will be bit-reversed during device
+	 * communication, because the device transmits and receives data
+	 * LSB-first.
+	 * DMA (thus cache coherency maintenance) requires the transfer
+	 * buffers to live in their own cache lines.
+	 */
+	__be16 spi_tx_buffer __aligned(IIO_DMA_MINALIGN);
+	__be16 spi_rx_buffer;
+};
+
+static struct max14001_chip_info max14001_chip_info_tbl[] = {
+	[max14001] = {
+		.name = "max14001",
+	},
+	[max14002] = {
+		.name = "max14002",
+	},
+};
+
+static int max14001_read(struct max14001_state *st, u16 reg_addr, u16 *reg_data)
+{
+	struct spi_transfer xfers[] = {
+		{
+			.tx_buf = &st->spi_tx_buffer,
+			.len = sizeof(st->spi_tx_buffer),
+			.cs_change = 1,
+		}, {
+			.rx_buf = &st->spi_rx_buffer,
+			.len = sizeof(st->spi_rx_buffer),
+		},
+	};
+	int ret;
+
+	/*
+	 * Prepare SPI transmit buffer 16 bit-value big-endian format and
+	 * reverses bit order to align with the LSB-first input on SDI port
+	 * in order to meet the device communication requirements.
+	 */
+	st->spi_tx_buffer = FIELD_PREP(MAX14001_MASK_ADDR, reg_addr);
+	st->spi_tx_buffer = bitrev16(cpu_to_be16(st->spi_tx_buffer));
+
+	ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers));
+	if (ret)
+		return ret;
+
+	/*
+	 * Convert received 16-bit value from big-endian to cpu-endian format
+	 * and reverses bit order.
+	 */
+	st->spi_rx_buffer = bitrev16(be16_to_cpu(st->spi_rx_buffer));
+	*reg_data = FIELD_GET(MAX14001_MASK_DATA, st->spi_rx_buffer);
+
+	return 0;
+}
+
+static int max14001_write(struct max14001_state *st, u16 reg_addr, u16 reg_data)
+{
+	/*
+	 * Prepare SPI transmit buffer 16 bit-value big-endian format and
+	 * reverses bit order to align with the LSB-first input on SDI port
+	 * in order to meet the device communication requirements.
+	 */
+	st->spi_tx_buffer = FIELD_PREP(MAX14001_MASK_ADDR, reg_addr) |
+			    FIELD_PREP(MAX14001_SET_WRITE_BIT, 1) |
+			    FIELD_PREP(MAX14001_MASK_DATA, reg_data);
+	st->spi_tx_buffer = bitrev16(cpu_to_be16(st->spi_tx_buffer));
+
+	return spi_write(st->spi, &st->spi_tx_buffer, sizeof(st->spi_tx_buffer));
+}
+
+static int max14001_write_single_reg(struct max14001_state *st, u16 reg_addr,
+				     u16 reg_data)
+{
+	int ret;
+
+	/* Enable register write */
+	ret = max14001_write(st, MAX14001_REG_WEN, MAX14001_WRITE_WEN);
+	if (ret)
+		return ret;
+
+	/* Write data into register */
+	ret = max14001_write(st, reg_addr, reg_data);
+	if (ret)
+		return ret;
+
+	/* Disable register write */
+	ret = max14001_write(st, MAX14001_REG_WEN, 0);
+	if (ret)
+		return ret;
+
+	return ret;
+}
+
+static int max14001_write_verification_reg(struct max14001_state *st,
+					   u16 reg_addr)
+{
+	u16 reg_data;
+	int ret;
+
+	ret = max14001_read(st, reg_addr, &reg_data);
+	if (ret)
+		return ret;
+
+	return max14001_write(st, MAX14001_REG_VERIFICATION(reg_addr), reg_data);
+}
+
+static int max14001_read_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int *val, int *val2, long mask)
+{
+	struct max14001_state *st = iio_priv(indio_dev);
+	u16 reg_data;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		mutex_lock(&st->lock);
+		ret = max14001_read(st, MAX14001_REG_ADC, &reg_data);
+		mutex_unlock(&st->lock);
+		if (ret)
+			return ret;
+
+		*val = reg_data;
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_AVERAGE_RAW:
+		mutex_lock(&st->lock);
+		ret = max14001_read(st, MAX14001_REG_FADC, &reg_data);
+		mutex_unlock(&st->lock);
+		if (ret)
+			return ret;
+
+		*val = reg_data;
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		*val = st->vref_mv;
+		*val2 = 10;
+
+		return IIO_VAL_FRACTIONAL_LOG2;
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info max14001_info = {
+	.read_raw = max14001_read_raw,
+};
+
+static const struct iio_chan_spec max14001_channel[] = {
+	{
+		.type = IIO_VOLTAGE,
+		.indexed = 1,
+		.channel = 0,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_AVERAGE_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+	}
+};
+
+static int max14001_disable_mv_fault(struct max14001_state *st)
+{
+	u16 reg_addr;
+	int ret;
+
+	/* Enable SPI Registers Write */
+	ret = max14001_write(st, MAX14001_REG_WEN, MAX14001_WRITE_WEN);
+	if (ret)
+		return ret;
+
+	/*
+	 * Reads all registers and writes the values back to their appropriate
+	 * verification registers to clear the Memory Validation fault.
+	 */
+	for (reg_addr = MAX14001_REG_FLTEN; reg_addr <= MAX14001_REG_ENBL; reg_addr++) {
+		ret = max14001_write_verification_reg(st, reg_addr);
+		if (ret)
+			return ret;
+	}
+
+	/* Disable SPI Registers Write */
+	return max14001_write(st, MAX14001_REG_WEN, 0);
+}
+
+static int max14001_probe(struct spi_device *spi)
+{
+	struct iio_dev *indio_dev;
+	struct max14001_state *st;
+	struct device *dev = &spi->dev;
+	int ret, ext_vrefin = 0;
+	u16 reg_data;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	st = iio_priv(indio_dev);
+	st->spi = spi;
+	st->chip_info = spi_get_device_match_data(spi);
+	if (!st->chip_info)
+		return dev_err_probe(dev, -ENODEV, "Failed to get match data\n");
+
+	indio_dev->name = st->chip_info->name;
+	indio_dev->info = &max14001_info;
+	indio_dev->channels = max14001_channel;
+	indio_dev->num_channels = ARRAY_SIZE(max14001_channel);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ret = devm_regulator_get_enable(dev, "vdd");
+	if (ret)
+		return dev_err_probe(dev, ret,
+			"Failed to enable specified Vdd supply\n");
+
+	ret = devm_regulator_get_enable(dev, "vddl");
+	if (ret)
+		return dev_err_probe(dev, ret,
+			"Failed to enable specified Vddl supply\n");
+
+	ret = devm_regulator_get_enable_read_voltage(dev, "vrefin");
+	if (ret < 0) {
+		st->vref_mv = 1250000 / 1000;
+	} else {
+		st->vref_mv = ret / 1000;
+		ext_vrefin = 1;
+	}
+
+	ret = devm_mutex_init(dev, &st->lock);
+	if (ret)
+		return dev_err_probe(dev, ret,
+			"Failed to init the mutex\n");
+
+	if (ext_vrefin) {
+		/*
+		 * Configure the MAX14001/MAX14002 to use an external voltage
+		 * reference source for the ADC.
+		 */
+		ret = max14001_read(st, MAX14001_REG_CFG, &reg_data);
+		if (ret)
+			return dev_err_probe(dev, ret,
+				"Failed to read Configuration Register\n");
+
+		reg_data |= FIELD_PREP(MAX14001_REG_CFG_EXRF, 1);
+		ret = max14001_write_single_reg(st, MAX14001_REG_CFG, reg_data);
+		if (ret)
+			return dev_err_probe(dev, ret,
+				"Failed to set Configuration Register\n");
+	}
+
+	ret = max14001_disable_mv_fault(st);
+	if (ret)
+		return dev_err_probe(dev, ret,
+			"Failed to disable MV Fault\n");
+
+	return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct spi_device_id max14001_id_table[] = {
+	{ "max14001", (kernel_ulong_t)&max14001_chip_info_tbl[max14001] },
+	{ "max14002", (kernel_ulong_t)&max14001_chip_info_tbl[max14002] },
+	{ }
+};
+
+static const struct of_device_id max14001_of_match[] = {
+	{ .compatible = "adi,max14001",
+	  .data = &max14001_chip_info_tbl[max14001], },
+	{ .compatible = "adi,max14002",
+	  .data = &max14001_chip_info_tbl[max14002], },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, max14001_of_match);
+
+static struct spi_driver max14001_driver = {
+	.driver = {
+		.name = "max14001",
+		.of_match_table = max14001_of_match,
+	},
+	.probe = max14001_probe,
+	.id_table = max14001_id_table,
+};
+module_spi_driver(max14001_driver);
+
+MODULE_AUTHOR("Kim Seer Paller <kimseer.paller@analog.com>");
+MODULE_AUTHOR("Marilene Andrade Garcia <marilene.agarcia@gmail.com>");
+MODULE_DESCRIPTION("Analog Devices MAX14001/MAX14002 ADCs driver");
+MODULE_LICENSE("GPL");
-- 
2.34.1
Re: [PATCH v10 2/2] iio: adc: max14001: New driver
Posted by kernel test robot 4 weeks, 1 day ago
Hi Marilene,

kernel test robot noticed the following build warnings:

[auto build test WARNING on d1487b0b78720b86ec2a2ac7acc683ec90627e5b]

url:    https://github.com/intel-lab-lkp/linux/commits/Marilene-Andrade-Garcia/dt-bindings-iio-adc-add-max14001/20250902-212046
base:   d1487b0b78720b86ec2a2ac7acc683ec90627e5b
patch link:    https://lore.kernel.org/r/f3ea9c127b7836cc978def5d906740c6da1cfb1e.1756816682.git.marilene.agarcia%40gmail.com
patch subject: [PATCH v10 2/2] iio: adc: max14001: New driver
config: hexagon-randconfig-r113-20250904 (https://download.01.org/0day-ci/archive/20250904/202509040617.gcAKQNlG-lkp@intel.com/config)
compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project 2e122990391b2ba062e6308a12cfedf7206270ba)
reproduce: (https://download.01.org/0day-ci/archive/20250904/202509040617.gcAKQNlG-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/202509040617.gcAKQNlG-lkp@intel.com/

sparse warnings: (new ones prefixed by >>)
>> drivers/iio/adc/max14001.c:109:27: sparse: sparse: incorrect type in assignment (different base types) @@     expected restricted __be16 [usertype] spi_tx_buffer @@     got unsigned long @@
   drivers/iio/adc/max14001.c:109:27: sparse:     expected restricted __be16 [usertype] spi_tx_buffer
   drivers/iio/adc/max14001.c:109:27: sparse:     got unsigned long
>> drivers/iio/adc/max14001.c:110:29: sparse: sparse: incorrect type in argument 1 (different base types) @@     expected unsigned short [usertype] val @@     got restricted __be16 [usertype] spi_tx_buffer @@
   drivers/iio/adc/max14001.c:110:29: sparse:     expected unsigned short [usertype] val
   drivers/iio/adc/max14001.c:110:29: sparse:     got restricted __be16 [usertype] spi_tx_buffer
>> drivers/iio/adc/max14001.c:110:29: sparse: sparse: cast from restricted __be16
>> drivers/iio/adc/max14001.c:110:29: sparse: sparse: cast from restricted __be16
>> drivers/iio/adc/max14001.c:110:29: sparse: sparse: incorrect type in initializer (different base types) @@     expected unsigned short [usertype] __x @@     got restricted __be16 [usertype] @@
   drivers/iio/adc/max14001.c:110:29: sparse:     expected unsigned short [usertype] __x
   drivers/iio/adc/max14001.c:110:29: sparse:     got restricted __be16 [usertype]
>> drivers/iio/adc/max14001.c:110:27: sparse: sparse: incorrect type in assignment (different base types) @@     expected restricted __be16 [usertype] spi_tx_buffer @@     got int @@
   drivers/iio/adc/max14001.c:110:27: sparse:     expected restricted __be16 [usertype] spi_tx_buffer
   drivers/iio/adc/max14001.c:110:27: sparse:     got int
>> drivers/iio/adc/max14001.c:120:27: sparse: sparse: incorrect type in assignment (different base types) @@     expected restricted __be16 [usertype] spi_rx_buffer @@     got int @@
   drivers/iio/adc/max14001.c:120:27: sparse:     expected restricted __be16 [usertype] spi_rx_buffer
   drivers/iio/adc/max14001.c:120:27: sparse:     got int
>> drivers/iio/adc/max14001.c:121:21: sparse: sparse: cast to restricted __be16
>> drivers/iio/adc/max14001.c:121:21: sparse: sparse: restricted __be16 degrades to integer
>> drivers/iio/adc/max14001.c:121:21: sparse: sparse: restricted __be16 degrades to integer
   drivers/iio/adc/max14001.c:133:27: sparse: sparse: incorrect type in assignment (different base types) @@     expected restricted __be16 [usertype] spi_tx_buffer @@     got unsigned long @@
   drivers/iio/adc/max14001.c:133:27: sparse:     expected restricted __be16 [usertype] spi_tx_buffer
   drivers/iio/adc/max14001.c:133:27: sparse:     got unsigned long
   drivers/iio/adc/max14001.c:136:29: sparse: sparse: incorrect type in argument 1 (different base types) @@     expected unsigned short [usertype] val @@     got restricted __be16 [usertype] spi_tx_buffer @@
   drivers/iio/adc/max14001.c:136:29: sparse:     expected unsigned short [usertype] val
   drivers/iio/adc/max14001.c:136:29: sparse:     got restricted __be16 [usertype] spi_tx_buffer
   drivers/iio/adc/max14001.c:136:29: sparse: sparse: cast from restricted __be16
   drivers/iio/adc/max14001.c:136:29: sparse: sparse: cast from restricted __be16
   drivers/iio/adc/max14001.c:136:29: sparse: sparse: incorrect type in initializer (different base types) @@     expected unsigned short [usertype] __x @@     got restricted __be16 [usertype] @@
   drivers/iio/adc/max14001.c:136:29: sparse:     expected unsigned short [usertype] __x
   drivers/iio/adc/max14001.c:136:29: sparse:     got restricted __be16 [usertype]
   drivers/iio/adc/max14001.c:136:27: sparse: sparse: incorrect type in assignment (different base types) @@     expected restricted __be16 [usertype] spi_tx_buffer @@     got int @@
   drivers/iio/adc/max14001.c:136:27: sparse:     expected restricted __be16 [usertype] spi_tx_buffer
   drivers/iio/adc/max14001.c:136:27: sparse:     got int

vim +109 drivers/iio/adc/max14001.c

    89	
    90	static int max14001_read(struct max14001_state *st, u16 reg_addr, u16 *reg_data)
    91	{
    92		struct spi_transfer xfers[] = {
    93			{
    94				.tx_buf = &st->spi_tx_buffer,
    95				.len = sizeof(st->spi_tx_buffer),
    96				.cs_change = 1,
    97			}, {
    98				.rx_buf = &st->spi_rx_buffer,
    99				.len = sizeof(st->spi_rx_buffer),
   100			},
   101		};
   102		int ret;
   103	
   104		/*
   105		 * Prepare SPI transmit buffer 16 bit-value big-endian format and
   106		 * reverses bit order to align with the LSB-first input on SDI port
   107		 * in order to meet the device communication requirements.
   108		 */
 > 109		st->spi_tx_buffer = FIELD_PREP(MAX14001_MASK_ADDR, reg_addr);
 > 110		st->spi_tx_buffer = bitrev16(cpu_to_be16(st->spi_tx_buffer));
   111	
   112		ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers));
   113		if (ret)
   114			return ret;
   115	
   116		/*
   117		 * Convert received 16-bit value from big-endian to cpu-endian format
   118		 * and reverses bit order.
   119		 */
 > 120		st->spi_rx_buffer = bitrev16(be16_to_cpu(st->spi_rx_buffer));
 > 121		*reg_data = FIELD_GET(MAX14001_MASK_DATA, st->spi_rx_buffer);
   122	
   123		return 0;
   124	}
   125	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
Re: [PATCH v10 2/2] iio: adc: max14001: New driver
Posted by David Lechner 1 month ago
On 9/2/25 8:16 AM, Marilene Andrade Garcia wrote:
> The MAX14001/MAX14002 is configurable, isolated 10-bit ADCs for multi-range 
> binary inputs. In addition to ADC readings, the MAX14001/MAX14002 offers 
> more features, like a binary comparator, a filtered reading that can 
> provide the average of the last 2, 4, or 8 ADC readings, and an inrush 
> comparator that triggers the inrush current. There is also a fault feature 
> that can diagnose seven possible fault conditions. And an option to select 
> an external or internal ADC voltage reference.
> 
> MAX14001/MAX14002 features implemented so far:
> - Raw ADC reading.
> - Filtered ADC average reading with the default configuration.
> - MV fault disable.
> - Selection of external or internal ADC voltage reference, depending on
> whether it is declared in the device tree.
> 
> Co-developed-by: Marilene Andrade Garcia <marilene.agarcia@gmail.com>
> Signed-off-by: Marilene Andrade Garcia <marilene.agarcia@gmail.com>
> Signed-off-by: Kim Seer Paller <kimseer.paller@analog.com>
> ---
>  MAINTAINERS                |   1 +
>  drivers/iio/adc/Kconfig    |  10 ++
>  drivers/iio/adc/Makefile   |   1 +
>  drivers/iio/adc/max14001.c | 355 +++++++++++++++++++++++++++++++++++++
>  4 files changed, 367 insertions(+)
>  create mode 100644 drivers/iio/adc/max14001.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f145f0204407..b6457063da6c 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14976,6 +14976,7 @@ L:	linux-iio@vger.kernel.org
>  S:	Maintained
>  W:	https://ez.analog.com/linux-software-drivers
>  F:	Documentation/devicetree/bindings/iio/adc/adi,max14001.yaml
> +F:	drivers/iio/adc/max14001.c
>  
>  MAXBOTIX ULTRASONIC RANGER IIO DRIVER
>  M:	Andreas Klinger <ak@it-klinger.de>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index e3d3826c3357..11e911ceab4c 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -958,6 +958,16 @@ config MAX11410
>  	  To compile this driver as a module, choose M here: the module will be
>  	  called max11410.
>  
> +config MAX14001
> +	tristate "Analog Devices MAX14001/MAX14002 ADC driver"
> +	depends on SPI
> +	help
> +	  Say yes here to build support for Analog Devices MAX14001/MAX14002
> +	  Configurable, Isolated 10-bit ADCs for Multi-Range Binary Inputs.
> +
> +	  To compile this driver as a module, choose M here: the module will be
> +	  called max14001.
> +
>  config MAX1241
>  	tristate "Maxim max1241 ADC driver"
>  	depends on SPI_MASTER
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 89d72bf9ce70..569f2f5613d4 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -85,6 +85,7 @@ obj-$(CONFIG_MAX11100) += max11100.o
>  obj-$(CONFIG_MAX1118) += max1118.o
>  obj-$(CONFIG_MAX11205) += max11205.o
>  obj-$(CONFIG_MAX11410) += max11410.o
> +obj-$(CONFIG_MAX14001) += max14001.o
>  obj-$(CONFIG_MAX1241) += max1241.o
>  obj-$(CONFIG_MAX1363) += max1363.o
>  obj-$(CONFIG_MAX34408) += max34408.o
> diff --git a/drivers/iio/adc/max14001.c b/drivers/iio/adc/max14001.c
> new file mode 100644
> index 000000000000..6755df152976
> --- /dev/null
> +++ b/drivers/iio/adc/max14001.c
> @@ -0,0 +1,355 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
> +/*
> + * Analog Devices MAX14001/MAX14002 ADC driver
> + *
> + * Copyright (C) 2023-2025 Analog Devices Inc.
> + * Copyright (C) 2023 Kim Seer Paller <kimseer.paller@analog.com>
> + * Copyright (c) 2025 Marilene Andrade Garcia <marilene.agarcia@gmail.com>
> + *
> + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX14001-MAX14002.pdf
> + */
> +
> +#include <linux/array_size.h>
> +#include <linux/bitfield.h>
> +#include <linux/bitrev.h>
> +#include <linux/bits.h>
> +#include <linux/byteorder/generic.h>
> +#include <linux/device.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/types.h>
> +#include <linux/kernel.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +#include <linux/types.h>
> +
> +/* MAX14001 Registers Address */
> +#define MAX14001_REG_ADC		0x00
> +#define MAX14001_REG_FADC		0x01
> +#define MAX14001_REG_FLAGS		0x02
> +#define MAX14001_REG_FLTEN		0x03
> +#define MAX14001_REG_THL		0x04
> +#define MAX14001_REG_THU		0x05
> +#define MAX14001_REG_INRR		0x06
> +#define MAX14001_REG_INRT		0x07
> +#define MAX14001_REG_INRP		0x08
> +#define MAX14001_REG_CFG		0x09
> +#define MAX14001_REG_ENBL		0x0A
> +#define MAX14001_REG_ACT		0x0B
> +#define MAX14001_REG_WEN		0x0C
> +
> +#define MAX14001_REG_VERIFICATION(x)	((x) + 0x10)
> +
> +#define MAX14001_REG_CFG_EXRF		BIT(5)
> +
> +#define MAX14001_MASK_ADDR		GENMASK(15, 11)
> +#define MAX14001_MASK_DATA		GENMASK(9, 0)
> +
> +#define MAX14001_SET_WRITE_BIT		BIT(10)
> +#define MAX14001_WRITE_WEN		0x294
> +
> +enum max14001_chip_model {
> +	max14001,
> +	max14002,
> +};
> +
> +struct max14001_chip_info {
> +	const char *name;
> +};
> +
> +struct max14001_state {
> +	const struct max14001_chip_info *chip_info;
> +	struct spi_device *spi;
> +	int vref_mv;
> +	/*
> +	 * lock protect against multiple concurrent accesses, RMW sequence,
> +	 * and SPI transfer.
> +	 */
> +	struct mutex lock;
> +	/*
> +	 * The following buffers will be bit-reversed during device
> +	 * communication, because the device transmits and receives data
> +	 * LSB-first.

Some SPI controllers may be able to handle LSB first without us having
to reverse things in the driver. Search the kernel for SPI_LSB_FIRST
to see what I mean.

By setting `spi-lsb-first;` in the devicetree, the core SPI code will
check if the controller supports it and let the hardare handle everything
so we don't need to reverse bits here in this driver.

If we need to make this work on a SPI controller that doesn't support
SPI_LSB_FIRST, then we should add something in the core SPI code that
does the reversing during __spi_optimize_message() so that every
peripheral driver doesn't have to do this. For example, search spi.c
for SPI_CS_WORD to see how it handles that flag for controllers that
don't support it.


> +	 * DMA (thus cache coherency maintenance) requires the transfer
> +	 * buffers to live in their own cache lines.
> +	 */
> +	__be16 spi_tx_buffer __aligned(IIO_DMA_MINALIGN);
> +	__be16 spi_rx_buffer;
> +};
> +



> +static int max14001_probe(struct spi_device *spi)
> +{

...

> +	ret = devm_mutex_init(dev, &st->lock);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +			"Failed to init the mutex\n");
> +

The only possible error here is -ENOMEM, which we don't
print messages for. So just `return ret;` on this one.
Re: [PATCH v10 2/2] iio: adc: max14001: New driver
Posted by Andy Shevchenko 1 month ago
On Tue, Sep 02, 2025 at 10:16:12AM -0300, Marilene Andrade Garcia wrote:
> The MAX14001/MAX14002 is configurable, isolated 10-bit ADCs for multi-range 
> binary inputs. In addition to ADC readings, the MAX14001/MAX14002 offers 
> more features, like a binary comparator, a filtered reading that can 
> provide the average of the last 2, 4, or 8 ADC readings, and an inrush 
> comparator that triggers the inrush current. There is also a fault feature 
> that can diagnose seven possible fault conditions. And an option to select 
> an external or internal ADC voltage reference.
> 
> MAX14001/MAX14002 features implemented so far:
> - Raw ADC reading.
> - Filtered ADC average reading with the default configuration.
> - MV fault disable.
> - Selection of external or internal ADC voltage reference, depending on
> whether it is declared in the device tree.

...

> +#include <linux/array_size.h>
> +#include <linux/bitfield.h>
> +#include <linux/bitrev.h>
> +#include <linux/bits.h>
> +#include <linux/byteorder/generic.h>
> +#include <linux/device.h>

> +#include <linux/iio/iio.h>
> +#include <linux/iio/types.h>

Please, move this group...

> +#include <linux/kernel.h>

This header must not be used in a driver like this.
Please, replace it with the headers that you are actually used.

> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +#include <linux/types.h>


...to be here (and add a blank line above).

...

> +#define MAX14001_REG_VERIFICATION(x)	((x) + 0x10)
> +
> +#define MAX14001_REG_CFG_EXRF		BIT(5)

Is it REG? I'm lost in the naming(s) and value(s) in this driver.

...

> +#define MAX14001_WRITE_WEN		0x294

Also, what's this? Shouldn't it be a (WRITE_EN | 0x94) ?

...

> +enum max14001_chip_model {
> +	max14001,
> +	max14002,
> +};

No need, just make the data structures separate.

...

> +struct max14001_state {
> +	const struct max14001_chip_info *chip_info;
> +	struct spi_device *spi;
> +	int vref_mv;

Can it be _mV? This will follow the real unit spelling.
(And yes, we have such suffixes in the variable names ion the kernel.)

> +	/*
> +	 * lock protect against multiple concurrent accesses, RMW sequence,
> +	 * and SPI transfer.
> +	 */
> +	struct mutex lock;

+ mutex.h

> +	/*
> +	 * The following buffers will be bit-reversed during device
> +	 * communication, because the device transmits and receives data
> +	 * LSB-first.
> +	 * DMA (thus cache coherency maintenance) requires the transfer
> +	 * buffers to live in their own cache lines.
> +	 */
> +	__be16 spi_tx_buffer __aligned(IIO_DMA_MINALIGN);
> +	__be16 spi_rx_buffer;
> +};

...

> +static int max14001_read(struct max14001_state *st, u16 reg_addr, u16 *reg_data)
> +{
> +	struct spi_transfer xfers[] = {
> +		{
> +			.tx_buf = &st->spi_tx_buffer,
> +			.len = sizeof(st->spi_tx_buffer),
> +			.cs_change = 1,
> +		}, {
> +			.rx_buf = &st->spi_rx_buffer,
> +			.len = sizeof(st->spi_rx_buffer),
> +		},
> +	};
> +	int ret;
> +
> +	/*
> +	 * Prepare SPI transmit buffer 16 bit-value big-endian format and
> +	 * reverses bit order to align with the LSB-first input on SDI port
> +	 * in order to meet the device communication requirements.
> +	 */
> +	st->spi_tx_buffer = FIELD_PREP(MAX14001_MASK_ADDR, reg_addr);
> +	st->spi_tx_buffer = bitrev16(cpu_to_be16(st->spi_tx_buffer));

Use temporary variable. There are two issues with the above:

1) the usual pattern is to avoid putting data to the external buffers /
variables until it's ready (this will be more robust against potential
synchronisation issues);

2) it's simply hard to read and follow; also it's prone to mistakes if
something more comes in the future among these lines.

> +	ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers));
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Convert received 16-bit value from big-endian to cpu-endian format
> +	 * and reverses bit order.
> +	 */
> +	st->spi_rx_buffer = bitrev16(be16_to_cpu(st->spi_rx_buffer));
> +	*reg_data = FIELD_GET(MAX14001_MASK_DATA, st->spi_rx_buffer);
> +
> +	return 0;
> +}

...

> +static int max14001_write(struct max14001_state *st, u16 reg_addr, u16 reg_data)
> +{
> +	/*
> +	 * Prepare SPI transmit buffer 16 bit-value big-endian format and
> +	 * reverses bit order to align with the LSB-first input on SDI port
> +	 * in order to meet the device communication requirements.
> +	 */
> +	st->spi_tx_buffer = FIELD_PREP(MAX14001_MASK_ADDR, reg_addr) |
> +			    FIELD_PREP(MAX14001_SET_WRITE_BIT, 1) |
> +			    FIELD_PREP(MAX14001_MASK_DATA, reg_data);
> +	st->spi_tx_buffer = bitrev16(cpu_to_be16(st->spi_tx_buffer));

Ditto.

> +	return spi_write(st->spi, &st->spi_tx_buffer, sizeof(st->spi_tx_buffer));
> +}

...

> +static int max14001_write_single_reg(struct max14001_state *st, u16 reg_addr,
> +				     u16 reg_data)
> +{
> +	int ret;
> +
> +	/* Enable register write */
> +	ret = max14001_write(st, MAX14001_REG_WEN, MAX14001_WRITE_WEN);
> +	if (ret)
> +		return ret;
> +
> +	/* Write data into register */
> +	ret = max14001_write(st, reg_addr, reg_data);
> +	if (ret)
> +		return ret;

> +	/* Disable register write */
> +	ret = max14001_write(st, MAX14001_REG_WEN, 0);
> +	if (ret)
> +		return ret;
> +
> +	return ret;

Are you expecting anything than 0 here? Why?

Also the whole block is simply

	return max14001_write(st, MAX14001_REG_WEN, 0);

> +}

...

> +static int max14001_read_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     int *val, int *val2, long mask)
> +{
> +	struct max14001_state *st = iio_priv(indio_dev);
> +	u16 reg_data;
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:

Use scoped_guard() from cleanup.h.

> +		mutex_lock(&st->lock);
> +		ret = max14001_read(st, MAX14001_REG_ADC, &reg_data);
> +		mutex_unlock(&st->lock);
> +		if (ret)
> +			return ret;
> +
> +		*val = reg_data;
> +
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_AVERAGE_RAW:
> +		mutex_lock(&st->lock);
> +		ret = max14001_read(st, MAX14001_REG_FADC, &reg_data);
> +		mutex_unlock(&st->lock);

Ditto.

> +		if (ret)
> +			return ret;
> +
> +		*val = reg_data;
> +
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_SCALE:
> +		*val = st->vref_mv;
> +		*val2 = 10;
> +
> +		return IIO_VAL_FRACTIONAL_LOG2;
> +	default:
> +		return -EINVAL;
> +	}
> +}

...

> +static const struct iio_chan_spec max14001_channel[] = {
> +	{
> +		.type = IIO_VOLTAGE,
> +		.indexed = 1,
> +		.channel = 0,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +				      BIT(IIO_CHAN_INFO_AVERAGE_RAW) |
> +				      BIT(IIO_CHAN_INFO_SCALE),
> +	}

Leave a trailing comma. It's not a terminator entry.

> +};

...

> +static int max14001_disable_mv_fault(struct max14001_state *st)
> +{
> +	u16 reg_addr;

It's enough to call it reg.

> +	int ret;
> +
> +	/* Enable SPI Registers Write */
> +	ret = max14001_write(st, MAX14001_REG_WEN, MAX14001_WRITE_WEN);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Reads all registers and writes the values back to their appropriate
> +	 * verification registers to clear the Memory Validation fault.
> +	 */
> +	for (reg_addr = MAX14001_REG_FLTEN; reg_addr <= MAX14001_REG_ENBL; reg_addr++) {
> +		ret = max14001_write_verification_reg(st, reg_addr);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	/* Disable SPI Registers Write */
> +	return max14001_write(st, MAX14001_REG_WEN, 0);
> +}

...

> +static int max14001_probe(struct spi_device *spi)
> +{
> +	struct iio_dev *indio_dev;
> +	struct max14001_state *st;
> +	struct device *dev = &spi->dev;
> +	int ret, ext_vrefin = 0;
> +	u16 reg_data;

Call it 'value' ('reg' part is redundant, and 'data' may be ambiguous in this function).

> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	st = iio_priv(indio_dev);
> +	st->spi = spi;
> +	st->chip_info = spi_get_device_match_data(spi);
> +	if (!st->chip_info)
> +		return dev_err_probe(dev, -ENODEV, "Failed to get match data\n");
> +
> +	indio_dev->name = st->chip_info->name;
> +	indio_dev->info = &max14001_info;
> +	indio_dev->channels = max14001_channel;
> +	indio_dev->num_channels = ARRAY_SIZE(max14001_channel);
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	ret = devm_regulator_get_enable(dev, "vdd");
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +			"Failed to enable specified Vdd supply\n");
> +
> +	ret = devm_regulator_get_enable(dev, "vddl");
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +			"Failed to enable specified Vddl supply\n");
> +
> +	ret = devm_regulator_get_enable_read_voltage(dev, "vrefin");
> +	if (ret < 0) {
> +		st->vref_mv = 1250000 / 1000;

(MICRO / MILLI)

> +	} else {
> +		st->vref_mv = ret / 1000;

Ditto.

> +		ext_vrefin = 1;
> +	}

And with deduplication refactored code:

	ret = devm_regulator_get_enable_read_voltage(dev, "vrefin");
	if (ret < 0)
		ret = 1250000;
	else
		ext_vrefin = 1;
	st->vref_mv = ret / (MICRO / MILLI);

> +	ret = devm_mutex_init(dev, &st->lock);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +			"Failed to init the mutex\n");

One line and do not interleave vref related pieces. I.o.w. move this block
upper before this line "ret = devm_regulator_get_enable(dev, "vdd");".

> +	if (ext_vrefin) {
> +		/*
> +		 * Configure the MAX14001/MAX14002 to use an external voltage
> +		 * reference source for the ADC.
> +		 */
> +		ret = max14001_read(st, MAX14001_REG_CFG, &reg_data);
> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +				"Failed to read Configuration Register\n");

Indentation issue. Note, for the lines ending with string literals there is no
line limit for a long time (10+ years)

> +		reg_data |= FIELD_PREP(MAX14001_REG_CFG_EXRF, 1);

FIELD_MODIFY() ?

> +		ret = max14001_write_single_reg(st, MAX14001_REG_CFG, reg_data);
> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +				"Failed to set Configuration Register\n");

As per above.

> +	}
> +
> +	ret = max14001_disable_mv_fault(st);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +			"Failed to disable MV Fault\n");

One line.

> +	return devm_iio_device_register(dev, indio_dev);
> +}

...

> +static const struct of_device_id max14001_of_match[] = {
> +	{ .compatible = "adi,max14001",
> +	  .data = &max14001_chip_info_tbl[max14001], },
> +	{ .compatible = "adi,max14002",
> +	  .data = &max14001_chip_info_tbl[max14002], },
> +	{ }

After splitting this hard-to-read style will be just as

	{ .compatible = "adi,max14001", .data = &max14001_chip_info },
	{ .compatible = "adi,max14002", .data = &max14002_chip_info },

> +};


-- 
With Best Regards,
Andy Shevchenko
Re: [PATCH v10 2/2] iio: adc: max14001: New driver
Posted by David Lechner 1 month ago
On 9/2/25 8:45 AM, Andy Shevchenko wrote:

...

>> +	ret = devm_regulator_get_enable_read_voltage(dev, "vrefin");
>> +	if (ret < 0) {
>> +		st->vref_mv = 1250000 / 1000;
> 
> (MICRO / MILLI)
> 
>> +	} else {
>> +		st->vref_mv = ret / 1000;
> 
> Ditto.
> 
>> +		ext_vrefin = 1;
>> +	}
> 
> And with deduplication refactored code:
> 
> 	ret = devm_regulator_get_enable_read_voltage(dev, "vrefin");

	if (ret < 0 && ret != -ENODEV)
		return dev_err_probe(dev, ret, "Failed to get REFIN voltage\n");

Most errors should be propagated, so we should also add this.
Only -ENODEV means that the supply was omitted from the devicetree
and we should use the internal reference voltage.

> 	if (ret < 0)
> 		ret = 1250000;
> 	else
> 		ext_vrefin = 1;
> 	st->vref_mv = ret / (MICRO / MILLI);
> 
>> +	ret = devm_mutex_init(dev, &st->lock);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret,
>> +			"Failed to init the mutex\n");
>
Re: [PATCH v10 2/2] iio: adc: max14001: New driver
Posted by Andy Shevchenko 1 month ago
On Tue, Sep 02, 2025 at 09:12:08AM -0500, David Lechner wrote:
> On 9/2/25 8:45 AM, Andy Shevchenko wrote:

...

> >> +	ret = devm_regulator_get_enable_read_voltage(dev, "vrefin");
> >> +	if (ret < 0) {
> >> +		st->vref_mv = 1250000 / 1000;
> > 
> > (MICRO / MILLI)
> > 
> >> +	} else {
> >> +		st->vref_mv = ret / 1000;
> > 
> > Ditto.
> > 
> >> +		ext_vrefin = 1;
> >> +	}
> > 
> > And with deduplication refactored code:
> > 
> > 	ret = devm_regulator_get_enable_read_voltage(dev, "vrefin");
> 
> 	if (ret < 0 && ret != -ENODEV)
> 		return dev_err_probe(dev, ret, "Failed to get REFIN voltage\n");
> 
> Most errors should be propagated, so we should also add this.
> Only -ENODEV means that the supply was omitted from the devicetree
> and we should use the internal reference voltage.

Good point.

> > 	if (ret < 0)
> > 		ret = 1250000;
> > 	else
> > 		ext_vrefin = 1;
> > 	st->vref_mv = ret / (MICRO / MILLI);

-- 
With Best Regards,
Andy Shevchenko