From: Chi-Wen Weng <cwweng.linux@gmail.com>
To: jic23@kernel.org, robh@kernel.org, krzk+dt@kernel.org,
conor+dt@kernel.org
Cc: dlechner@baylibre.com, nuno.sa@analog.com, andy@kernel.org,
linux-arm-kernel@lists.infradead.org, linux-iio@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
cwweng@nuvoton.com, cwweng.linux@gmail.com
Subject: [PATCH v2 2/2] iio: adc: Add Nuvoton MA35D1 EADC driver
Date: Mon, 13 Jul 2026 16:11:27 +0800 [thread overview]
Message-ID: <20260713081127.115197-3-cwweng.linux@gmail.com> (raw)
In-Reply-To: <20260713081127.115197-1-cwweng.linux@gmail.com>
From: Chi-Wen Weng <cwweng@nuvoton.com>
Add an IIO driver for the Nuvoton MA35D1 Enhanced ADC controller.
The driver supports interrupt-driven direct raw reads and triggered
buffered capture paced by an external IIO trigger. Buffered capture is
limited to a single enabled voltage channel per scan in this initial
implementation.
Channels are described by firmware child nodes. Single-ended external
channels and the fixed hardware differential input pairs are supported.
The driver reports IIO scale from either an optional external reference
supply or the internal 1.6 V reference.
The driver also handles the functional clock and optional reset line.
DMA support is not used by this initial driver.
Signed-off-by: Chi-Wen Weng <cwweng@nuvoton.com>
---
drivers/iio/adc/Kconfig | 12 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ma35d1_eadc.c | 740 ++++++++++++++++++++++++++++++++++
3 files changed, 753 insertions(+)
create mode 100644 drivers/iio/adc/ma35d1_eadc.c
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 1c663c98c6c9..509118dc5643 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -981,6 +981,18 @@ config LTC2497
To compile this driver as a module, choose M here: the module will be
called ltc2497.
+config MA35D1_EADC
+ tristate "Nuvoton MA35D1 EADC driver"
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select REGMAP_MMIO
+ help
+ Say yes here to build support for the Nuvoton MA35D1
+ Enhanced Analog to Digital Converter.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ma35d1_eadc.
+
config MAX1027
tristate "Maxim max1027 ADC driver"
depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 707dd708912f..7b9b38688223 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -85,6 +85,7 @@ obj-$(CONFIG_LTC2471) += ltc2471.o
obj-$(CONFIG_LTC2485) += ltc2485.o
obj-$(CONFIG_LTC2496) += ltc2496.o ltc2497-core.o
obj-$(CONFIG_LTC2497) += ltc2497.o ltc2497-core.o
+obj-$(CONFIG_MA35D1_EADC) += ma35d1_eadc.o
obj-$(CONFIG_MAX1027) += max1027.o
obj-$(CONFIG_MAX11100) += max11100.o
obj-$(CONFIG_MAX1118) += max1118.o
diff --git a/drivers/iio/adc/ma35d1_eadc.c b/drivers/iio/adc/ma35d1_eadc.c
new file mode 100644
index 000000000000..b2733e816c49
--- /dev/null
+++ b/drivers/iio/adc/ma35d1_eadc.c
@@ -0,0 +1,740 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton MA35D1 EADC driver
+ *
+ * Copyright (c) 2026 Nuvoton Technology Corp.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/types.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define MA35D1_EADC_DAT(n) (0x00 + (n) * 0x04)
+#define MA35D1_EADC_CTL 0x50
+#define MA35D1_EADC_SWTRG 0x54
+#define MA35D1_EADC_SCTL(n) (0x80 + (n) * 0x04)
+#define MA35D1_EADC_INTSRC0 0xd0
+#define MA35D1_EADC_STATUS2 0xf8
+#define MA35D1_EADC_SELSMP0 0x140
+#define MA35D1_EADC_REFADJCTL 0x150
+
+#define MA35D1_EADC_CTL_ADCEN BIT(0)
+#define MA35D1_EADC_CTL_ADCIEN0 BIT(2)
+#define MA35D1_EADC_CTL_DIFFEN BIT(8)
+#define MA35D1_EADC_CTL_DMOF BIT(9)
+#define MA35D1_EADC_CTL_VREFSEL_MASK GENMASK(11, 10)
+#define MA35D1_EADC_CTL_VREFSEL_1V6 FIELD_PREP(MA35D1_EADC_CTL_VREFSEL_MASK, 0)
+
+#define MA35D1_EADC_SCTL_CHSEL_MASK GENMASK(3, 0)
+#define MA35D1_EADC_SCTL_TRGSEL_MASK GENMASK(21, 16)
+
+#define MA35D1_EADC_DAT_RESULT_MASK GENMASK(15, 0)
+#define MA35D1_EADC_DAT_RESULT_12BIT GENMASK(11, 0)
+#define MA35D1_EADC_DAT_OV BIT(16)
+#define MA35D1_EADC_DAT_VALID BIT(17)
+
+#define MA35D1_EADC_INTSRC0_SPLIEN(n) BIT(n)
+#define MA35D1_EADC_STATUS2_ADIF0 BIT(0)
+#define MA35D1_EADC_REFADJCTL_PDREF BIT(0)
+#define MA35D1_EADC_SELSMP0_SMPT0_MASK GENMASK(1, 0)
+#define MA35D1_EADC_SELSMP_LONG FIELD_PREP(MA35D1_EADC_SELSMP0_SMPT0_MASK, 3)
+
+#define MA35D1_EADC_MAX_EXT_CHANNELS 8
+#define MA35D1_EADC_INTERNAL_VREF_MV 1600
+#define MA35D1_EADC_TIMEOUT msecs_to_jiffies(1000)
+#define MA35D1_EADC_RESET_DELAY_US 10
+#define MA35D1_EADC_REF_STABLE_US 1000
+
+struct ma35d1_adc {
+ struct regmap *regmap;
+ struct clk *clk;
+ struct reset_control *rst;
+ struct regulator *vref;
+ struct completion completion;
+ /* Protects conversion state and register accesses from PM transitions. */
+ struct mutex lock;
+ const struct iio_chan_spec *scan_chan;
+ unsigned int vref_mv;
+ int irq;
+ bool suspended;
+};
+
+static const struct regmap_config ma35d1_adc_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = MA35D1_EADC_REFADJCTL,
+};
+
+static bool ma35d1_adc_valid_diff_pair(unsigned int vinp, unsigned int vinn)
+{
+ return (vinp == 0 && vinn == 4) ||
+ (vinp == 1 && vinn == 5) ||
+ (vinp == 2 && vinn == 6) ||
+ (vinp == 3 && vinn == 7);
+}
+
+static int ma35d1_adc_set_bits(struct ma35d1_adc *adc, unsigned int reg,
+ unsigned int bits)
+{
+ return regmap_set_bits(adc->regmap, reg, bits);
+}
+
+static int ma35d1_adc_clear_bits(struct ma35d1_adc *adc, unsigned int reg,
+ unsigned int bits)
+{
+ return regmap_clear_bits(adc->regmap, reg, bits);
+}
+
+static int ma35d1_adc_disable_irq(struct ma35d1_adc *adc)
+{
+ return ma35d1_adc_clear_bits(adc, MA35D1_EADC_CTL,
+ MA35D1_EADC_CTL_ADCIEN0);
+}
+
+static int ma35d1_adc_setup_reference(struct ma35d1_adc *adc)
+{
+ int ret;
+
+ if (adc->vref) {
+ ret = regmap_set_bits(adc->regmap, MA35D1_EADC_REFADJCTL,
+ MA35D1_EADC_REFADJCTL_PDREF);
+ if (ret)
+ return ret;
+ } else {
+ ret = regmap_clear_bits(adc->regmap, MA35D1_EADC_REFADJCTL,
+ MA35D1_EADC_REFADJCTL_PDREF);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(adc->regmap, MA35D1_EADC_CTL,
+ MA35D1_EADC_CTL_VREFSEL_MASK,
+ MA35D1_EADC_CTL_VREFSEL_1V6);
+ if (ret)
+ return ret;
+ }
+
+ fsleep(MA35D1_EADC_REF_STABLE_US);
+
+ return 0;
+}
+
+static int ma35d1_adc_hw_init(struct ma35d1_adc *adc)
+{
+ int ret;
+
+ ret = ma35d1_adc_disable_irq(adc);
+ if (ret)
+ return ret;
+
+ ret = ma35d1_adc_setup_reference(adc);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(adc->regmap, MA35D1_EADC_SELSMP0,
+ MA35D1_EADC_SELSMP0_SMPT0_MASK,
+ MA35D1_EADC_SELSMP_LONG);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(adc->regmap, MA35D1_EADC_STATUS2,
+ MA35D1_EADC_STATUS2_ADIF0);
+ if (ret)
+ return ret;
+
+ return ma35d1_adc_set_bits(adc, MA35D1_EADC_CTL, MA35D1_EADC_CTL_ADCEN);
+}
+
+static int ma35d1_adc_hw_disable(struct ma35d1_adc *adc)
+{
+ int ret;
+
+ ret = ma35d1_adc_disable_irq(adc);
+ if (ret)
+ return ret;
+
+ return ma35d1_adc_clear_bits(adc, MA35D1_EADC_CTL, MA35D1_EADC_CTL_ADCEN);
+}
+
+static void ma35d1_adc_hw_disable_action(void *data)
+{
+ ma35d1_adc_hw_disable(data);
+}
+
+static void ma35d1_adc_vref_disable(void *data)
+{
+ regulator_disable(data);
+}
+
+static int ma35d1_adc_reset(struct ma35d1_adc *adc)
+{
+ int ret;
+
+ if (!adc->rst)
+ return 0;
+
+ ret = reset_control_assert(adc->rst);
+ if (ret)
+ return ret;
+
+ fsleep(MA35D1_EADC_RESET_DELAY_US);
+
+ return reset_control_deassert(adc->rst);
+}
+
+static int ma35d1_adc_config_channel(struct ma35d1_adc *adc,
+ const struct iio_chan_spec *chan)
+{
+ unsigned int ctl;
+ int ret;
+
+ ctl = chan->differential ? MA35D1_EADC_CTL_DIFFEN |
+ MA35D1_EADC_CTL_DMOF : 0;
+
+ ret = regmap_update_bits(adc->regmap, MA35D1_EADC_CTL,
+ MA35D1_EADC_CTL_DIFFEN |
+ MA35D1_EADC_CTL_DMOF, ctl);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(adc->regmap, MA35D1_EADC_SCTL(0),
+ MA35D1_EADC_SCTL_CHSEL_MASK |
+ MA35D1_EADC_SCTL_TRGSEL_MASK,
+ FIELD_PREP(MA35D1_EADC_SCTL_CHSEL_MASK,
+ chan->channel));
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(adc->regmap, MA35D1_EADC_INTSRC0,
+ MA35D1_EADC_INTSRC0_SPLIEN(0),
+ MA35D1_EADC_INTSRC0_SPLIEN(0));
+}
+
+static int ma35d1_adc_read_conversion(struct ma35d1_adc *adc,
+ const struct iio_chan_spec *chan, u16 *raw)
+{
+ unsigned int val;
+ long timeout;
+ int ret;
+
+ reinit_completion(&adc->completion);
+
+ ret = regmap_write(adc->regmap, MA35D1_EADC_STATUS2,
+ MA35D1_EADC_STATUS2_ADIF0);
+ if (ret)
+ return ret;
+
+ ret = ma35d1_adc_config_channel(adc, chan);
+ if (ret)
+ return ret;
+
+ ret = ma35d1_adc_set_bits(adc, MA35D1_EADC_CTL, MA35D1_EADC_CTL_ADCIEN0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(adc->regmap, MA35D1_EADC_SWTRG, BIT(0));
+ if (ret)
+ goto disable_irq;
+
+ timeout = wait_for_completion_timeout(&adc->completion,
+ MA35D1_EADC_TIMEOUT);
+
+ ret = ma35d1_adc_disable_irq(adc);
+ if (ret)
+ return ret;
+
+ if (!timeout)
+ return -ETIMEDOUT;
+
+ ret = regmap_read(adc->regmap, MA35D1_EADC_DAT(0), &val);
+ if (ret)
+ return ret;
+
+ if (!(val & MA35D1_EADC_DAT_VALID))
+ return -EIO;
+
+ if (val & MA35D1_EADC_DAT_OV)
+ return -EOVERFLOW;
+
+ *raw = FIELD_GET(MA35D1_EADC_DAT_RESULT_MASK, val);
+
+ return 0;
+
+disable_irq:
+ ma35d1_adc_disable_irq(adc);
+
+ return ret;
+}
+
+static irqreturn_t ma35d1_adc_isr(int irq, void *data)
+{
+ struct iio_dev *indio_dev = data;
+ struct ma35d1_adc *adc = iio_priv(indio_dev);
+ unsigned int status;
+ int ret;
+
+ ret = regmap_read(adc->regmap, MA35D1_EADC_STATUS2, &status);
+ if (ret || !(status & MA35D1_EADC_STATUS2_ADIF0))
+ return IRQ_NONE;
+
+ regmap_write(adc->regmap, MA35D1_EADC_STATUS2,
+ MA35D1_EADC_STATUS2_ADIF0);
+ complete(&adc->completion);
+
+ return IRQ_HANDLED;
+}
+
+static int ma35d1_adc_sign_extend(u16 raw)
+{
+ return sign_extend32(raw & MA35D1_EADC_DAT_RESULT_12BIT, 11);
+}
+
+static int ma35d1_adc_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long mask)
+{
+ struct ma35d1_adc *adc = iio_priv(indio_dev);
+ u16 raw;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW: {
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ guard(mutex)(&adc->lock);
+ if (adc->suspended)
+ return -EBUSY;
+
+ ret = ma35d1_adc_read_conversion(adc, chan, &raw);
+ if (ret)
+ return ret;
+
+ if (chan->differential)
+ *val = ma35d1_adc_sign_extend(raw);
+ else
+ *val = raw & MA35D1_EADC_DAT_RESULT_12BIT;
+
+ return IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ *val = adc->vref_mv;
+ *val2 = chan->differential ? 11 : 12;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ma35d1_adc_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *scan_mask)
+{
+ struct ma35d1_adc *adc = iio_priv(indio_dev);
+ const struct iio_chan_spec *scan_chan = NULL;
+ unsigned long bit;
+ unsigned int count;
+
+ count = 0;
+ for_each_set_bit(bit, scan_mask, indio_dev->masklength) {
+ scan_chan = &indio_dev->channels[bit];
+ count++;
+ }
+
+ if (count != 1 || scan_chan->type != IIO_VOLTAGE)
+ return -EINVAL;
+
+ adc->scan_chan = scan_chan;
+
+ return 0;
+}
+
+static int ma35d1_adc_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ma35d1_adc *adc = iio_priv(indio_dev);
+
+ guard(mutex)(&adc->lock);
+
+ if (adc->suspended)
+ return -EBUSY;
+
+ return adc->scan_chan ? 0 : -EINVAL;
+}
+
+static int ma35d1_adc_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ma35d1_adc *adc = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&adc->lock);
+
+ ret = ma35d1_adc_disable_irq(adc);
+ if (ret)
+ return ret;
+
+ return regmap_write(adc->regmap, MA35D1_EADC_STATUS2,
+ MA35D1_EADC_STATUS2_ADIF0);
+}
+
+static irqreturn_t ma35d1_adc_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ma35d1_adc *adc = iio_priv(indio_dev);
+ IIO_DECLARE_BUFFER_WITH_TS(u16, scan, 1) = { };
+ u16 raw;
+ int ret;
+
+ guard(mutex)(&adc->lock);
+ if (adc->suspended || !adc->scan_chan)
+ goto done;
+
+ ret = ma35d1_adc_read_conversion(adc, adc->scan_chan, &raw);
+ if (ret)
+ goto done;
+
+ scan[0] = raw & MA35D1_EADC_DAT_RESULT_12BIT;
+ iio_push_to_buffers_with_timestamp(indio_dev, scan, pf->timestamp);
+
+done:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static const struct iio_buffer_setup_ops ma35d1_adc_buffer_ops = {
+ .postenable = ma35d1_adc_buffer_postenable,
+ .predisable = ma35d1_adc_buffer_predisable,
+};
+
+static const struct iio_info ma35d1_adc_info = {
+ .read_raw = ma35d1_adc_read_raw,
+ .update_scan_mode = ma35d1_adc_update_scan_mode,
+};
+
+static void ma35d1_adc_init_channel(struct iio_chan_spec *chan, u32 vinp,
+ u32 vinn, unsigned int scan_index,
+ bool differential)
+{
+ chan->type = IIO_VOLTAGE;
+ chan->indexed = 1;
+ chan->channel = vinp;
+ chan->scan_index = scan_index;
+ chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE);
+ chan->scan_type.format = differential ? IIO_SCAN_FORMAT_SIGNED_INT :
+ IIO_SCAN_FORMAT_UNSIGNED_INT;
+ chan->scan_type.realbits = 12;
+ chan->scan_type.storagebits = 16;
+ chan->scan_type.endianness = IIO_CPU;
+
+ if (differential) {
+ chan->differential = 1;
+ chan->channel2 = vinn;
+ }
+}
+
+static struct iio_chan_spec
+ma35d1_adc_timestamp_channel(unsigned int scan_index)
+{
+ struct iio_chan_spec chan = IIO_CHAN_SOFT_TIMESTAMP(scan_index);
+
+ return chan;
+}
+
+static int ma35d1_adc_parse_channels(struct iio_dev *indio_dev,
+ struct device *dev)
+{
+ DECLARE_BITMAP(used_channels, MA35D1_EADC_MAX_EXT_CHANNELS);
+ struct iio_chan_spec *channels;
+ unsigned int scan_index;
+ int num_channels;
+ int ret;
+
+ bitmap_zero(used_channels, MA35D1_EADC_MAX_EXT_CHANNELS);
+
+ num_channels = device_get_child_node_count(dev);
+ if (!num_channels)
+ return dev_err_probe(dev, -ENODATA,
+ "no ADC channels configured\n");
+
+ if (num_channels > MA35D1_EADC_MAX_EXT_CHANNELS)
+ return dev_err_probe(dev, -EINVAL, "too many ADC channels\n");
+
+ channels = devm_kcalloc(dev, num_channels + 1, sizeof(*channels), GFP_KERNEL);
+ if (!channels)
+ return -ENOMEM;
+
+ scan_index = 0;
+ device_for_each_child_node_scoped(dev, child) {
+ u32 diff[2];
+ u32 reg;
+ u32 vinn;
+ bool differential;
+
+ ret = fwnode_property_read_u32(child, "reg", ®);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "missing channel reg property\n");
+
+ if (reg >= MA35D1_EADC_MAX_EXT_CHANNELS)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid ADC channel %u\n", reg);
+
+ if (test_and_set_bit(reg, used_channels))
+ return dev_err_probe(dev, -EINVAL,
+ "duplicate ADC channel %u\n", reg);
+
+ differential = false;
+ vinn = 0;
+ if (fwnode_property_present(child, "diff-channels")) {
+ ret = fwnode_property_read_u32_array(child, "diff-channels", diff,
+ ARRAY_SIZE(diff));
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "invalid diff-channels for channel %u\n",
+ reg);
+
+ if (diff[0] != reg ||
+ !ma35d1_adc_valid_diff_pair(diff[0], diff[1]))
+ return dev_err_probe(dev, -EINVAL,
+ "invalid differential ADC channel %u-%u\n",
+ diff[0], diff[1]);
+
+ if (test_and_set_bit(diff[1], used_channels))
+ return dev_err_probe(dev, -EINVAL,
+ "ADC channel %u already used\n",
+ diff[1]);
+
+ vinn = diff[1];
+ differential = true;
+ }
+
+ ma35d1_adc_init_channel(&channels[scan_index], reg, vinn,
+ scan_index, differential);
+ scan_index++;
+ }
+
+ channels[scan_index] = ma35d1_adc_timestamp_channel(scan_index);
+
+ indio_dev->channels = channels;
+ indio_dev->num_channels = scan_index + 1;
+ indio_dev->masklength = indio_dev->num_channels;
+
+ return 0;
+}
+
+static int ma35d1_adc_init_vref(struct ma35d1_adc *adc, struct device *dev)
+{
+ int ret;
+
+ adc->vref = devm_regulator_get_optional(dev, "vref");
+ if (IS_ERR(adc->vref)) {
+ if (PTR_ERR(adc->vref) != -ENODEV)
+ return dev_err_probe(dev, PTR_ERR(adc->vref),
+ "failed to get VREF supply\n");
+
+ adc->vref = NULL;
+ adc->vref_mv = MA35D1_EADC_INTERNAL_VREF_MV;
+
+ return 0;
+ }
+
+ ret = regulator_enable(adc->vref);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable VREF supply\n");
+
+ ret = devm_add_action_or_reset(dev, ma35d1_adc_vref_disable, adc->vref);
+ if (ret)
+ return ret;
+
+ ret = regulator_get_voltage(adc->vref);
+ if (ret <= 0)
+ return dev_err_probe(dev, ret ?: -EINVAL,
+ "failed to get VREF voltage\n");
+
+ adc->vref_mv = ret / 1000;
+
+ return 0;
+}
+
+static int ma35d1_adc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct iio_dev *indio_dev;
+ struct ma35d1_adc *adc;
+ void __iomem *regs;
+ int irq;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ adc = iio_priv(indio_dev);
+ init_completion(&adc->completion);
+
+ ret = devm_mutex_init(dev, &adc->lock);
+ if (ret)
+ return ret;
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return dev_err_probe(dev, PTR_ERR(regs),
+ "failed to map registers\n");
+
+ adc->regmap = devm_regmap_init_mmio(dev, regs, &ma35d1_adc_regmap_config);
+ if (IS_ERR(adc->regmap))
+ return dev_err_probe(dev, PTR_ERR(adc->regmap),
+ "failed to initialize regmap\n");
+
+ adc->clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(adc->clk))
+ return dev_err_probe(dev, PTR_ERR(adc->clk),
+ "failed to get and enable ADC clock\n");
+
+ adc->rst = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(adc->rst))
+ return dev_err_probe(dev, PTR_ERR(adc->rst),
+ "failed to get reset control\n");
+
+ ret = ma35d1_adc_reset(adc);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to reset ADC\n");
+
+ ret = ma35d1_adc_init_vref(adc, dev);
+ if (ret)
+ return ret;
+
+ indio_dev->name = "ma35d1-eadc";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &ma35d1_adc_info;
+
+ ret = ma35d1_adc_parse_channels(indio_dev, dev);
+ if (ret)
+ return ret;
+
+ ret = ma35d1_adc_hw_init(adc);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to initialize ADC\n");
+
+ ret = devm_add_action_or_reset(dev, ma35d1_adc_hw_disable_action, adc);
+ if (ret)
+ return ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ adc->irq = irq;
+ ret = devm_request_irq(dev, irq, ma35d1_adc_isr, 0, dev_name(dev),
+ indio_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ ma35d1_adc_trigger_handler,
+ &ma35d1_adc_buffer_ops);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static int ma35d1_adc_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct ma35d1_adc *adc = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&adc->lock);
+
+ ret = ma35d1_adc_disable_irq(adc);
+ if (ret)
+ return ret;
+
+ synchronize_irq(adc->irq);
+
+ ret = ma35d1_adc_hw_disable(adc);
+ if (ret)
+ return ret;
+
+ clk_disable_unprepare(adc->clk);
+ adc->suspended = true;
+
+ return 0;
+}
+
+static int ma35d1_adc_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct ma35d1_adc *adc = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&adc->lock);
+
+ ret = clk_prepare_enable(adc->clk);
+ if (ret)
+ return ret;
+
+ ret = ma35d1_adc_hw_init(adc);
+ if (ret) {
+ clk_disable_unprepare(adc->clk);
+ return ret;
+ }
+
+ adc->suspended = false;
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(ma35d1_adc_pm_ops,
+ ma35d1_adc_suspend, ma35d1_adc_resume);
+
+static const struct of_device_id ma35d1_adc_of_match[] = {
+ { .compatible = "nuvoton,ma35d1-eadc" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ma35d1_adc_of_match);
+
+static struct platform_driver ma35d1_adc_driver = {
+ .probe = ma35d1_adc_probe,
+ .driver = {
+ .name = "ma35d1-eadc",
+ .of_match_table = ma35d1_adc_of_match,
+ .pm = pm_sleep_ptr(&ma35d1_adc_pm_ops),
+ },
+};
+module_platform_driver(ma35d1_adc_driver);
+
+MODULE_AUTHOR("Chi-Wen Weng <cwweng@nuvoton.com>");
+MODULE_DESCRIPTION("Nuvoton MA35D1 EADC driver");
+MODULE_LICENSE("GPL");
--
2.25.1
next prev parent reply other threads:[~2026-07-13 8:12 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-13 8:11 [PATCH v2 0/2] iio: adc: Add Nuvoton MA35D1 EADC support Chi-Wen Weng
2026-07-13 8:11 ` [PATCH v2 1/2] dt-bindings: iio: adc: Add Nuvoton MA35D1 EADC Chi-Wen Weng
2026-07-13 9:35 ` Rob Herring (Arm)
2026-07-13 8:11 ` Chi-Wen Weng [this message]
2026-07-13 8:30 ` [PATCH v2 2/2] iio: adc: Add Nuvoton MA35D1 EADC driver sashiko-bot
2026-07-13 11:17 ` Andy Shevchenko
2026-07-13 12:06 ` Chi-Wen Weng
2026-07-14 12:25 ` Uwe Kleine-König
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260713081127.115197-3-cwweng.linux@gmail.com \
--to=cwweng.linux@gmail.com \
--cc=andy@kernel.org \
--cc=conor+dt@kernel.org \
--cc=cwweng@nuvoton.com \
--cc=devicetree@vger.kernel.org \
--cc=dlechner@baylibre.com \
--cc=jic23@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-iio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=nuno.sa@analog.com \
--cc=robh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.