public inbox for devicetree@vger.kernel.org
 help / color / mirror / Atom feed
From: "Nuno Sá" <noname.nuno@gmail.com>
To: Liviu Stan <liviu.stan@analog.com>
Cc: "Lars-Peter Clausen" <lars@metafoo.de>,
	"Michael Hennerich" <Michael.Hennerich@analog.com>,
	"Nuno Sá" <nuno.sa@analog.com>,
	"Jonathan Cameron" <jic23@kernel.org>,
	"David Lechner" <dlechner@baylibre.com>,
	"Andy Shevchenko" <andy@kernel.org>,
	"Rob Herring" <robh@kernel.org>,
	"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
	"Conor Dooley" <conor+dt@kernel.org>,
	linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH 2/2] iio: temperature: ltc2983: Add support for ADT7604
Date: Tue, 28 Apr 2026 12:14:05 +0100	[thread overview]
Message-ID: <afCVtXBHIIoLlsRo@nsa> (raw)
In-Reply-To: <20260427132526.272716-3-liviu.stan@analog.com>

On Mon, Apr 27, 2026 at 04:25:08PM +0300, Liviu Stan wrote:
> The ADT7604 shares the same die as the LTC2984. It repurposes the
> custom RTD sensor type (18) as a copper trace resistance sensor
> and the custom thermistor type (27) as a leak detector, and
> removes thermocouple, diode and direct ADC sensor types.
> 
> Custom RTD (type 18) becomes the copper trace sensor. Sensor
> configuration bits 21:18 are hardcoded to 0b1001 per the
> datasheet. Two variants are supported via the new
> adi,copper-trace-sub-ohm DT property: sub-ohm traces (< 1 ohm)
> have bits 17:0 cleared with no excitation current or custom
> table; standard traces (> 1 ohm) accept an optional
> resistance-to-temperature table.
> 
> Custom thermistor (type 27) becomes the leak detector. Sensor
> configuration bits are hardcoded to 0b001. The custom table
> uses a resolution of 16 (20+4 bit resistance field) instead of
> 64, and is specified via the new adi,custom-leak-detector DT
> property.
> 
> Both sensor types expose an IIO_RESISTANCE channel reading from
> the resistance result register bank (0x060-0x00AF), added to
> the regmap readable ranges. Scales are 1/1,024,000 for copper
> trace (result in mOhm) and 1/1024 for leak detector (result
> in Ohm).

But for userspace we report both in Ohm? That's the ABI AFAICT. In DT,
you also mention IIO_TEMP is used:

"IIO_TEMP reports coverage percentage"

Can you expand more on what the above means? Are we reporting milli
degrees celcius to userspace?

I could not find the datasheet so I guess it's not yet public?

> 
> A has_copper_trace capability flag is introduced in
> ltc2983_chip_info to identify the ADT7604, following the
> existing has_temp and has_eeprom pattern.
> 
> Tested on EVAL-ADT7604-AZ connected to Raspberry Pi 5 via SPI.
> 
> Signed-off-by: Liviu Stan <liviu.stan@analog.com>
> ---
>  drivers/iio/temperature/ltc2983.c | 347 +++++++++++++++++++++---------
>  1 file changed, 251 insertions(+), 96 deletions(-)
> 
> diff --git a/drivers/iio/temperature/ltc2983.c b/drivers/iio/temperature/ltc2983.c
> index 38e6f8dfd3b8..1966f6fb0305 100644
> --- a/drivers/iio/temperature/ltc2983.c
> +++ b/drivers/iio/temperature/ltc2983.c
> @@ -28,6 +28,8 @@
>  #define LTC2983_STATUS_REG			0x0000
>  #define LTC2983_TEMP_RES_START_REG		0x0010
>  #define LTC2983_TEMP_RES_END_REG		0x005F
> +#define ADT7604_RES_RES_START_REG		0x0060
> +#define ADT7604_RES_RES_END_REG			0x00AF
>  #define LTC2983_EEPROM_KEY_REG			0x00B0
>  #define LTC2983_EEPROM_READ_STATUS_REG		0x00D0
>  #define LTC2983_GLOBAL_CONFIG_REG		0x00F0
> @@ -58,8 +60,8 @@
>  
>  #define LTC2983_CHAN_START_ADDR(chan) \
>  			(((chan - 1) * 4) + LTC2983_CHAN_ASSIGN_START_REG)
> -#define LTC2983_CHAN_RES_ADDR(chan) \
> -			(((chan - 1) * 4) + LTC2983_TEMP_RES_START_REG)
> +#define LTC2983_CHAN_RES_ADDR(chan, base) \
> +			((((chan) - 1) * 4) + (base))
>  #define LTC2983_THERMOCOUPLE_DIFF_MASK		BIT(3)
>  #define LTC2983_THERMOCOUPLE_SGL(x) \
>  				FIELD_PREP(LTC2983_THERMOCOUPLE_DIFF_MASK, x)
> @@ -214,6 +216,7 @@ struct ltc2983_chip_info {
>  	unsigned int max_channels_nr;
>  	bool has_temp;
>  	bool has_eeprom;
> +	bool has_copper_trace;
>  };
>  
>  struct ltc2983_data {
> @@ -272,6 +275,7 @@ struct ltc2983_rtd {
>  	u32 r_sense_chan;
>  	u32 excitation_current;
>  	u32 rtd_curve;
> +	bool sub_ohm;
>  };
>  
>  struct ltc2983_thermistor {
> @@ -575,6 +579,10 @@ static int ltc2983_rtd_assign_chan(struct ltc2983_data *st,
>  		if (ret)
>  			return ret;
>  	}
> +
> +	if (rtd->sub_ohm)
> +		chan_val &= ~GENMASK(17, 0);
> +
>  	return __ltc2983_chan_assign_common(st, sensor, chan_val);
>  }

I'm not sure if we shouldn't just treat the new types as new sensors
instead of trying to push them in the existing one. I agree with Andy,
the patch does not look great with respect to if() else() and going to
deep in indentation.

>  
> @@ -758,83 +766,113 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st,
>  		return dev_err_ptr_probe(dev, ret,
>  					 "Property reg must be given\n");
>  
> -	ret = fwnode_property_read_u32(child, "adi,number-of-wires", &n_wires);
> -	if (!ret) {
> -		switch (n_wires) {
> -		case 2:
> -			rtd->sensor_config = LTC2983_RTD_N_WIRES(0);
> -			break;
> -		case 3:
> -			rtd->sensor_config = LTC2983_RTD_N_WIRES(1);
> -			break;
> -		case 4:
> -			rtd->sensor_config = LTC2983_RTD_N_WIRES(2);
> -			break;
> -		case 5:
> -			/* 4 wires, Kelvin Rsense */
> -			rtd->sensor_config = LTC2983_RTD_N_WIRES(3);
> -			break;
> -		default:
> +	/* ADT7604 requires hardcoding sensor configuration bits to 0b1001 */
> +	if (st->info->has_copper_trace &&
> +	    sensor->type == LTC2983_SENSOR_RTD_CUSTOM) {
> +		rtd->sensor_config = 0x9;
> +		if (sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN)

Like the above, we have the following kind of condition all over the
place. In DT we can just have a different type for these and map it to
real value when creating the sensor.

...

>  
>  	/* set common parameters */
> @@ -908,17 +946,27 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s
>  		return dev_err_ptr_probe(dev, ret,
>  					 "rsense channel must be configured...\n");
>  
> -	if (fwnode_property_read_bool(child, "adi,single-ended")) {
> -		thermistor->sensor_config = LTC2983_THERMISTOR_SGL(1);
> -	} else if (fwnode_property_read_bool(child, "adi,rsense-share")) {
> -		/* rotation is only possible if sharing rsense */
> -		if (fwnode_property_read_bool(child, "adi,current-rotate"))
> -			thermistor->sensor_config =
> -						LTC2983_THERMISTOR_C_ROTATE(1);
> -		else
> -			thermistor->sensor_config =
> -						LTC2983_THERMISTOR_R_SHARE(1);
> +	if (st->info->has_copper_trace &&
> +	    sensor->type == LTC2983_SENSOR_THERMISTOR_CUSTOM) {
> +		thermistor->sensor_config = LTC2983_THERMISTOR_C_ROTATE(1);
> +		if (sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN)
> +			return dev_err_ptr_probe(dev, -EINVAL,
> +						 "Invalid chann:%d for leak detector\n",
> +						 sensor->chan);

Same story

> +	} else {
> +		if (fwnode_property_read_bool(child, "adi,single-ended")) {
> +			thermistor->sensor_config = LTC2983_THERMISTOR_SGL(1);
> +		} else if (fwnode_property_read_bool(child, "adi,rsense-share")) {
> +			/* rotation is only possible if sharing rsense */
> +			if (fwnode_property_read_bool(child, "adi,current-rotate"))
> +				thermistor->sensor_config =
> +							LTC2983_THERMISTOR_C_ROTATE(1);
> +			else
> +				thermistor->sensor_config =
> +							LTC2983_THERMISTOR_R_SHARE(1);
> +		}
>  	}
> +
>  	/* validate channel index */
>  	if (!(thermistor->sensor_config & LTC2983_THERMISTOR_DIFF_MASK) &&
>  	    sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN)
> @@ -928,23 +976,36 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s
>  
>  	/* check custom sensor */
>  	if (sensor->type >= LTC2983_SENSOR_THERMISTOR_STEINHART) {
> -		bool steinhart = false;
> -		const char *propname;
> -
> -		if (sensor->type == LTC2983_SENSOR_THERMISTOR_STEINHART) {
> -			steinhart = true;
> -			propname = "adi,custom-steinhart";
> +		if (st->info->has_copper_trace &&
> +		    sensor->type == LTC2983_SENSOR_THERMISTOR_CUSTOM) {
> +			if (fwnode_property_present(child, "adi,custom-leak-detector")) {
> +				thermistor->custom =
> +					__ltc2983_custom_sensor_new(st, child,
> +								    "adi,custom-leak-detector",
> +								    false, 16, false);
> +				if (IS_ERR(thermistor->custom))
> +					return ERR_CAST(thermistor->custom);
> +			}
>  		} else {
> -			propname = "adi,custom-thermistor";
> +			bool steinhart = false;
> +			const char *propname;
> +
> +			if (sensor->type == LTC2983_SENSOR_THERMISTOR_STEINHART) {
> +				steinhart = true;
> +				propname = "adi,custom-steinhart";
> +			} else {
> +				propname = "adi,custom-thermistor";
> +			}
> +
> +			thermistor->custom = __ltc2983_custom_sensor_new(st, child,
> +									 propname,
> +									 steinhart,
> +									 64, false);
> +			if (IS_ERR(thermistor->custom))
> +				return ERR_CAST(thermistor->custom);
>  		}
> -
> -		thermistor->custom = __ltc2983_custom_sensor_new(st, child,
> -								 propname,
> -								 steinhart,
> -								 64, false);
> -		if (IS_ERR(thermistor->custom))
> -			return ERR_CAST(thermistor->custom);
>  	}
> +
>  	/* set common parameters */
>  	thermistor->sensor.fault_handler = ltc2983_common_fault_handler;
>  	thermistor->sensor.assign_chan = ltc2983_thermistor_assign_chan;
> @@ -1167,7 +1228,8 @@ static struct ltc2983_sensor *ltc2983_temp_new(struct fwnode_handle *child,
>  }
>  
>  static int ltc2983_chan_read(struct ltc2983_data *st,
> -			const struct ltc2983_sensor *sensor, int *val)
> +			const struct ltc2983_sensor *sensor,
> +			u32 base_reg, int *val)
>  {
>  	u32 start_conversion = 0;
>  	int ret;
> @@ -1197,13 +1259,23 @@ static int ltc2983_chan_read(struct ltc2983_data *st,
>  	}
>  
>  	/* read the converted data */
> -	ret = regmap_bulk_read(st->regmap, LTC2983_CHAN_RES_ADDR(sensor->chan),
> +	ret = regmap_bulk_read(st->regmap, LTC2983_CHAN_RES_ADDR(sensor->chan, base_reg),
>  			       &st->temp, sizeof(st->temp));
>  	if (ret)
>  		return ret;
>  
>  	*val = __be32_to_cpu(st->temp);
>  
> +	if (base_reg == ADT7604_RES_RES_START_REG) {
> +		/*
> +		 * Resistance result register gives a plain unsigned value,
> +		 * D31 is always 0, no valid bit, no fault bits. Read bits[30:0]
> +		 * directly — the temperature result format does not apply here.
> +		 */
> +		*val &= GENMASK(30, 0);
> +		return 0;
> +	}
> +
>  	if (!(LTC2983_RES_VALID_MASK & *val)) {
>  		dev_err(&st->spi->dev, "Invalid conversion detected\n");
>  		return -EIO;
> @@ -1214,6 +1286,7 @@ static int ltc2983_chan_read(struct ltc2983_data *st,
>  		return ret;
>  
>  	*val = sign_extend32((*val) & LTC2983_DATA_MASK, LTC2983_DATA_SIGN_BIT);
> +
>  	return 0;
>  }
>  
> @@ -1234,7 +1307,12 @@ static int ltc2983_read_raw(struct iio_dev *indio_dev,
>  	switch (mask) {
>  	case IIO_CHAN_INFO_RAW:
>  		mutex_lock(&st->lock);
> -		ret = ltc2983_chan_read(st, st->sensors[chan->address], val);
> +		if (chan->type == IIO_RESISTANCE)
> +			ret = ltc2983_chan_read(st, st->sensors[chan->address],
> +						ADT7604_RES_RES_START_REG, val);
> +		else
> +			ret = ltc2983_chan_read(st, st->sensors[chan->address],
> +						LTC2983_TEMP_RES_START_REG, val);

I think the preferred style is to also have switch() case for the above

>  		mutex_unlock(&st->lock);
>  		return ret ?: IIO_VAL_INT;
>  	case IIO_CHAN_INFO_SCALE:
> @@ -1251,6 +1329,18 @@ static int ltc2983_read_raw(struct iio_dev *indio_dev,
>  			/* 2^21 */
>  			*val2 = 2097152;
>  			return IIO_VAL_FRACTIONAL;
> +		case IIO_RESISTANCE:
> +			/* value in ohm */
> +			*val = 1;
> +			/*
> +			 * Copper trace result is in milliohm with 10 fractional
> +			 * bits: divide by 2^10 * 1000 = 1024000.
> +			 * Leak detector result is in ohm with 10 fractional
> +			 * bits: divide by 2^10 = 1024.
> +			 */
> +			*val2 = (st->sensors[chan->address]->type == LTC2983_SENSOR_RTD_CUSTOM) ?
> +				1024000 : 1024;
> +			return IIO_VAL_FRACTIONAL;

I would prefer a plain if() else

>  		default:
>  			return -EINVAL;
>  		}
> @@ -1292,6 +1382,17 @@ static irqreturn_t ltc2983_irq_handler(int irq, void *data)
>  	__chan; \
>  })
>  
> +#define LTC2983_RESISTANCE_CHAN(index, __address) ({ \
> +	struct iio_chan_spec __chan = { \
> +		.type = IIO_RESISTANCE, \
> +		.indexed = 1, \
> +		.channel = index, \
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \
> +		.address = __address, \
> +	}; \
> +	__chan; \
> +})
> +
>  static int ltc2983_parse_fw(struct ltc2983_data *st)
>  {
>  	struct device *dev = &st->spi->dev;
> @@ -1339,6 +1440,16 @@ static int ltc2983_parse_fw(struct ltc2983_data *st)
>  			return dev_err_probe(dev, ret,
>  				"adi,sensor-type property must given for child nodes\n");
>  
> +		if (st->info->has_copper_trace) {
> +			if ((sensor.type >= LTC2983_SENSOR_THERMOCOUPLE &&
> +			     sensor.type <= LTC2983_SENSOR_THERMOCOUPLE_CUSTOM) ||
> +			     sensor.type == LTC2983_SENSOR_DIODE ||
> +			     sensor.type == LTC2983_SENSOR_DIRECT_ADC)
> +				return dev_err_probe(dev, -EINVAL,
> +			 "sensor type %d not supported on %s\n",
> +			 sensor.type, st->info->name);
> +		}
> +

The above is also not great! Maybe see the possibility of having a
supported sensors mask that you fill in chip_info! Then we would just
test_bit() in here

>  		dev_dbg(dev, "Create new sensor, type %u, chann %u",
>  			sensor.type, sensor.chan);
>  
> @@ -1380,6 +1491,15 @@ static int ltc2983_parse_fw(struct ltc2983_data *st)
>  		st->sensors[chan]->chan = sensor.chan;
>  		st->sensors[chan]->type = sensor.type;
>  
> +		if (st->info->has_copper_trace) {
> +			if (st->sensors[chan]->type == LTC2983_SENSOR_THERMISTOR_CUSTOM &&
> +			    to_thermistor(st->sensors[chan])->custom)
> +				st->iio_channels++;
> +			else if (st->sensors[chan]->type == LTC2983_SENSOR_RTD_CUSTOM &&
> +				 to_rtd(st->sensors[chan])->custom)
> +				st->iio_channels++;
> +		}
> +

Having to go up to to_thermistor() and to_rtd() in a common path like
here also smells :). One possible solution would be to refactor things
so that:

`st->iio_channels = st->num_channels` is not necessarily true.

struct ltc2983_sensor could have a new n_iio_chan count given that now
we can (AFAIU) one sensor with more that one IIO channel. Then we could
count things in a generic way in here.

We might need to change more things that I'm missing now.

>  		channel_avail_mask |= BIT(sensor.chan);
>  		chan++;
>  	}
> @@ -1426,7 +1546,7 @@ static int ltc2983_eeprom_cmd(struct ltc2983_data *st, unsigned int cmd,
>  
>  static int ltc2983_setup(struct ltc2983_data *st, bool assign_iio)
>  {
> -	u32 iio_chan_t = 0, iio_chan_v = 0, chan, iio_idx = 0, status;
> +	u32 iio_chan_t = 0, iio_chan_v = 0, iio_chan_r = 0, chan, iio_idx = 0, status;
>  	int ret;
>  
>  	/* make sure the device is up: start bit (7) is 0 and done bit (6) is 1 */
> @@ -1473,6 +1593,26 @@ static int ltc2983_setup(struct ltc2983_data *st, bool assign_iio)
>  		    !assign_iio)
>  			continue;
>  
> +		/*
> +		 * Copper trace and leak detector sensors without a custom table
> +		 * produce only a resistance result; the chip does not populate
> +		 * the temperature result register. Emit only an IIO_RESISTANCE
> +		 * channel in this case.
> +		 */
> +		if (st->info->has_copper_trace) {
> +			bool resistance_only =
> +				(st->sensors[chan]->type == LTC2983_SENSOR_RTD_CUSTOM &&
> +				 !to_rtd(st->sensors[chan])->custom) ||
> +				(st->sensors[chan]->type == LTC2983_SENSOR_THERMISTOR_CUSTOM &&
> +				 !to_thermistor(st->sensors[chan])->custom);
> +
> +			if (resistance_only) {
> +				st->iio_chan[iio_idx++] =
> +					LTC2983_RESISTANCE_CHAN(iio_chan_r++, chan);
> +				continue;
> +			}
> +		}
> +

My above suggestion would also fit for the above I believe.

>  		/* assign iio channel */
>  		if (st->sensors[chan]->type != LTC2983_SENSOR_DIRECT_ADC) {
>  			chan_type = IIO_TEMP;
> @@ -1488,6 +1628,11 @@ static int ltc2983_setup(struct ltc2983_data *st, bool assign_iio)
>  		 */
>  		st->iio_chan[iio_idx++] = LTC2983_CHAN(chan_type, (*iio_chan)++,
>  						       chan);
> +
> +		if (st->info->has_copper_trace &&
> +		    (st->sensors[chan]->type == LTC2983_SENSOR_RTD_CUSTOM ||
> +		     st->sensors[chan]->type == LTC2983_SENSOR_THERMISTOR_CUSTOM))
> +			st->iio_chan[iio_idx++] = LTC2983_RESISTANCE_CHAN(iio_chan_r++, chan);


I think the above can also be dropped and improved with what I
suggested.

- Nuno Sá


      parent reply	other threads:[~2026-04-28 11:13 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-27 13:25 [PATCH 0/2] iio: temperature: ltc2983: Add support for ADT7604 Liviu Stan
2026-04-27 13:25 ` [PATCH 1/2] dt-bindings: iio: temperature: Add ADT7604 support to adi,ltc2983 Liviu Stan
2026-04-27 19:34   ` Conor Dooley
2026-04-27 13:25 ` [PATCH 2/2] iio: temperature: ltc2983: Add support for ADT7604 Liviu Stan
2026-04-27 18:23   ` Andy Shevchenko
2026-04-28 11:14   ` Nuno Sá [this message]

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=afCVtXBHIIoLlsRo@nsa \
    --to=noname.nuno@gmail.com \
    --cc=Michael.Hennerich@analog.com \
    --cc=andy@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dlechner@baylibre.com \
    --cc=jic23@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=lars@metafoo.de \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=liviu.stan@analog.com \
    --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