lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <8e2c73ca-3746-4b2a-9d85-c12b51a69059@gmail.com>
Date: Mon, 15 Dec 2025 21:43:33 +0530
From: Ajith Anandhan <ajithanandhan0406@...il.com>
To: David Lechner <dlechner@...libre.com>, jic23@...nel.org
Cc: nuno.sa@...log.com, andy@...nel.org, robh@...nel.org, krzk+dt@...nel.org,
 conor+dt@...nel.org, linux-iio@...r.kernel.org, devicetree@...r.kernel.org,
 linux-kernel@...r.kernel.org
Subject: Re: [PATCH v2 2/2] iio: adc: Add support for TI ADS1120

On 11/18/25 7:34 PM, David Lechner wrote:
> On 11/9/25 8:11 AM, Ajith Anandhan wrote:
>> Add driver for the Texas Instruments ADS1120, a precision 16-bit
>> analog-to-digital converter with an SPI interface.
>>
> ...
>
>> +#define ADS1120_CFG0_GAIN_MASK		GENMASK(3, 1)
>> +#define ADS1120_CFG0_GAIN_1		0
>> +#define ADS1120_CFG0_GAIN_2		1
>> +#define ADS1120_CFG0_GAIN_4		2
>> +#define ADS1120_CFG0_GAIN_8		3
>> +#define ADS1120_CFG0_GAIN_16		4
>> +#define ADS1120_CFG0_GAIN_32		5
>> +#define ADS1120_CFG0_GAIN_64		6
>> +#define ADS1120_CFG0_GAIN_128		7
> We could avoid these macros by just doing ilog2(gain).


I understand your concern about unused macros. I've kept them for 
documentation purposes as they map directly to the datasheet register 
definitions, which makes it easier to verify correctness against 
hardware specs also I'd prefer to keep it like this since it give more 
readability Shall i keep this as it is for this initial version?

>> +
>> +#define ADS1120_CFG0_PGA_BYPASS		BIT(0)
>> +
>> +/* Config Register 1 bit definitions */
>> +#define ADS1120_CFG1_DR_MASK		GENMASK(7, 5)
>> +#define ADS1120_CFG1_DR_20SPS		0
>> +#define ADS1120_CFG1_DR_45SPS		1
>> +#define ADS1120_CFG1_DR_90SPS		2
>> +#define ADS1120_CFG1_DR_175SPS		3
>> +#define ADS1120_CFG1_DR_330SPS		4
>> +#define ADS1120_CFG1_DR_600SPS		5
>> +#define ADS1120_CFG1_DR_1000SPS		6
> I would avoid writing macros for values we don't use yet. For example,
> it may make more sense to have a lookup table when it comes to actually
> implementing something that uses this.
>
> Same applies to the rest below.
>
>> +
>> +#define ADS1120_CFG1_MODE_MASK		GENMASK(4, 3)
>> +#define ADS1120_CFG1_MODE_NORMAL	0
>> +#define ADS1120_CFG1_MODE_DUTY		1
>> +#define ADS1120_CFG1_MODE_TURBO		2
>> +
>> +#define ADS1120_CFG1_CM_MASK		BIT(2)
>> +#define ADS1120_CFG1_CM_SINGLE		0
>> +#define ADS1120_CFG1_CM_CONTINUOUS	1
>> +
>> +#define ADS1120_CFG1_TS_EN		BIT(1)
>> +#define ADS1120_CFG1_BCS_EN		BIT(0)
>> +
>> +/* Config Register 2 bit definitions */
>> +#define ADS1120_CFG2_VREF_MASK		GENMASK(7, 6)
>> +#define ADS1120_CFG2_VREF_INTERNAL	0
>> +#define ADS1120_CFG2_VREF_EXT_REFP0	1
>> +#define ADS1120_CFG2_VREF_EXT_AIN0	2
>> +#define ADS1120_CFG2_VREF_AVDD		3
>> +
>> +#define ADS1120_CFG2_REJECT_MASK	GENMASK(5, 4)
>> +#define ADS1120_CFG2_REJECT_OFF		0
>> +#define ADS1120_CFG2_REJECT_50_60	1
>> +#define ADS1120_CFG2_REJECT_50		2
>> +#define ADS1120_CFG2_REJECT_60		3
>> +
>> +#define ADS1120_CFG2_PSW_EN		BIT(3)
>> +
>> +#define ADS1120_CFG2_IDAC_MASK		GENMASK(2, 0)
>> +#define ADS1120_CFG2_IDAC_OFF		0
>> +#define ADS1120_CFG2_IDAC_10UA		1
>> +#define ADS1120_CFG2_IDAC_50UA		2
>> +#define ADS1120_CFG2_IDAC_100UA		3
>> +#define ADS1120_CFG2_IDAC_250UA		4
>> +#define ADS1120_CFG2_IDAC_500UA		5
>> +#define ADS1120_CFG2_IDAC_1000UA	6
>> +#define ADS1120_CFG2_IDAC_1500UA	7
>> +
>> +/* Config Register 3 bit definitions */
>> +#define ADS1120_CFG3_IDAC1_MASK		GENMASK(7, 5)
>> +#define ADS1120_CFG3_IDAC1_DISABLED	0
>> +#define ADS1120_CFG3_IDAC1_AIN0		1
>> +#define ADS1120_CFG3_IDAC1_AIN1		2
>> +#define ADS1120_CFG3_IDAC1_AIN2		3
>> +#define ADS1120_CFG3_IDAC1_AIN3		4
>> +#define ADS1120_CFG3_IDAC1_REFP0	5
>> +#define ADS1120_CFG3_IDAC1_REFN0	6
>> +
>> +#define ADS1120_CFG3_IDAC2_MASK		GENMASK(4, 2)
>> +#define ADS1120_CFG3_IDAC2_DISABLED	0
>> +#define ADS1120_CFG3_IDAC2_AIN0		1
>> +#define ADS1120_CFG3_IDAC2_AIN1		2
>> +#define ADS1120_CFG3_IDAC2_AIN2		3
>> +#define ADS1120_CFG3_IDAC2_AIN3		4
>> +#define ADS1120_CFG3_IDAC2_REFP0	5
>> +#define ADS1120_CFG3_IDAC2_REFN0	6
>> +
>> +#define ADS1120_CFG3_DRDYM_MASK		BIT(1)
>> +#define ADS1120_CFG3_DRDYM_DRDY_ONLY	0
>> +#define ADS1120_CFG3_DRDYM_BOTH		1
>> +
>> +/* Conversion time for 20 SPS */
>> +#define ADS1120_CONV_TIME_MS		51
>> +
>> +/* Internal reference voltage in millivolts */
>> +#define ADS1120_VREF_INTERNAL_MV	2048
>> +
>> +
>> +struct ads1120_state {
>> +	struct spi_device	*spi;
>> +	struct regmap		*regmap;
>> +	/*
>> +	 * Protects chip configuration and ADC reads to ensure
>> +	 * consistent channel/gain settings during conversions.
>> +	 */
>> +	struct mutex		lock;
>> +
>> +	int vref_mv;
>> +
>> +	/* DMA-safe buffer for SPI transfers */
>> +	u8 data[4] __aligned(IIO_DMA_MINALIGN);
>> +};
>> +
>> +
>> +static const int ads1120_gain_values[] = { 1, 2, 4, 8, 16, 32, 64, 128 };
>> +static const int ads1120_low_gain_values[] = { 1, 2, 4 };
>> +
>> +/* Differential channel macro */
>> +#define ADS1120_DIFF_CHANNEL(index, chan1, chan2)		\
>> +{								\
>> +	.type = IIO_VOLTAGE,					\
>> +	.indexed = 1,						\
>> +	.channel = chan1,					\
>> +	.channel2 = chan2,					\
>> +	.differential = 1,					\
>> +	.address = index,					\
>> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |		\
>> +			      BIT(IIO_CHAN_INFO_SCALE),		\
>> +	.info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), \
>> +}
>> +
>> +/* Single-ended channel macro */
>> +#define ADS1120_SINGLE_CHANNEL(index, chan)			\
>> +{								\
>> +	.type = IIO_VOLTAGE,					\
>> +	.indexed = 1,						\
>> +	.channel = chan,					\
>> +	.address = index,					\
>> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |		\
>> +			      BIT(IIO_CHAN_INFO_SCALE),		\
>> +	.info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), \
>> +}
>> +
>> +/* Diagnostic channel macro */
>> +#define ADS1120_DIAG_CHANNEL(index, label)			\
>> +{								\
>> +	.type = IIO_VOLTAGE,					\
>> +	.indexed = 1,						\
>> +	.channel = index,					\
>> +	.address = index,					\
>> +	.extend_name = label,					\
>> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |		\
>> +			      BIT(IIO_CHAN_INFO_SCALE),		\
>> +	.info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), \
>> +}
>> +
>> +static const struct iio_chan_spec ads1120_channels[] = {
>> +	/* Differential inputs */
>> +	ADS1120_DIFF_CHANNEL(ADS1120_CFG0_MUX_AIN0_AIN1, 0, 1),
>> +	ADS1120_DIFF_CHANNEL(ADS1120_CFG0_MUX_AIN0_AIN2, 0, 2),
>> +	ADS1120_DIFF_CHANNEL(ADS1120_CFG0_MUX_AIN0_AIN3, 0, 3),
>> +	ADS1120_DIFF_CHANNEL(ADS1120_CFG0_MUX_AIN1_AIN2, 1, 2),
>> +	ADS1120_DIFF_CHANNEL(ADS1120_CFG0_MUX_AIN1_AIN3, 1, 3),
>> +	ADS1120_DIFF_CHANNEL(ADS1120_CFG0_MUX_AIN2_AIN3, 2, 3),
>> +	ADS1120_DIFF_CHANNEL(ADS1120_CFG0_MUX_AIN1_AIN0, 1, 0),
>> +	ADS1120_DIFF_CHANNEL(ADS1120_CFG0_MUX_AIN3_AIN2, 3, 2),
>> +	/* Single-ended inputs */
>> +	ADS1120_SINGLE_CHANNEL(ADS1120_CFG0_MUX_AIN0_AVSS, 0),
>> +	ADS1120_SINGLE_CHANNEL(ADS1120_CFG0_MUX_AIN1_AVSS, 1),
>> +	ADS1120_SINGLE_CHANNEL(ADS1120_CFG0_MUX_AIN2_AVSS, 2),
>> +	ADS1120_SINGLE_CHANNEL(ADS1120_CFG0_MUX_AIN3_AVSS, 3),
>> +	/* Diagnostic inputs */
>> +	ADS1120_DIAG_CHANNEL(ADS1120_CFG0_MUX_REFP_REFN_4, "ref_div4"),
>> +	ADS1120_DIAG_CHANNEL(ADS1120_CFG0_MUX_AVDD_AVSS_4, "avdd_div4"),
>> +	ADS1120_DIAG_CHANNEL(ADS1120_CFG0_MUX_SHORTED, "shorted"),
>> +};
> Usually we don't make macros for the index. When it comes to adding
> buffer support, having the macros makes it harder to see which scan_index
> belongs to which channel. And if buffer support is planned for later,
> it would make sense to use .scan_index instead of .address to avoid
> duplicating that info later.
>
>
>> +static int ads1120_set_gain(struct ads1120_state *st, int gain_val)
>> +{
>> +	int i;
>> +
>> +	for (i = 0; i < ARRAY_SIZE(ads1120_gain_values); i++) {
>> +		if (ads1120_gain_values[i] == gain_val)
>> +			break;
>> +	}
>> +
>> +	if (i == ARRAY_SIZE(ads1120_gain_values))
>> +		return -EINVAL;
> Since there is only one gain register, we should store this in a
> per-channel variable, then set the gain in the register in
> ads1120_set_mux() instead (and it would make sense to rename
> that function as well to something like ads1120_configure_channel()).


I've implemented a global gain that applies to all channels 
forsimplicity. I planed to add

  per-channel gain storage in the later patches.

>
>> +
>> +	return regmap_update_bits(st->regmap, ADS1120_REG_CONFIG0,
>> +				  ADS1120_CFG0_GAIN_MASK,
>> +				  FIELD_PREP(ADS1120_CFG0_GAIN_MASK, i));
>> +}
>> +
> ...
>
>> +static int ads1120_read_raw(struct iio_dev *indio_dev,
>> +			    struct iio_chan_spec const *chan,
>> +			    int *val, int *val2, long mask)
>> +{
>> +	struct ads1120_state *st = iio_priv(indio_dev);
>> +	int ret, gain;
>> +
>> +	switch (mask) {
>> +	case IIO_CHAN_INFO_RAW:
>> +		guard(mutex)(&st->lock);
>> +		ret = ads1120_read_measurement(st, chan, val);
>> +		if (ret)
>> +			return ret;
>> +		return IIO_VAL_INT;
>> +
>> +	case IIO_CHAN_INFO_SCALE:
>> +		/*
>> +		 * Scale = Vref / (gain * 2^15)
>> +		 * Return in format: val / 2^val2
>> +		 */
>> +		gain = ads1120_get_gain(st);
>> +		if (gain < 0)
>> +			return gain;
>> +
>> +		*val = st->vref_mv;
>> +		*val2 = gain * 15;
> I think this would need to be `*val2 = ilog2(gain) + 15`.
>
>> +		return IIO_VAL_FRACTIONAL_LOG2;
>> +
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +}
>> +
>
>
>> +
>> +/* Regmap read function for ADS1120 */
>> +static int ads1120_regmap_read(void *context, const void *reg_buf,
>> +			       size_t reg_size, void *val_buf, size_t val_size)
>> +{
>> +	struct ads1120_state *st = context;
>> +	u8 reg = *(u8 *)reg_buf;
>> +	u8 *val = val_buf;
>> +	int ret;
>> +	struct spi_transfer xfer[2] = {
>> +		{
>> +			.tx_buf = st->data,
>> +			.len = 1,
>> +		}, {
>> +			.rx_buf = val,
>> +			.len = val_size,
>> +		}
>> +	};
>> +
>> +	if (reg > ADS1120_REG_CONFIG3)
>> +		return -EINVAL;
> This check should not be needed because of .maxregister.
>
>> +
>> +	/* RREG command: 0010rr00 where rr is register address */
>> +	st->data[0] = ADS1120_CMD_RREG | (reg << 2);
>> +
>> +	ret = spi_sync_transfer(st->spi, xfer, ARRAY_SIZE(xfer));
>> +	if (ret)
>> +		return ret;
>> +
>> +	return 0;
>> +}
>> +
>> +/* Regmap write function for ADS1120 */
>> +static int ads1120_regmap_write(void *context, const void *data, size_t count)
>> +{
>> +	struct ads1120_state *st = context;
>> +	const u8 *buf = data;
>> +
>> +	if (count != 2)
>> +		return -EINVAL;
>> +
>> +	/* WREG command: 0100rr00 where rr is register address */
>> +	st->data[0] = ADS1120_CMD_WREG | (buf[0] << 2);
>> +	st->data[1] = buf[1];
>> +
>> +	return spi_write(st->spi, st->data, 2);
>> +}
> I don't see anyting unusal about these read/write functions. We should
> be able to use the existing spi_regmap with the proper configuration
> instead of making a custom regmap_bus.


    The ADS1120 needs register address shifted by 2 bits
    in command byte (reg << 2). I couldn't find a way to do this with 
standard
    SPI regmap. If there's a configuration I'm missing, please point me 
to it and I'll gladly simplify.


>> +
>> +static const struct regmap_bus ads1120_regmap_bus = {
>> +	.read = ads1120_regmap_read,
>> +	.write = ads1120_regmap_write,
>> +	.reg_format_endian_default = REGMAP_ENDIAN_BIG,
>> +	.val_format_endian_default = REGMAP_ENDIAN_BIG,
>> +};
>> +
>> +static const struct regmap_config ads1120_regmap_config = {
>> +	.reg_bits = 8,
>> +	.val_bits = 8,
>> +	.max_register = ADS1120_REG_CONFIG3,
>> +	.cache_type = REGCACHE_FLAT,
>> +};
>> +
>> +static int ads1120_init(struct ads1120_state *st)
>> +{
>> +	int ret;
> It's just a few extra lines to turn on the avdd supply here before
> resettting.
>
>> +
>> +	ret = ads1120_reset(st);
>> +	if (ret)
>> +		return dev_err_probe(&st->spi->dev, ret,
>> +					"Failed to reset device\n");
>> +
>> +	/*
>> +	 * Configure Register 0:
>> +	 * - Input MUX: AIN0/AVSS
>> +	 * - Gain: 1
>> +	 * - PGA bypass enabled. When gain is set > 4, this bit is
>> +	 *   automatically ignored by the hardware and PGA is enabled,
>> +	 *   so it's safe to leave it set.
>> +	 */
>> +	ret = regmap_write(st->regmap, ADS1120_REG_CONFIG0,
>> +			   FIELD_PREP(ADS1120_CFG0_MUX_MASK,
>> +				      ADS1120_CFG0_MUX_AIN0_AVSS) |
>> +			   FIELD_PREP(ADS1120_CFG0_GAIN_MASK,
>> +				      ADS1120_CFG0_GAIN_1) |
>> +			   ADS1120_CFG0_PGA_BYPASS);
>> +	if (ret)
>> +		return ret;
>> +
>> +	/*
>> +	 * Configure Register 1:
>> +	 * - Data rate: 20 SPS (for single-shot mode)
>> +	 * - Operating mode: Normal
>> +	 * - Conversion mode: Single-shot
>> +	 * - Temperature sensor: Disabled
>> +	 * - Burnout current: Disabled
>> +	 */
>> +	ret = regmap_write(st->regmap, ADS1120_REG_CONFIG1,
>> +			   FIELD_PREP(ADS1120_CFG1_DR_MASK,
>> +				      ADS1120_CFG1_DR_20SPS) |
>> +			   FIELD_PREP(ADS1120_CFG1_MODE_MASK,
>> +				      ADS1120_CFG1_MODE_NORMAL) |
>> +			   FIELD_PREP(ADS1120_CFG1_CM_MASK,
>> +				      ADS1120_CFG1_CM_SINGLE) |
>> +			   FIELD_PREP(ADS1120_CFG1_TS_EN, 0) |
>> +			   FIELD_PREP(ADS1120_CFG1_BCS_EN, 0));
>> +	if (ret)
>> +		return ret;
>> +
>> +	/*
>> +	 * Configure Register 2:
>> +	 * - Voltage reference: Internal 2.048V
>> +	 * - 50/60Hz rejection: Off
>> +	 * - Power switch: Disabled
>> +	 * - IDAC current: Off
>> +	 */
>> +	ret = regmap_write(st->regmap, ADS1120_REG_CONFIG2,
>> +			   FIELD_PREP(ADS1120_CFG2_VREF_MASK,
>> +				      ADS1120_CFG2_VREF_INTERNAL) |
>> +			   FIELD_PREP(ADS1120_CFG2_REJECT_MASK,
>> +				      ADS1120_CFG2_REJECT_OFF) |
>> +			   FIELD_PREP(ADS1120_CFG2_PSW_EN, 0) |
>> +			   FIELD_PREP(ADS1120_CFG2_IDAC_MASK,
>> +				      ADS1120_CFG2_IDAC_OFF));
>> +	if (ret)
>> +		return ret;
>> +
>> +	/*
>> +	 * Configure Register 3:
>> +	 * - IDAC1: Disabled
>> +	 * - IDAC2: Disabled
>> +	 * - DRDY mode: Only reflects data ready status
>> +	 */
>> +	ret = regmap_write(st->regmap, ADS1120_REG_CONFIG3,
>> +			   FIELD_PREP(ADS1120_CFG3_IDAC1_MASK,
>> +				      ADS1120_CFG3_IDAC1_DISABLED) |
>> +			   FIELD_PREP(ADS1120_CFG3_IDAC2_MASK,
>> +				      ADS1120_CFG3_IDAC2_DISABLED) |
>> +			   FIELD_PREP(ADS1120_CFG3_DRDYM_MASK,
>> +				      ADS1120_CFG3_DRDYM_DRDY_ONLY));
>> +	if (ret)
>> +		return ret;
>> +
> If we want to wait for a later patch to support external reference,
> I would consider to check for the devicetree properties here and
> print a warning that they aren't supported if present. Maybe others
> think that is too much noise though? Especially if you plan to add
> it soon.
>
>> +	st->vref_mv = ADS1120_VREF_INTERNAL_MV;
>> +
>> +	return 0;
>> +}
>> +



Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ