All of lore.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: 37+ 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-05-06 13:06     ` Stan, Liviu
2026-05-06 17:26       ` Conor Dooley
2026-05-07  8:53         ` Stan, Liviu
2026-04-28 14:58   ` Jonathan Cameron
2026-05-06 14:52     ` Stan, Liviu
2026-05-07 10:35       ` Jonathan Cameron
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-05-07 15:31     ` Stan, Liviu
2026-05-08  7:44       ` Andy Shevchenko
2026-05-12  7:12         ` Stan, Liviu
2026-05-12  7:57           ` Andy Shevchenko
2026-05-12  9:37             ` Stan, Liviu
2026-05-12 16:25               ` Andy Shevchenko
2026-04-28 11:14   ` Nuno Sá [this message]
2026-05-07 17:25     ` Stan, Liviu
2026-05-08  9:19       ` Nuno Sá
2026-05-08 11:14         ` Jonathan Cameron
2026-05-08 12:46           ` Stan, Liviu
2026-05-08 13:44             ` Nuno Sá
2026-05-08 14:48               ` Stan, Liviu
2026-05-08 16:13                 ` Nuno Sá
2026-05-09 14:46                   ` Jonathan Cameron
2026-05-11  7:52                     ` Stan, Liviu
2026-05-11 11:18                       ` Jonathan Cameron
2026-05-11 12:02                         ` Stan, Liviu
2026-05-12  8:24                           ` Nuno Sá
2026-05-12 10:55                             ` Jonathan Cameron
2026-05-12 11:06                               ` Nuno Sá
2026-05-12 11:55                             ` Stan, Liviu
2026-05-12 12:06                               ` Nuno Sá
2026-05-12 12:26                                 ` Stan, Liviu
2026-05-12 15:56                                   ` Jonathan Cameron
2026-05-13 16:08                                     ` Stan, Liviu

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 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.