From: Andy Shevchenko <andriy.shevchenko@intel.com>
To: Chi-Wen Weng <cwweng.linux@gmail.com>
Cc: jic23@kernel.org, robh@kernel.org, krzk+dt@kernel.org,
conor+dt@kernel.org, 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
Subject: Re: [PATCH v2 2/2] iio: adc: Add Nuvoton MA35D1 EADC driver
Date: Mon, 13 Jul 2026 14:17:16 +0300 [thread overview]
Message-ID: <alTJPExu9b3lRtx_@ashevche-desk.local> (raw)
In-Reply-To: <20260713081127.115197-3-cwweng.linux@gmail.com>
On Mon, Jul 13, 2026 at 04:11:27PM +0800, Chi-Wen Weng wrote:
> 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.
...
> +#include <linux/bitfield.h>
> +#include <linux/bitmap.h>
bitmap.h implies bitops.h...
> +#include <linux/bitops.h>
...so you may drop this one.
> +#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>
Uwe made a series to get rid of this header. The platform_device.h seems
the one which will provide the thing.
> +#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>
...
> +#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
Please, make all register offsets fixed width, exempli gratia
define MA35D1_EADC_STATUS2 0x0f8
...
> +#define MA35D1_EADC_INTSRC0_SPLIEN(n) BIT(n)
Useless? Can't BIT() be used directly? (Note, I don't know
if it's good or bad suggestion, wanting to understand a bit more.)
...
> +#define MA35D1_EADC_MAX_EXT_CHANNELS 8
> +#define MA35D1_EADC_INTERNAL_VREF_MV 1600
_mV (yes, as per SI).
> +#define MA35D1_EADC_TIMEOUT msecs_to_jiffies(1000)
> +#define MA35D1_EADC_RESET_DELAY_US 10
> +#define MA35D1_EADC_REF_STABLE_US 1000
(1 * USEC_PER_MSEC) ?
If go this way, include time.h for the multiplier definition.
...
> +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;
_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,
No cache?
> +};
...
> +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);
Wondering if this is just
return (vinp >= 0 && vinp < 4 && (vinn == vinp + 4));
But I'm fine with the original, perhaps compiler may optimise that.
> +}
...
> +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);
> +}
What's the point in these wrappers?
...
> +static int ma35d1_adc_disable_irq(struct ma35d1_adc *adc)
> +{
> + return ma35d1_adc_clear_bits(adc, MA35D1_EADC_CTL,
> + MA35D1_EADC_CTL_ADCIEN0);
I would make it one line (*yes, 84 characters).
> +}
...
> +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;
> + }
Add a short comment with reference to a datasheet table/section/et cetera.
> + 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);
With
static regmap *map = adc->regmap;
this becomes
ret = regmap_write(map, MA35D1_EADC_STATUS2, MA35D1_EADC_STATUS2_ADIF0);
exactly one line. Apply this trick everywhere and it might help reduce
amount of LoC.
> + if (ret)
> + return ret;
> +
> + return ma35d1_adc_set_bits(adc, MA35D1_EADC_CTL, MA35D1_EADC_CTL_ADCEN);
> +}
...
> +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;
regmap_assign_bits()
> + 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_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;
This is interesting check. First of all, it checks if the only a single bit is
set in the whole mask, then it checks the type of the *last* listed channel.
The latter seems fragile to me, as it depends on ordering (yes, the limitation
to a single enabled channel helps).
With that being said, the both checks can be unrolled to the find_next_bit().
/* Find the first enabled channel to scan */
bit = find_next_bit(scan_mask, indio_dev->masklength, 0);
if (bit >= indio_dev->masklength)
return -EINVAL;
/* Check that the type of the channel is what we are looking for */
scan_chan = &indio_dev->channels[bit];
if (scan_chan->type != IIO_VOLTAGE)
return -EINVAL;
/* Check that this is the only enabled channel */
bit = find_next_bit(scan_mask, indio_dev->masklength, bit);
if (bit < indio_dev->masklength)
return -EINVAL;
Not sure that this will generate less code, though. So up to you.
One also might argue that the original code is easier to understand.
> + adc->scan_chan = scan_chan;
> +
> + return 0;
> +}
...
> +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;
This is usually leads to a mess in the compiler. This is written in the top of
cleanup.h that goto should be avoided while using guard()() or similar macros.
> + 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 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);
Broken indentation, better to have
chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE);
OR
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;
> +}
Useless wrapper. Just put _SOFT_TIMESTAMP() in place.
...
> +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");
I would return -ENOENT, as it's usual error code for 0 count in counting APIs
in the kernel. Also you can put it on a single line.
> + 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);
size_add() ?
> + 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;
Make it an 'else' branch.
> + 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;
> + }
It can be unnested:
if (PTR_ERR(adc->vref) == -ENODEV) {
adc->vref = NULL;
adc->vref_mV = MA35D1_EADC_INTERNAL_VREF_mV;
return 0;
}
if (IS_ERR(adc->vref))
return dev_err_probe(dev, PTR_ERR(adc->vref),
"failed to get VREF supply\n");
> + 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;
(MICRO / MILLI)
instead of plain 1000.
> + return 0;
> +}
--
With Best Regards,
Andy Shevchenko
next prev parent reply other threads:[~2026-07-13 11:17 UTC|newest]
Thread overview: 6+ 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 ` [PATCH v2 2/2] iio: adc: Add Nuvoton MA35D1 EADC driver Chi-Wen Weng
2026-07-13 11:17 ` Andy Shevchenko [this message]
2026-07-13 12:06 ` Chi-Wen Weng
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=alTJPExu9b3lRtx_@ashevche-desk.local \
--to=andriy.shevchenko@intel.com \
--cc=andy@kernel.org \
--cc=conor+dt@kernel.org \
--cc=cwweng.linux@gmail.com \
--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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox