Add device support for MAX22530-MAX22531.
Implement scale and read functionality for raw/filtered ADC readings.
Signed-off-by: Abhinav Jain <jain.abhinav177@gmail.com>
---
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 10 ++
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/max22531.c | 191 +++++++++++++++++++++++++++++++++++++
4 files changed, 203 insertions(+)
create mode 100644 drivers/iio/adc/max22531.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 6f26db9cf742..8a63faf1bde7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14708,6 +14708,7 @@ L: linux-iio@vger.kernel.org
S: Maintained
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/adc/adi,max22531.yaml
+F: drivers/iio/adc/max22531.c
MAX31335 RTC DRIVER
M: Antoniu Miclaus <antoniu.miclaus@analog.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index ea3ba1397392..a35c3c945e27 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -933,6 +933,16 @@ config MAX1363
To compile this driver as a module, choose M here: the module will be
called max1363.
+config MAX22531
+ tristate "Analog Devices MAX22531 ADC Driver"
+ depends on SPI
+ help
+ Say yes here to build support for field-side self-powered 12-bit
+ isolated Maxim ADCs. (max22530, max22531, max22532).
+
+ To compile this driver as a module, choose M here: the module will be
+ called max22531.
+
config MAX34408
tristate "Maxim max34408/max344089 ADC driver"
depends on I2C
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 09ae6edb2650..0429d9da571f 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -83,6 +83,7 @@ obj-$(CONFIG_MAX11205) += max11205.o
obj-$(CONFIG_MAX11410) += max11410.o
obj-$(CONFIG_MAX1241) += max1241.o
obj-$(CONFIG_MAX1363) += max1363.o
+obj-$(CONFIG_MAX22531) += max22531.o
obj-$(CONFIG_MAX34408) += max34408.o
obj-$(CONFIG_MAX77541_ADC) += max77541-adc.o
obj-$(CONFIG_MAX9611) += max9611.o
diff --git a/drivers/iio/adc/max22531.c b/drivers/iio/adc/max22531.c
new file mode 100644
index 000000000000..fb035225e426
--- /dev/null
+++ b/drivers/iio/adc/max22531.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MAX22531 SPI ADC Driver
+ *
+ * Copyright (C) 2025 Abhinav Jain
+ *
+ * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max22530-max22532.pdf
+ */
+
+#include <linux/module.h>
+#include <linux/unaligned.h>
+#include <linux/spi/spi.h>
+#include <linux/iio/iio.h>
+#include <linux/regulator/consumer.h>
+
+#define MAX22531_REG_PROD_ID 0x00
+#define MAX22531_REG_ADC_CHAN(x) ((x) + 1)
+#define MAX22531_REG_FADC_CHAN(x) ((x) + 1)
+
+#define MAX22531_VREF_MV 1800
+#define MAX22531_DEVICE_REV_MSK GENMASK(6, 0)
+#define MAX22531_DEVICE_REV 0x01
+
+#define MAX22531_REG_ADDR_MASK GENMASK(7, 2)
+#define MAX22531_REG_WRITE_MASK BIT(1)
+
+enum max22531_id {
+ max22530,
+ max22531,
+ max22532,
+};
+
+struct max22531_chip_info {
+ const char *name;
+};
+
+static struct max22531_chip_info max22531_chip_info_tbl[] = {
+ [max22530] = {
+ .name = "max22530",
+ },
+ [max22531] = {
+ .name = "max22531",
+ },
+ [max22532] = {
+ .name = "max22532",
+ },
+};
+
+struct max22531 {
+ struct spi_device *spi_dev;
+ const struct max22531_chip_info *chip_info;
+};
+
+#define MAX22531_CHANNEL(ch) \
+ { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = (ch), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_AVERAGE_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ }
+
+static const struct iio_chan_spec max22531_channels[] = {
+ MAX22531_CHANNEL(0),
+ MAX22531_CHANNEL(1),
+ MAX22531_CHANNEL(2),
+ MAX22531_CHANNEL(3),
+};
+
+static int max22531_reg_read(struct max22531 *adc, unsigned int reg,
+ unsigned int *readval)
+{
+ u8 cmd;
+
+ cmd = FIELD_PREP(MAX22531_REG_ADDR_MASK, reg);
+ *readval = spi_w8r16be(adc->spi_dev, cmd);
+ if (*readval < 0)
+ return *readval;
+
+ return 0;
+}
+
+static int max22531_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct max22531 *adc = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = max22531_reg_read(adc, MAX22531_REG_ADC_CHAN(chan->channel), val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_AVERAGE_RAW:
+ ret = max22531_reg_read(adc, MAX22531_REG_FADC_CHAN(chan->channel), val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = MAX22531_VREF_MV;
+ *val2 = 12;
+
+ return IIO_VAL_FRACTIONAL_LOG2;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info max22531_info = {
+ .read_raw = max22531_read_raw,
+};
+
+static int max22531_probe(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev;
+ struct max22531 *adc;
+ unsigned int prod_id;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ adc = iio_priv(indio_dev);
+ adc->spi_dev = spi;
+ adc->chip_info = spi_get_device_match_data(spi);
+ if (!adc->chip_info)
+ return dev_err_probe(&spi->dev, -EINVAL,
+ "no chip info\n");
+
+ indio_dev->name = adc->chip_info->name;
+ indio_dev->info = &max22531_info;
+ indio_dev->channels = max22531_channels;
+ indio_dev->num_channels = ARRAY_SIZE(max22531_channels);
+
+ ret = devm_regulator_get_enable(&spi->dev, "vddl");
+ if (ret)
+ return dev_err_probe(&spi->dev, ret,
+ "Failed to retrieve power logic supply.\n");
+
+ ret = devm_regulator_get_enable(&spi->dev, "vddpl");
+ if (ret)
+ return dev_err_probe(&spi->dev, ret,
+ "Failed to retrieve isolated DC-DC supply.\n");
+
+ ret = max22531_reg_read(adc, MAX22531_REG_PROD_ID, &prod_id);
+ if (ret ||
+ FIELD_GET(MAX22531_DEVICE_REV_MSK, prod_id) != MAX22531_DEVICE_REV)
+ dev_warn(&spi->dev, "PROD_ID verification failed\n");
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct spi_device_id max22531_id[] = {
+ { "max22530", (kernel_ulong_t)&max22531_chip_info_tbl[max22530] },
+ { "max22531", (kernel_ulong_t)&max22531_chip_info_tbl[max22531] },
+ { "max22532", (kernel_ulong_t)&max22531_chip_info_tbl[max22532] },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, max22531_id);
+
+static const struct of_device_id max22531_spi_of_id[] = {
+ { .compatible = "adi,max22530",
+ .data = &max22531_chip_info_tbl[max22530], },
+ { .compatible = "adi,max22531",
+ .data = &max22531_chip_info_tbl[max22531], },
+ { .compatible = "adi,max22532",
+ .data = &max22531_chip_info_tbl[max22532], },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max22531_spi_of_id);
+
+static struct spi_driver max22531_driver = {
+ .driver = {
+ .name = "max22531",
+ .of_match_table = max22531_spi_of_id,
+ },
+ .probe = max22531_probe,
+ .id_table = max22531_id,
+};
+module_spi_driver(max22531_driver);
+
+MODULE_AUTHOR("Abhinav Jain <jain.abhinav177@gmail.com>");
+MODULE_DESCRIPTION("MAX22531 ADC");
+MODULE_LICENSE("GPL");
--
2.34.1
On Tue, 26 Aug 2025 02:55:49 +0530 Abhinav Jain <jain.abhinav177@gmail.com> wrote: > Add device support for MAX22530-MAX22531. > Implement scale and read functionality for raw/filtered ADC readings. > > Signed-off-by: Abhinav Jain <jain.abhinav177@gmail.com> Hi Abhinav, A few minor style related things and one question on FADC registers address. Thanks, Jonathan > M: Antoniu Miclaus <antoniu.miclaus@analog.com> > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index ea3ba1397392..a35c3c945e27 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -933,6 +933,16 @@ config MAX1363 > To compile this driver as a module, choose M here: the module will be > called max1363. > > +config MAX22531 > + tristate "Analog Devices MAX22531 ADC Driver" > + depends on SPI > + help > + Say yes here to build support for field-side self-powered 12-bit > + isolated Maxim ADCs. (max22530, max22531, max22532). Use a list - max22530 - max22531 etc because it means new parts being added create less fuzz. We've gotten this wrong in far too many drivers and ended up with messier follow up series as a result! > + > + To compile this driver as a module, choose M here: the module will be > + called max22531. Should be tab index + 2 spaces for whole help block. > + > diff --git a/drivers/iio/adc/max22531.c b/drivers/iio/adc/max22531.c > new file mode 100644 > index 000000000000..fb035225e426 > --- /dev/null > +++ b/drivers/iio/adc/max22531.c > @@ -0,0 +1,191 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * MAX22531 SPI ADC Driver > + * > + * Copyright (C) 2025 Abhinav Jain > + * > + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max22530-max22532.pdf > + */ > + > +#include <linux/module.h> > +#include <linux/unaligned.h> > +#include <linux/spi/spi.h> > +#include <linux/iio/iio.h> > +#include <linux/regulator/consumer.h> As per the build bot report, there are some headers that want to be here and aren't. In general aim for following Include What You Use IWYU principles for kernel code, subject to some fuzz around headers that are always used via an other one. > + > +#define MAX22531_REG_PROD_ID 0x00 > +#define MAX22531_REG_ADC_CHAN(x) ((x) + 1) > +#define MAX22531_REG_FADC_CHAN(x) ((x) + 1) I'm confused. Why the same registers for both of these? If they really are the same, perhaps one macro is enough. > + > +#define MAX22531_VREF_MV 1800 > +#define MAX22531_DEVICE_REV_MSK GENMASK(6, 0) > +#define MAX22531_DEVICE_REV 0x01 > + > +#define MAX22531_REG_ADDR_MASK GENMASK(7, 2) > +#define MAX22531_REG_WRITE_MASK BIT(1) > + > +enum max22531_id { > + max22530, > + max22531, > + max22532, > +}; > + > +struct max22531_chip_info { > + const char *name; > +}; > + > +static struct max22531_chip_info max22531_chip_info_tbl[] = { > + [max22530] = { > + .name = "max22530", > + }, > + [max22531] = { > + .name = "max22531", > + }, > + [max22532] = { > + .name = "max22532", > + }, > +}; See below for reasoning. Split these into separate structures rather than an array. > +static int max22531_reg_read(struct max22531 *adc, unsigned int reg, > + unsigned int *readval) > +{ > + u8 cmd; > + > + cmd = FIELD_PREP(MAX22531_REG_ADDR_MASK, reg); > + *readval = spi_w8r16be(adc->spi_dev, cmd); Rather than having side effect of leaving a negative in *readval, use a local variable and only assign readval if all is good. > + if (*readval < 0) > + return *readval; > + > + return 0; > +} > +static int max22531_probe(struct spi_device *spi) > +{ > + ret = max22531_reg_read(adc, MAX22531_REG_PROD_ID, &prod_id); > + if (ret || A failure to read is a bug that we should fail on, whereas the value read not matching is indeed something were a warn or info makes sense. So split this check if (ret) return ret; if (FIELD_GET()... dev_warn > + FIELD_GET(MAX22531_DEVICE_REV_MSK, prod_id) != MAX22531_DEVICE_REV) > + dev_warn(&spi->dev, "PROD_ID verification failed\n"); > + > + return devm_iio_device_register(&spi->dev, indio_dev); > +} > + > +static const struct spi_device_id max22531_id[] = { > + { "max22530", (kernel_ulong_t)&max22531_chip_info_tbl[max22530] }, > + { "max22531", (kernel_ulong_t)&max22531_chip_info_tbl[max22531] }, > + { "max22532", (kernel_ulong_t)&max22531_chip_info_tbl[max22532] }, Whilst this style used to be common, over time we've come to the conclusion that an indexed array for these doesn't bring value. Instead just have separate structures with names that indicate which chip they are for. max22532_chip_info etc That allows the enum to be dropped which has the advantage of removing the temptation to use it for anything else (which is usually a bad idea) > + { } > +}; > +MODULE_DEVICE_TABLE(spi, max22531_id); > + > +static const struct of_device_id max22531_spi_of_id[] = { > + { .compatible = "adi,max22530", > + .data = &max22531_chip_info_tbl[max22530], }, > + { .compatible = "adi,max22531", > + .data = &max22531_chip_info_tbl[max22531], }, > + { .compatible = "adi,max22532", > + .data = &max22531_chip_info_tbl[max22532], }, > + { }
Hi Abhinav, kernel test robot noticed the following build errors: [auto build test ERROR on 19272b37aa4f83ca52bdf9c16d5d81bdd1354494] url: https://github.com/intel-lab-lkp/linux/commits/Abhinav-Jain/dt-bindings-iio-adc-Add-device-tree-binding-for-MAX22531-ADC/20250826-052702 base: 19272b37aa4f83ca52bdf9c16d5d81bdd1354494 patch link: https://lore.kernel.org/r/edc52c93e0d4e08619ba8a98674aeb7d49e6dd1b.1756115378.git.jain.abhinav177%40gmail.com patch subject: [PATCH v1 2/2] iio: adc: Add initial support for MAX22531 ADC config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20250826/202508262023.u2lGZ2mB-lkp@intel.com/config) compiler: m68k-linux-gcc (GCC) 15.1.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250826/202508262023.u2lGZ2mB-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/202508262023.u2lGZ2mB-lkp@intel.com/ All errors (new ones prefixed by >>): drivers/iio/adc/max22531.c: In function 'max22531_reg_read': >> drivers/iio/adc/max22531.c:76:15: error: implicit declaration of function 'FIELD_PREP' [-Wimplicit-function-declaration] 76 | cmd = FIELD_PREP(MAX22531_REG_ADDR_MASK, reg); | ^~~~~~~~~~ drivers/iio/adc/max22531.c: In function 'max22531_probe': >> drivers/iio/adc/max22531.c:154:13: error: implicit declaration of function 'FIELD_GET' [-Wimplicit-function-declaration] 154 | FIELD_GET(MAX22531_DEVICE_REV_MSK, prod_id) != MAX22531_DEVICE_REV) | ^~~~~~~~~ vim +/FIELD_PREP +76 drivers/iio/adc/max22531.c 70 71 static int max22531_reg_read(struct max22531 *adc, unsigned int reg, 72 unsigned int *readval) 73 { 74 u8 cmd; 75 > 76 cmd = FIELD_PREP(MAX22531_REG_ADDR_MASK, reg); 77 *readval = spi_w8r16be(adc->spi_dev, cmd); 78 if (*readval < 0) 79 return *readval; 80 81 return 0; 82 } 83 84 static int max22531_read_raw(struct iio_dev *indio_dev, 85 struct iio_chan_spec const *chan, 86 int *val, int *val2, long mask) 87 { 88 struct max22531 *adc = iio_priv(indio_dev); 89 int ret; 90 91 switch (mask) { 92 case IIO_CHAN_INFO_RAW: 93 ret = max22531_reg_read(adc, MAX22531_REG_ADC_CHAN(chan->channel), val); 94 if (ret) 95 return ret; 96 return IIO_VAL_INT; 97 98 case IIO_CHAN_INFO_AVERAGE_RAW: 99 ret = max22531_reg_read(adc, MAX22531_REG_FADC_CHAN(chan->channel), val); 100 if (ret) 101 return ret; 102 return IIO_VAL_INT; 103 104 case IIO_CHAN_INFO_SCALE: 105 *val = MAX22531_VREF_MV; 106 *val2 = 12; 107 108 return IIO_VAL_FRACTIONAL_LOG2; 109 110 default: 111 return -EINVAL; 112 } 113 } 114 115 static const struct iio_info max22531_info = { 116 .read_raw = max22531_read_raw, 117 }; 118 119 static int max22531_probe(struct spi_device *spi) 120 { 121 struct iio_dev *indio_dev; 122 struct max22531 *adc; 123 unsigned int prod_id; 124 int ret; 125 126 indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); 127 if (!indio_dev) 128 return -ENOMEM; 129 130 adc = iio_priv(indio_dev); 131 adc->spi_dev = spi; 132 adc->chip_info = spi_get_device_match_data(spi); 133 if (!adc->chip_info) 134 return dev_err_probe(&spi->dev, -EINVAL, 135 "no chip info\n"); 136 137 indio_dev->name = adc->chip_info->name; 138 indio_dev->info = &max22531_info; 139 indio_dev->channels = max22531_channels; 140 indio_dev->num_channels = ARRAY_SIZE(max22531_channels); 141 142 ret = devm_regulator_get_enable(&spi->dev, "vddl"); 143 if (ret) 144 return dev_err_probe(&spi->dev, ret, 145 "Failed to retrieve power logic supply.\n"); 146 147 ret = devm_regulator_get_enable(&spi->dev, "vddpl"); 148 if (ret) 149 return dev_err_probe(&spi->dev, ret, 150 "Failed to retrieve isolated DC-DC supply.\n"); 151 152 ret = max22531_reg_read(adc, MAX22531_REG_PROD_ID, &prod_id); 153 if (ret || > 154 FIELD_GET(MAX22531_DEVICE_REV_MSK, prod_id) != MAX22531_DEVICE_REV) 155 dev_warn(&spi->dev, "PROD_ID verification failed\n"); 156 157 return devm_iio_device_register(&spi->dev, indio_dev); 158 } 159 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
© 2016 - 2025 Red Hat, Inc.